SpringMVC学习笔记 | 关于异常处理
SpringMVC通过HandlerExceptionResolver
处理程序的异常,包括Handler映射、数据绑定以及目标方法执行时发生的异常。
DispatcherServlet默认装配的HandlerExceptionResoolver
实现类:
- 没有使用
<mvc:annotation-driven />
配置
(1)AnnotationMethodHandlerExceptionResolver
(2)ResponseStatusExceptionResolver
(3)DefaultHandlerExceptionResolver - 使用了
<mvc:annotation-driven />
配置
(1)ExceptionHandlerExceptionResolver
(2)ResponseStatusExceptionResolver
(3)DefaultHandlerExceptionResolver
ExceptionHandlerExceptionResolver
主要处理Handler中用@ExceptionHandler
注解定义的方法。
@ExceptionHandler
注解定义的方法有优先级问题,例如发生的是ArithmeticException,而声明的异常有ArithmeticException和RuntimeException,此时会根据异常的最近继承关系找到继承深度最浅的那个@ExceptionHandler
注解方法,即标记了ArithmeticException的那个方法。即按继承关系进行最近原则。
如果ExceptionHandlerExceptionResolver
内部找不到@ExceptionHandler
注解的话,会找@ControllerAdvice
注解的类中的@ExceptionHandler
注解方法。
下面我们通过例子来说明:
我们首先先定义一个出现异常的方法:
@RequestMapping(value = "/testExceptionHandlerExceptionResolver")
public String testExceptionHandlerExceptionResolver(@RequestParam("i") int i){
System.out.println("res:"+10/i);
return "success";
}
该方法接受一个整型参数并且将10与参数相除,因此我们只需要传入一个0参数即可发生异常。如果此处没有定义异常,然后我们直接访问http://localhost:8080/springmvc_test/testExceptionHandlerExceptionResolver?i=0,会出错:
我们再定义一个处理异常的方法
@ExceptionHandler({ArithmeticException.class})
public String handleArithmeticException(Exception e){
System.out.println("出异常了:"+e);
return "error";
}
该方法会接受异常,并处理异常,在控制台中打印异常信息并且跳转到error.jsp。
如果我们想要在页面上显示异常信息,我们可能一开始想到的会是在方法的入参中加入一个Map
,但是这样是不可行的。因此我们要使用另外一种方法,返回一个ModelAndView
对象,因此上面的方法修改为如下:
@ExceptionHandler({ArithmeticException.class})
public ModelAndView handleArithmeticException(Exception e){
System.out.println("出异常了:"+e);
ModelAndView mv = new ModelAndView("error");
mv.addObject("exception",e);
return mv;
}
然后我们在error.jsp页面上通过${exception}
即使显示错误信息了。
对于优先级的问题,我们此时再定义一个处理异常的方法:
@ExceptionHandler({RuntimeException.class})
public ModelAndView handleArithmeticException2(Exception e){
System.out.println("***出异常了:"+e);
ModelAndView mv = new ModelAndView("error");
mv.addObject("exception",e);
return mv;
}
此时有两个处理异常的方法,会执行哪一个呢,按照继承关系,是ArithmeticException
异常最近,因此会执行这个,如果我们此时把ArithmeticException
异常的方法去掉,则会执行RuntimeException
异常的方法,如果两个方法都没有则会去查找@ControllerAdvice
标记的类中的@ExceptionHandler
标记的方法,现在我们先把这两个方法给注释,然后创建一个实体类:HandleException,代码如下:
package com.cerr.springmvc.handlers;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.servlet.ModelAndView;
@ControllerAdvice
public class HandleException {
@ExceptionHandler({ArithmeticException.class})
public ModelAndView handleArithmeticException(Exception e){
System.out.println("出异常了:"+e);
ModelAndView mv = new ModelAndView("error");
mv.addObject("exception",e);
return mv;
}
}
这样的话,由于我们的方法的类中没有@ExceptionHandler
注释的方法,因此它会来找@ControllerAdvice
注解的类中的@ExceptionHandler
注解的方法,很明显能找到,因此就执行该方法,如果都没有找到那就报错。
总结而言就是:
- 在
@ExceptionHandler
方法的入参中可以加入Exception
类型的参数,该参数即对应发生的异常对象。 - 在
@ExceptionHandler
方法的入参中不能传入Map
,若希望把异常信息传到页面上,需要使用ModelAndView
作为返回值 -
@ExceptionHandler
方法标记的异常有优先级问题 - 如果在当前handler中找不到
@ExceptionHandler
方法出现的异常,则去@ControllerAdvice
标记的类中查找@ExceptionHandler
标记的方法
ResponseStatusExceptionResolver
处理@ResponseStatus
标注的异常类或异常方法,在异常及异常父类中找到@ResponseStatus
注解,然后使用这个注解的属性进行处理。
@ResponseStatus
注解有两个属性,一个是value
,表示状态码,而reason
表示错误消息。
在类中使用@ResponseStatus注解
我们先定义一个异常类:
package com.cerr.springmvc.test;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;
@ResponseStatus(value = HttpStatus.FORBIDDEN,reason = "用户名和密码不匹配")
public class UserNameNotMatchPasswordException extends RuntimeException{
}
然后我们在控制器类中编写一个方法如下:
@RequestMapping(value = "/testResponseStatusExceptionResolver")
public String testResponseStatusExceptionResolver(@RequestParam("i") int i){
if(i == 13){
throw new UserNameNotMatchPasswordException();
}
System.out.println("testResponseStatusExceptionResolver...");
return "success";
}
这个方法会接收一个参数为i的整形参数,如果i的值为13,就抛出我们刚刚定义的异常类。我们在浏览器中访问http://localhost:8080/springmvc_test/testResponseStatusExceptionResolver?i=13,结果如下:
在目标方法中标记@ResponseStatus
@ResponseStatus(reason = "测试",value = HttpStatus.FORBIDDEN)
@RequestMapping(value = "/testResponseStatusExceptionResolver")
public String testResponseStatusExceptionResolver(@RequestParam("i") int i){
if(i == 13){
throw new UserNameNotMatchPasswordException();
}
System.out.println("testResponseStatusExceptionResolver...");
return "success";
}
我们在刚刚的目标方法上加入了@ResponseStatus
注解,然后我们在浏览器中访问http://localhost:8080/springmvc_test/testResponseStatusExceptionResolver?i=10,此时i不等于13,因此我们自定义的那个异常类不会被触发,但是会触发我们注解上声明的那个异常,结果如下:
DefaultHandlerExceptionResolver
对一些特殊的异常进行处理。
SimpleMappingExceptionResolver
如果希望对所有异常进行统一处理,可以使用SimpleMappingExceptionResolver
,它将异常类名映射为视图名,即发生异常时使用对应的视图报告异常。
我们现在定义一个目标方法:
@RequestMapping(value = "/testSimpleMappingExceptionResolver")
public String testSimpleMappingExceptionResolver(@RequestParam("i") int i){
String [] val = new String[10];
System.out.println(val[i]);
return "success";
}
该方法接受一个参数i,并访问数组的i下标的内容,我们现在要使其发生数组下标越界异常,我们就要传入i>10的值就行了。
我们在配置文件中配置SimpleMappingExceptionResolver
:
<!-- 配置使用SimpleMappingExceptionResolver来映射异常 -->
<bean id="simpleMappingExceptionResolver"
class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
<property name="exceptionMappings">
<props>
<!--发生java.lang.ArrayIndexOutOfBoundsException异常时会跳转到error.jsp页面-->
<prop key="java.lang.ArrayIndexOutOfBoundsException">error</prop>
</props>
</property>
</bean>
在错误页面中对异常信息的显示,通过观察其源码发现,其会在ModelAndView
中将错误信息添加进去,所以在错误页面中我们可以通过${名字}
来显示错误页面,默认的名字为exception
,因此我们可以通过${exception}
来显示。也可以更改这个默认的名字,在<property>
节点中通过exceptionAttribute
属性来修改其名字,例如下面的配置:
<!-- 配置使用SimpleMappingExceptionResolver来映射异常 -->
<bean id="simpleMappingExceptionResolver"
class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
<property name="exceptionAttribute" value="ex" />
<property name="exceptionMappings">
<props>
<prop key="java.lang.ArrayIndexOutOfBoundsException">error</prop>
</props>
</property>
</bean>
该配置中使用了<property name="exceptionAttribute" value="ex" />
,因此我们在页面中要使用${ex}
来显示错误信息。
访问后结果如下:
error.jsp源码:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<h4>Error Page</h4>
${ex}
</body>
</html>