Spring MVC通过HandlerExceptionResolver处理程序的异常,包括Handler 映射、数据绑定以及目标方法执行时发生的异常。在Eclipse中可以查看到HandleExceptionResolver接口的实现类:
如果我们没有在springmvc配置文件中配置<mvc:annotation-driven/>,那么DispatcherServlet会默认装配AnnotationMethodHandlerExceptionResolver、DefaultHandlerExceptionResolver和ResponseStatusException三种解析器,但是其中AnnotationMethodHandlerExceptionResolver已经过期了。所以我们配置<mvc:annotation-driven/>,将会把AnnotationMethodHandlerExceptionResolver替换为ExceptionHandlerExceptionResolver。
下面的例子中,我们都默认已经在spring配置文件中配置了<mvc:annotation-driven/>。
ExceptionHandlerExceptionResolver
ExceptionHandlerExceptionResolver主要用于处理Handler 中用@ExceptionHandler注解标记的方法,示例如下:
页面
index.jsp
<a href="testExceptionHandlerExceptionResolver?i=0">
Test ExceptionHandlerExceptionResolver</a>
error.jsp
<h4>Error Page</h4>
${exception }
控制器
private final static String ERROR = "error";
//异常处理器
//value = ArithmeticException.class声明捕获数学异常
@ExceptionHandler(value = ArithmeticException.class)
public ModelAndView handleArithmeticException(Exception ex) {
ModelAndView mv = new ModelAndView(ERROR);
System.out.println("出异常了:" + ex);
mv.addObject("exception", ex);
return mv;
}
//定义一个可以抛出ArithmeticException异常的方法
@RequestMapping("/testExceptionHandlerExceptionResolver")
public String testExceptionHandlerExceptionResolver(@RequestParam("i") int i) {
System.out.println("result:" + 10 / i);
return SUCCESS;
}
运行程序:
点击index.jsp中的超链接,异常处理器会捕获到异常,在控制台输出:
并跳转到error.jsp,且在该页面打印了异常信息:
需要注意的是:
- @ExceptionHandler标记的方法的入参中不能传入Map。 若希望把异常信息传导页面上, 需要使用 ModelAndView 作为返回值。
- @ExceptionHandler 方法标记的异常有优先级的问题。如果我们在上面的例子中同时声明了可以捕获ArithmeticException异常和RuntimeException异常的两个异常处理器,那么会使用匹配度更高的,即捕获ArithmeticException异常的异常处理器。
- 通过以上方式创建的异常处理器只能处理该控制器类内部的异常,若需要统一配置可以捕获所有控制器类抛出的异常,可以新建一个异常处理器类,在类的声明处用@ControllerAdvice注解标记即可。如果在当前 控制器类 中找不到 @ExceptionHandler 方法来处理当前方法出现的异常, 则将去 @ControllerAdvice 标记的类中查找 @ExceptionHandler 标记的方法来处理异常。例如,我们现在创建一个异常处理器类,并在其中定义一个可以处理数学异常的方法:
@ControllerAdvice
public class SpringMVCTestExceptionHandler {
private static final String ERROR = "error";
@ExceptionHandler(value=ArithmeticException.class)
public ModelAndView handleArithmeticException1(Exception ex){
ModelAndView mv = new ModelAndView(ERROR);
System.out.println("[出异常了]:"+ex);
mv.addObject("exception",ex);
return mv;
}
}
注释掉之前定义的handleArithmeticException方法,运行程序,与之前的例子类似,仍然跳转到error.jsp,但是在控制台输出:
表示执行的是我们新定义的handleArithmeticException1方法,注意,如果不注释掉handleArithmeticException方法,那么会优先执行handleArithmeticException方法,因为handleArithmeticException方法和抛出异常的testExceptionHandlerExceptionResolver方法定义在同一个控制器类中,testExceptionHandlerExceptionResolver方法抛出异常后会先在当前 控制器类 中找 @ExceptionHandler方法来处理当前方法出现的异常,如果找不到,才会去 @ControllerAdvice 标记的类中查找 @ExceptionHandler 标记的方法来处理异常。
ResponseStatusExceptionResolver
ResponseStatusExceptionResolver的作用是在异常及异常父类中找到@ResponseStatus注解,然后使用这个注解的属性进行处理。下面举例说明:
页面
<a href="testResponseStatusExceptionResolver?i=13">
Test ResponseStatusExceptionResolver</a>
定义一个异常类
@ResponseStatus(value=HttpStatus.FORBIDDEN,reason="用户名和密码不匹配")
public class UserNameNotMatchPasswordException extends RuntimeException{
/**
*
*/
private static final long serialVersionUID = 1L;
}
模拟一个可以抛出上述异常的方法
//定义一个可以抛出UserNameNotMatchPasswordException异常的方法
@RequestMapping("/testResponseStatusExceptionResolver")
public String testResponseStatusExceptionResolver(@RequestParam("i") int i) {
if(i==13)
throw new UserNameNotMatchPasswordException();
return SUCCESS;
}
运行程序,由于抛出了异常,并且我们没有定义任何异常处理器对该异常进行处理,那么会出现我们定制的异常页面:
此外,@ResponseStatus注解还可以用来标记方法:
@ResponseStatus(reason="测试",value=HttpStatus.NOT_FOUND)
@RequestMapping("/testResponseStatusExceptionResolver")
public String testResponseStatusExceptionResolver(@RequestParam("i") int i) {
if(i==13)
throw new UserNameNotMatchPasswordException();
System.out.println("testResponseStatusExceptionResolver...");
return SUCCESS;
}
现在,我们在浏览器中令i=10,这样方法是不会抛出UserNameNotMatchPasswordException异常的,结果程序跳转到了如下页面:
但是控制台上输出了如下信息,说明这个方法被正常地执行了:
DefaultHandlerExceptionResolver
对一些特殊的异常进行处理,比如NoSuchRequestHandlingMethodException、HttpRequestMethodNotSupportedException等,具体处理哪些异常可以在DefaultHandlerExceptionResolver源码中查看doResolveException()方法。
SimpleMappingExceptionResolver
SimpleMappingExceptionResolver可以将异常类名映射为视图名,即发生异常时使用对应的视图报告异常。下面进行测试:
页面
index.jsp
<a href="testSimpleMappingExceptionResolver?i=21">
Test SimpleMappingExceptionResolver</a>
error.jsp
<h4>Error Page</h4>
${exception }
控制器
// 定义一个可以抛出ArrayIndexOutOfBoundsException异常的方法
@RequestMapping("/testSimpleMappingExceptionResolver")
public String testSimpleMappingExceptionResolver(@RequestParam("i") int i) {
int[] values = new int[20];
System.out.println(values[i]);
return SUCCESS;
}
spring配置文件
<!-- 配置使用 SimpleMappingExceptionResolver 来映射异常 -->
<bean
class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
<property name="exceptionMappings">
<props>
<prop key="java.lang.ArrayIndexOutOfBoundsException">error</prop>
</props>
</property>
</bean>
运行程序:
由于目标方法抛出了数组下标越界异常,程序跳转到了error.jsp页面,并且在页面上打印了异常信息,这是因为在SimpleMappingExceptionResolver中有一个String类型的属性exceptionAttribute,其默认值为exception,所以我们在error.jsp中通过${exception }可以获取到。也可以在配置文件中配置exceptionAttribute的值,例如:
<!-- 配置使用 SimpleMappingExceptionResolver 来映射异常 -->
<bean
class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
<property name="exceptionAttribute" value="ex"></property>
<property name="exceptionMappings">
<props>
<prop key="java.lang.ArrayIndexOutOfBoundsException">error</prop>
</props>
</property>
</bean>
那么,此时需要在error.jsp中通过如下方式在可以获取到异常信息:
<h4>Error Page</h4>
${ex }