SpringMVC之异常处理机制
SpringBoot异常处理机制
默认异常处理机制
springboot默认提供了一个处理/error的handler,全局异常处理。
对于机器客户端来说,产生JSON(具体的错误)、状态码和异常信息;
对于浏览器来说,产生一个白页同时附带上html错误信息;
为了自定义,还可以利用视图来解析和错误。
浏览器端响应页面
机器端响应JSON
对比一下,二者错误信息十分类似。
SpringBoot提供利用自定义页面处理异常
可以自定义页面在错误发生的时候响应到该页面上来
放在public/templates目录下的error目录下的页面来进行渲染响应,而且也说明了如果是以5开头的错误,都可以进入到5xx这个页面进行渲染响应,同理4xx也是可以的。
默认异常处理机制源码
看一下springboot中提供的ErrorMvcAutoConfiguration异常自动配置原理
首先放入了两个组件:DefaultErrorAttributes和BasicErrorController
BasicErrorController
看一下对应的类的信息,默认在server.error.path没有配置的时候,BasicErrorController默认处理的是/error请求。
然后接着看下面的handlermethod
存在着两种处理方式:一个是html,另外一个是json格式的数据。
对于html页面来说,默认响应的页面是error页面。
除此之外,还会放置一个默认的error视图bean和一个处理error的视图解析器,这个视图解析器是按照bean的名称来进行解析的。
也就是说,如果发送了/error请求,会从容器中根据/error作为组件ID来找(利用BeanNameViewResolver来找到的),然后进行渲染。
所以视图长什么样子,就渲染成什么样子。默认放置的view是StaticView,看看类中的渲染方式:
这里是默认的渲染页面风格
上面的只是响应页面,但是也可以响应json。
这里是根据内容协商原理来进行判断的。
也就是说如果希望的是响应JSON,那么将会调用BasicErrorController的error方法来处理当前请求,然后根据响应json还是页面来进行确定是否使用默认的视图解析器来进行响应。
DefaultErrorViewResolver
默认错误视图解析器
从这里可以知道官网中说明的支持4xx和5xx的大概逻辑了。
那么看一下具体的实现逻辑:
根据状态码来决定view。可以精确匹配,也可以模糊匹配。
如状态码是405,那么对应的页面是/error/405.html,也可以是/errot/4xx.html
从这里可以看到/error下面,然后加上错误错误码对应的详细信息来匹配到对应的viewname作为ModelAndView中的view对象。
DefaultErrorAttributes
从这里可以看到默认的错误属性,然后联想到响应出去的json和html中的信息都应该保存在哪个地方呢?就是这个地方了。
响应的错误信息会保存到reqeust作用域对象中来,然后在响应的时候会根据具体信息来进行删除。
这里只是简单的分析,但是具体是如何联动起来的,再来进行分析。
异常处理步骤
从例子开始,先来写一个案例来进行说明:
@RestController
public class ExceptionController {
@RequestMapping("/teste")
public String testException(){
int i = 1 / 0;
return "success";
}
}
访问的时候,下面这行代码肯定会出现问题:
// Actually invoke the handler.
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
然后看看里面是如何来进行处理的:
try {
ModelAndView mv = null;
Exception dispatchException = null;
try {
processedRequest = checkMultipart(request);
multipartRequestParsed = (processedRequest != request);
.............
// Actually invoke the handler.
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
...........
applyDefaultViewName(processedRequest, mv);
mappedHandler.applyPostHandle(processedRequest, response, mv);
}
catch (Exception ex) {
dispatchException = ex;
}
catch (Throwable err) {
dispatchException = new NestedServletException("Handler dispatch failed", err);
}
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
目标方法执行期间,出现了任何异常都会被之后,然后肯定会来到processDispatchResult方法中来进行处理异常。
这里也说明了什么?说明了controller中出现了异常信息,那么会有异常处理器来进行处理请求。
开始来处理Handler中出现的异常。
上来就是先把request作用域中设置的响应数据格式给清空掉,从新匹配新的数据格式。
开始来找异常处理器来处理请求,如果找到异常处理器能够处理请求,那么返回ModelAndView;如果没有找到,那么将再次抛出异常。
看看遍历异常处理器如何处理的
遍历异常处理器处理
DefaultErrorAttributes
实现了HandlerExceptionResolver接口
这个类的出来方式就是只做存储,返回null
public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler,Exception ex) {
storeErrorAttributes(request, ex);
return null;
}
将异常信息存储起来。
ExceptionHandlerExceptionResolver
用来处理方法上加了@ExceptionHandlerException注解信息的。
类似如下:
@ControllerAdvice
public class ExceptionControllerAdvice {
private static final Logger logger = LoggerFactory.getLogger(ExceptionControllerAdvice.class);
public ExceptionControllerAdvice() {
}
@ExceptionHandler({Throwable.class})
@ResponseStatus(HttpStatus.OK)
@ResponseBody
public CommonHttpEntity<Object> buildErrorMessage(Throwable t) {
if (t instanceof ServiceException) {
ServiceException serviceException = (ServiceException) t;
return CommonHttpEntity.buildFailResponse(serviceException);
} else {
logger.error("未捕获异常", t);
return CommonHttpEntity.buildFailResponse(ServiceExeceptionEnum.F999.buildServiceException());
}
}
}
那么就会进入到下面的响应中来
这么做的目的在于不管是否发生了错误,那么都将会来响应JSON数据。
这个是我们最终需要采用的数据格式。
ResponseStatusExceptionResolver
如果在方法上标注了@ResponseStatus那么这里将会来获取得到信息。
这里处理方式和ExceptionHandlerExceptionResolver是相同的处理逻辑。
DefaultHandlerExceptionResolver
和上面是相同的处理逻辑
没有任何异常处理器处理
将会抛出异常,进入到拦截器的最终拦截方法中来,做没有意义的事情。
但是此时此刻,springmvc将会再发一个/error的请求,那么将会由BasicErrorController中来进行请求。
又因为对于客户端来说(浏览器)发送的Accep字段中接收text的权重比较高,所以是响应页面的。
默认的 DefaultErrorViewResolver ,作用是把响应状态码作为错误页的地址,error/500.html
模板引擎最终响应这个页面 error/500.html
SpringBoot处理处理最佳方案
总结一下springboot中可以使用的几种方式:
-
1、自定义错误页
-
- error/404.html error/5xx.html;有精确的错误状态码页面就匹配精确,没有就找 4xx.html;如果都没有就触发白页
-
2、 @ControllerAdvice+@ExceptionHandler处理全局异常
-
- 底层是 ExceptionHandlerExceptionResolver 支持的
-
3、@ResponseStatus+自定义异常
-
- 底层是 ResponseStatusExceptionResolver ,把responsestatus注解的信息底层调用 response.sendError(statusCode, resolvedReason);tomcat发送的/error
-
4、Spring底层的异常
-
- 如 参数类型转换异常;DefaultHandlerExceptionResolver 处理框架底层的异常response.sendError(HttpServletResponse.SC_BAD_REQUEST, ex.getMessage());
推荐使用第二种方式
@ControllerAdvice
public class ExceptionControllerAdvice {
private static final Logger logger = LoggerFactory.getLogger(ExceptionControllerAdvice.class);
public ExceptionControllerAdvice() {
}
@ExceptionHandler({Throwable.class})
@ResponseStatus(HttpStatus.OK)
@ResponseBody
public CommonHttpEntity<Object> buildErrorMessage(Throwable t) {
if (t instanceof ServiceException) {
ServiceException serviceException = (ServiceException) t;
return CommonHttpEntity.buildFailResponse(serviceException);
} else {
logger.error("未捕获异常", t);
return CommonHttpEntity.buildFailResponse(ServiceExeceptionEnum.F999.buildServiceException());
}
}
}