图1 DispatchServlet 继承关系
图2 一次http请求经过的处理类 tomcat ---coyote---catalina---springframework
图3 一次http请求经过的处理类 tomcat ---coyote---catalina---springframework
图4 catalina 中经过的Filter 调用链 ApplicationFilterChain filter逻辑调用一次忽略处理结果,原路返回 可在catch finally里面做文章
图5 catalina 中Filter 具体实例 继承关系(实现接口) OrderedHiddenHttpMethodFilter 可以隐藏掉某些http 请求处理方法如 PUT DELETE 等
图6 springframework 中 DispatcherServlet 中根据request 映射对应的HandlerMapping
注意:如果请求的URL不存在,会遍历下图所有的HandlerMappings 直到遇到一个能返回默认的HandlerMapping
图7 springframework 中 DispatcherServlet 的HandlerMapping 实现举例
图8 如题描述,若果URL不存在,再返回默认的HandlerMapping 图6中的第5个即SimplerUrlHandlerMapping映射的Handler为 ResourceHttpRequestHandler 【 url = /**作为key的handler就是ResourceHttpRequestHandler】,ResourceHttpRequestHandler包含5个拦截器(根据项目不同而不同),如果处理过程中有一个拦截器(本图中第二个JWT拦截器)抛出了异常,则DispatcherServlet中的ha.handler(req,response)不会执行,因为拦截器的方法是有返回值的。
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// Actually invoke the handler. 可能没有机会执行
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
图9 springframework 中 DispatcherServlet 的 HandlerExecutionChain 拦截器处理链
图10 springframework 中 DispatcherServlet 的 HandlerExecutionChain 拦截器处理链中的拦截器抽象类,后面可以自己定义拦截器并在项目中扩展org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter接口 添加到容器中
图11 如题描述,若果URL不存在,再返回默认的HandlerMapping 图6中的第5个即SimplerUrlHandlerMapping映射的Handler为 ResourceHttpRequestHandler ,ResourceHttpRequestHandler包含5个拦截器(根据项目不同而不同),如果处理过程中有一个拦截器(本图中第二个JWT拦截器)抛出了异常,后面的处理会经过 org.springframework.web.servlet.DispatcherServlet#processDispatchResult 处理,但是异常无法解析,开始往上层返回,即遍历所有已经经过的拦截器的执行后处理方法(回调)如图8的返回虚线,逆向返回。调用拦截器方法
default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,
@Nullable Exception ex) throws Exception {
}
图12 异常从DispatcherServlet -->FramworkServlet---->catalina的ApplicationFilterChain ---->ApplicationDispatcher--->异常处理 封装新url=error的请求,再次发送请求。重复上面的down方向的请求
在次请求error url时对应的handlerMapping变为如下图所示,说明找到了 controller控制器!
根据启动日志也能发现这点!
2019-04-20 23:33:25,152]-[INFO]-[main]-[?:]-[Mapped "{[/error]}" onto public org.springframework.http.ResponseEntity
2019-04-20 23:33:25,152]-[INFO]-[main]-[?:]-[Mapped "{[/error],produces=[text/html]}" onto public org.springframework.web.servlet.ModelAndView org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController.errorHtml(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse)
请求error处理控制器
springboot默认ERROR处理器✨登场
最终返回json结果
{"timestamp":1555773618682,"status":500,"error":"Internal Server Error","message":"Unable to connect to Redis; nested exception is io.lettuce.core.RedisConnectionException: Unable to connect to 120.27.23.54:6379","path":"/api/shandong/huimin/jianglou"}%
所以我们可以得到情形的分析:
1 拦截器异常&&URL不存在
分析:ApplicationDispatcher 会发出二次请求到BasicErrorController 返回500 JSON结果!
第二次请求会到达BasicErrorController error 方法,而且构建状态码从上次请求的attributes中获取到了 是 500 !
但是!有一种特殊情况 :拦截器异常&&URL不存在 && ApplicationDispatcher 会发出二次请求时拦截器又异常了(即url = error的请求也被拦截器拦截并发生了异常) 会发生什么?
答案是:由org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver#getExceptionHandlerMethod来决定,比如业务自己定义了异常处理,则按照业务定义返回JSON数据,可能就不是500了,因为二次请求时拦截器又异常,这个异常完全可以是业务自定义异常,然后转换为自己业务想要的返回结果。即和可能不是500!
比如对 public org.springframework.http.ResponseEntity
如下:没有解析成功
但是 如果我们业务使用advice注解方式,就能解析自己定义异常了
@ControllerAdvice
public class DefaultExceptionHandler {
@ExceptionHandler(value = BusinessException.class)
@ResponseBody
public BusinessException handlerError(HttpServletRequest req, BusinessException ex) {
logger.warn("path:{}, queryParam:{},errorCode:{} message:{}", req.getRequestURI(), req.getQueryString(),
ex.getErrorCode(), ex);
return ex;
}
}
如上就返回的自定义的数据(比如把自定义的异常类返回给前端(返回前端自定义的协议数据),前端自己决定做什么处理)。
至于为什么 有第二次请求,可以参考相关的历史文献,记得分享给我们!
2 拦截器正常&&URL不存在
分析:如上图8 还是映射到资源处理器设置状态码404,但是还是同样由ApplicationDispatcher 会发出二次请求最终返回 404!
org.springframework.web.servlet.resource.ResourceHttpRequestHandler
虽然返回了404 但是committed为false
第二次请求同样会到达BasicErrorController error 方法,只不过构建状态码从上次请求的attributes中获取到了 是 404 !
3 拦截器异常&&URL存在
分析:
第一种情况:同1的"但是!有一种特殊情况 :拦截器异常&&URL不存在 && ApplicationDispatcher 会发出二次请求时拦截器又异常了(即url = error的请求也被拦截器拦截并发生了异常) 会发生什么?" 相当于直接进行 拦截器异常&&URL存在,因为URL对应的controller是存在的,所以可以自定义异常处理器,返回前端自定义的协议数据。
第二种情况:如果没有异常处理器
返回:也是500 但不是json化的,默认就是html如下
HTTP Status 500 – Internal Server Error HTTP Status 500 – Internal Server Error
4 拦截器正常&&URL存在
分析:正常的业务数据。
5 问题 : 如何在小程序应用上避免 500 这么丑的结果呢?
1 拦截器不要抛出异常,可以try catch住,让业务逻辑走通即可;
2 扩展 org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice 检测返回对象,必要时替换掉
3 拦截器中检测,response 状态码,重写异常数据