文章目录
Spring MVC中异常的处理是交给HandlerExceptionResolver实现类的异常处理链来完成的;
HandlerExceptionResolver的实现类,默认有如下:
实现类 | 描述 |
---|---|
SimpleMappingExceptionResolver | 异常类名和错误视图名之间的映射。用于在浏览器应用程序中呈现错误页。 |
DefaultHandlerExceptionResolver | 解决SpringMVC引发的异常并将其映射到HTTP状态代码 |
ResponseStatusExceptionResolver | 使用@responsestatus注释解析异常,并根据注释中的值将其映射到HTTP状态代码。 |
ExceptionHandlerExceptionResolver | 通过调用@controller或@controlleradvice类中的@exceptionhandler方法来解决异常。 |
可以配置多个HandlerExceptionResolver实现类bean,然后通过order属性来指定顺序,order的值越大,在异常链中,越靠后。
配置SimpleMappingExceptionResolver异常处理
以SimpleMappingExceptionResolver为例子,我要将系统中的业务发生的异常,转到特定的错误页面中去。
首先,在系统的webapp/views/error目录中,添加错误页(比如arrayIndexOutOfBoundsExceptionError.jsp和nullPointerExceptionError.jsp);
这个时候我想做的是,如果系统中发生了数组越界的异常,那么就进入arrayIndexOutOfBoundsExceptionError页面 ,如果发生了空指针异常,那么就进入nullPointerExceptionError页面。
具体做法如下:
配置异常处理器:
<!-- 异常处理器 -->
<bean id="simpleMappingExceptionResolver" class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
<property name="exceptionMappings">
<props>
<prop key="java.lang.NullPointerException">/error/nullPointerExceptionError</prop>
<prop key="java.lang.ArrayIndexOutOfBoundsException">/error/arrayIndexOutOfBoundsExceptionError</prop>
</props>
</property>
</bean>
现在,模拟一下系统发生异常:
@GetMapping("exception")
public String testException(String value) {
if("NullPointerException".equals(value)) {
throw new NullPointerException();
}else if("ArrayIndexOutOfBoundsException".equals(value)) {
throw new ArrayIndexOutOfBoundsException();
}
String viewName = "/test/testPage";
return viewName;
}
当访问:http://fyk:8080/app/test/exception?requisiteParam=fyk&value=ArrayIndexOutOfBoundsException
的时候,代码就会抛出数组越界的异常,此时就会进入arrayIndexOutOfBoundsExceptionError.jsp错误页。
当然,SimpleMappingExceptionResolver这个类是Spring提供的,我们也可以自己实现HandlerExceptionResolver接口,然后在跳转到错误页的时候,带上一些信息。
使用@ExceptionHandler注解异常
把上一步中xml里配置的异常处理配置删掉,然后在controller中,写如下代码:
@ExceptionHandler(Exception.class)
public ModelAndView handleIOException(Exception ex) {
ModelAndView modelAndView = new ModelAndView();
if(ex instanceof NullPointerException) {
modelAndView.setViewName("/error/nullPointerExceptionError");
}else {
modelAndView.setViewName("/error/arrayIndexOutOfBoundsExceptionError");
}
return modelAndView;
}
需要说明的是,@ExceptionHandler注解,可以接受一个参数为异常类型的数组作为参数;另外,也可以不用给任何值,此时,方法中的参数,就是相当于他的值了。
另外,这个handleIOException方法,还可以接受HttpServletRequest参数,返回值也可以是String类型——这种情况下会被解析为视图名——可以是ModelAndView类型的对象,也可以是ResponseEntity。或者你还可以在方法上添加@ResponseBody注解以使用消息转换器会转换信息为特定类型的数据,然后把它们写回到响应流中。
初看,以上介绍的两种方式,都可以达到同样的效果,但是使用@ExceptionHandler方式,只有在该controller下有效,并没有达到全局配置的效果。当然,可以写在一个基础父类中,让所有的controller都继承这个父类。这样做,似乎有点侵略性。以下介绍一种终结方式。
@ControllerAdvice与@ExceptionHandler的组合使用
这个是在做微服务的时候遇到的一个实际场景:在前后端分离的情况下,整个系统返回的数据格式,都应该统一,所以我们对整个系统会抛出的异常进行了自定义,所以一般而言,系统抛出的异常都会是自定义异常。(唉,算了,在这个文章中先不说这个了,在写微服务的时候,再详细说。)
总之,现在要做的就是,实现一个Spring MVC的全局统一异常处理:
@ControllerAdvice
@Slf4j
public class BaseControllerConfig {
@ResponseBody
@ExceptionHandler({ Exception.class }) // 指定拦截异常的类型
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) // 返回的http状态码:500
public FykResponseEntity<Object> customExceptionHandler(Exception e) {
FykResponseEntity<Object> responseEntity = new FykResponseEntity<>();
if (e instanceof CustomException) {
CustomException customException = (CustomException) e;
responseEntity.setErrCode(customException.getErrCode());
responseEntity.setCustomMess(customException.getCustomMess());
} else {
log.error(e.getMessage(), e);
responseEntity.setErrCode(ExceptionCode.UNDEFINED_ERR.getErrCode());
responseEntity.setCustomMess("未定义异常");
}
return responseEntity;
}
}
在使用的时候,把这个类注入到spring中就ok了。至于@ExceptionHandler和它修饰的方法,和上小节中叙述的是一样的。而@ResponseStatus的作用的,将http的响应状态设置为500。