SpringMVC中的异常处理

  Spring MVC通过HandlerExceptionResolver处理程序的异常,包括Handler 映射、数据绑定以及目标方法执行时发生的异常。在Eclipse中可以查看到HandleExceptionResolver接口的实现类:
  
  image_1b1gosocup6i1ugoq3p119qdta9.png-34.4kB

  如果我们没有在springmvc配置文件中配置<mvc:annotation-driven/>,那么DispatcherServlet会默认装配AnnotationMethodHandlerExceptionResolver、DefaultHandlerExceptionResolver和ResponseStatusException三种解析器,但是其中AnnotationMethodHandlerExceptionResolver已经过期了。所以我们配置<mvc:annotation-driven/>,将会把AnnotationMethodHandlerExceptionResolver替换为ExceptionHandlerExceptionResolver。
  下面的例子中,我们都默认已经在spring配置文件中配置了<mvc:annotation-driven/>。

ExceptionHandlerExceptionResolver

  ExceptionHandlerExceptionResolver主要用于处理Handler 中用@ExceptionHandler注解标记的方法,示例如下:


页面
index.jsp

1
2
<a href="testExceptionHandlerExceptionResolver?i=0">
Test ExceptionHandlerExceptionResolver</a>

error.jsp

1
2
<h4>Error Page</h4>
${exception }


控制器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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中的超链接,异常处理器会捕获到异常,在控制台输出:

image_1b1gqo9gavuv47c1s4lq8r1je9m.png-13.7kB

并跳转到error.jsp,且在该页面打印了异常信息:

image_1b1gqpbg41slr1e5dccbvk91oju13.png-12.6kB


需要注意的是:

  1. @ExceptionHandler标记的方法的入参中不能传入Map。 若希望把异常信息传导页面上, 需要使用 ModelAndView 作为返回值。
  2. @ExceptionHandler 方法标记的异常有优先级的问题。如果我们在上面的例子中同时声明了可以捕获ArithmeticException异常和RuntimeException异常的两个异常处理器,那么会使用匹配度更高的,即捕获ArithmeticException异常的异常处理器。
  3. 通过以上方式创建的异常处理器只能处理该控制器类内部的异常,若需要统一配置可以捕获所有控制器类抛出的异常,可以新建一个异常处理器类,在类的声明处用@ControllerAdvice注解标记即可。如果在当前 控制器类 中找不到 @ExceptionHandler 方法来处理当前方法出现的异常, 则将去 @ControllerAdvice 标记的类中查找 @ExceptionHandler 标记的方法来处理异常。例如,我们现在创建一个异常处理器类,并在其中定义一个可以处理数学异常的方法:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    @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,但是在控制台输出:

image_1b1hdanpd1bqcb3sdc215gporm1g.png-26.6kB

表示执行的是我们新定义的handleArithmeticException1方法,注意,如果不注释掉handleArithmeticException方法,那么会优先执行handleArithmeticException方法,因为handleArithmeticException方法和抛出异常的testExceptionHandlerExceptionResolver方法定义在同一个控制器类中,testExceptionHandlerExceptionResolver方法抛出异常后会先在当前 控制器类 中找 @ExceptionHandler方法来处理当前方法出现的异常,如果找不到,才会去 @ControllerAdvice 标记的类中查找 @ExceptionHandler 标记的方法来处理异常。

ResponseStatusExceptionResolver

  ResponseStatusExceptionResolver的作用是在异常及异常父类中找到@ResponseStatus注解,然后使用这个注解的属性进行处理。下面举例说明:


页面

1
2
<a href="testResponseStatusExceptionResolver?i=13">
Test ResponseStatusExceptionResolver</a>


定义一个异常类

1
2
3
4
5
6
7
8
9
@ResponseStatus(value=HttpStatus.FORBIDDEN,reason="用户名和密码不匹配")
public class UserNameNotMatchPasswordException extends RuntimeException{
/**
*
*/
private static final long serialVersionUID = 1L;
}


模拟一个可以抛出上述异常的方法

1
2
3
4
5
6
7
//定义一个可以抛出UserNameNotMatchPasswordException异常的方法
@RequestMapping("/testResponseStatusExceptionResolver")
public String testResponseStatusExceptionResolver(@RequestParam("i") int i) {
if(i==13)
throw new UserNameNotMatchPasswordException();
return SUCCESS;
}


运行程序,由于抛出了异常,并且我们没有定义任何异常处理器对该异常进行处理,那么会出现我们定制的异常页面:

image_1b1hfd8r917su1p7i1viie55ac1t.png-28.8kB

此外,@ResponseStatus注解还可以用来标记方法:

1
2
3
4
5
6
7
8
@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异常的,结果程序跳转到了如下页面:

image_1b1hg7ludgi3dqeadvea917mq2a.png-23.5kB

但是控制台上输出了如下信息,说明这个方法被正常地执行了:

image_1b1hg8ssm5goidn1f5m1rjqjm42n.png-27.9kB

DefaultHandlerExceptionResolver

  对一些特殊的异常进行处理,比如NoSuchRequestHandlingMethodException、HttpRequestMethodNotSupportedException等,具体处理哪些异常可以在DefaultHandlerExceptionResolver源码中查看doResolveException()方法。

SimpleMappingExceptionResolver

  SimpleMappingExceptionResolver可以将异常类名映射为视图名,即发生异常时使用对应的视图报告异常。下面进行测试:


页面

index.jsp

1
2
<a href="testSimpleMappingExceptionResolver?i=21">
Test SimpleMappingExceptionResolver</a>

error.jsp

1
2
<h4>Error Page</h4>
${exception }


控制器

1
2
3
4
5
6
7
// 定义一个可以抛出ArrayIndexOutOfBoundsException异常的方法
@RequestMapping("/testSimpleMappingExceptionResolver")
public String testSimpleMappingExceptionResolver(@RequestParam("i") int i) {
int[] values = new int[20];
System.out.println(values[i]);
return SUCCESS;
}


spring配置文件

1
2
3
4
5
6
7
8
9
<!-- 配置使用 SimpleMappingExceptionResolver 来映射异常 -->
<bean
class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
<property name="exceptionMappings">
<props>
<prop key="java.lang.ArrayIndexOutOfBoundsException">error</prop>
</props>
</property>
</bean>


运行程序:

image_1b1hi0jq9bnd76gji918aa1pe234.png-14.7kB

由于目标方法抛出了数组下标越界异常,程序跳转到了error.jsp页面,并且在页面上打印了异常信息,这是因为在SimpleMappingExceptionResolver中有一个String类型的属性exceptionAttribute,其默认值为exception,所以我们在error.jsp中通过${exception }可以获取到。也可以在配置文件中配置exceptionAttribute的值,例如:

1
2
3
4
5
6
7
8
9
10
<!-- 配置使用 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中通过如下方式在可以获取到异常信息:

1
2
<h4>Error Page</h4>
${ex }