摘要:@WebFilter@Component@Slf4jpublic class PermissionFilter implements Filter {@Overridepublic void doFilter(ServletRequest request, S
本文已收录在Github,关注我,紧跟本系列专栏文章,咱们下篇再续!
public class UserController {public UserController {log.info("construct");}@GetMapping("/reg/{name}")@ResponseBodypublic String saveUser(String name) {log.info("register JavaEdge success!");return "OK";}}验证请求的Token合法性的Filter。Token校验失败时,直接抛自定义异常,移交给Spring处理:
@WebFilter@Component@Slf4jpublic class PermissionFilter implements Filter {@Overridepublic void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {HttpServletRequest httpServletRequest = (HttpServletRequest) request;String token = httpServletRequest.getHeader("token");if (!"JavaEdge".equals(token)) {log.info("throw IllegalRequestException");resolver.resolveException(httpServletRequest, httpServletResponse, null,new IllegalRequestException);}chain.doFilter(request, response);}}public class IllegalRequestException extends RuntimeException {public IllegalRequestException {super;}}@RestControllerAdvicepublic class IllegalRequestExceptionHandler {@ExceptionHandler(IllegalRequestException.class)@ResponseBodypublic String handle {System.out.println("403");return "{\"code\": 403}";}}测试HTTP请求:
日志输出如下:说明IllegalRequestExceptionHandler未生效。
why?需精通Spring异常处理流程。
2 解析当所有Filter执行完毕,Spring才处理Servlet相关,在DispatcherServlet,Spring处理了请求和处理器的对应关系及统一异常处理。
Filter内异常无法被统一处理,因为异常处理发生在DispatcherServlet#doDispatch,但此时,过滤器已全部执行完。
3 Spring异常统一处理3.1 Spring加载并暴露ControllerAdviceWebMvcConfigurationSupport#handlerExceptionResolver实例化并注册一个ExceptionHandlerExceptionResolver:
@Beanpublic HandlerExceptionResolver handlerExceptionResolver(@Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager) {List exceptionResolvers = new ArrayList;configureHandlerExceptionResolvers(exceptionResolvers);if (exceptionResolvers.isEmpty) {// 添加默认异常解析器addDefaultHandlerExceptionResolvers(exceptionResolvers, contentNegotiationManager);}extendHandlerExceptionResolvers(exceptionResolvers);HandlerExceptionResolverComposite composite = new HandlerExceptionResolverComposite;composite.setOrder(0);composite.setExceptionResolvers(exceptionResolvers);return composite;}最终,Spring实例化ExceptionHandlerExceptionResolver类:
public class ExceptionHandlerExceptionResolver extends AbstractHandlerMethodExceptionResolverimplements ApplicationContextAware, InitializingBean {@Overridepublic void afterPropertiesSet {initExceptionHandlerAdviceCache;...}完成所有ControllerAdvice中的ExceptionHandler初始化:查找 @ControllerAdvice 注解的Bean集,放入exceptionHandlerAdviceCache。这里看到自定义illegalRequestExceptionHandler:
所有被 @ControllerAdvice 注解的异常处理器,都在 ExceptionHandlerExceptionResolver 实例化时自动扫描并装载在其exceptionHandlerAdviceCache。
当第一次客户端请求发生时,DispatcherServlet#initHandlerExceptionResolvers 获取所有注册到 Spring 的 HandlerExceptionResolver 实例(如ExceptionHandlerExceptionResolver),存到handlerExceptionResolvers:
/** List of HandlerExceptionResolvers used by this servlet. */@Nullableprivate List handlerExceptionResolvers;private void initHandlerExceptionResolvers(ApplicationContext context) {this.handlerExceptionResolvers = null;if (this.detectAllHandlerExceptionResolvers) {// Find all HandlerExceptionResolvers in the ApplicationContext, including ancestor contexts.Map matchingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerExceptionResolver.class, true, false);if (!matchingBeans.isEmpty) {this.handlerExceptionResolvers = new ArrayList(matchingBeans.values);// We keep HandlerExceptionResolvers in sorted order.AnnotationAwareOrderComparator.sort(this.handlerExceptionResolvers);}}...}protected void doDispatch(HttpServletRequest request, HttpServletResponse response)throws Exception {// ...try {ModelAndView mv = null;Exception dispatchException = null;try {// ...// 查找当前请求对应的 handler, 并执行// ...}catch (Exception ex) {// 先赋值 dispatchException = ex;}catch (Throwable err) {dispatchException = new NestedServletException("Handler dispatch failed", err);}// 再移交processDispatchResult(processedRequest, response, mappedHandler, mv,dispatchException);}// ...}private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,@Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,@Nullable Exception exception) throws Exception {boolean errorView = false;if (exception != null) {if (exception instanceof ModelAndViewDefiningException) {logger.debug("ModelAndViewDefiningException encountered", exception);mv = ((ModelAndViewDefiningException) exception).getModelAndView;}else {Object handler = (mappedHandler != null ? mappedHandler.getHandler : null);// 当Exception非空时,继续移交mv = processHandlerException(request, response, handler, exception);errorView = (mv != null);}}}protected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) throws Exception {// Success and error responses may use different content typesrequest.removeAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);// Check registered HandlerExceptionResolvers...ModelAndView exMv = null;if (this.handlerExceptionResolvers != null) {for (HandlerExceptionResolver resolver : this.handlerExceptionResolvers) {// 从 handlerExceptionResolvers 获取有效的异常解析器以解析异常exMv = resolver.resolveException(request, response, handler, ex);if (exMv != null) {break;}}}}这里的 handlerExceptionResolvers 一定包含声明的IllegalRequestExceptionHandler#IllegalRequestException 的异常处理器的 ExceptionHandlerExceptionResolver 包装类。
4 修正为利用到 Spring MVC 异常处理机制,改造Filter:
手动捕获异常将异常通过 HandlerExceptionResolver 解析处理修改 PermissionFilter,注入 HandlerExceptionResolver:
@WebFilter@Component@Slf4jpublic class PermissionFilter implements Filter {@Autowired@Qualifier("handlerExceptionResolver")private HandlerExceptionResolver resolver;然后,在 doFilter 捕获异常并移交 HandlerExceptionResolver:
@Overridepublic void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {HttpServletRequest httpServletRequest = (HttpServletRequest) request;HttpServletResponse httpServletResponse = (HttpServletResponse) response;String token = httpServletRequest.getHeader("token");if (!"JavaEdge".equals(token)) {log.info("throw IllegalRequestException");// see! resolver.resolveException(httpServletRequest, httpServletResponse, null,new IllegalRequestException);return;// see! }chain.doFilter(request, response);}再用错误 Token 请求,日志:
[21:40:21.095] [http-nio-12345-exec-1] [INFO ] [c.j.spring.exception.PermissionFilter:35 ] - throw IllegalRequestException 403来源:JavaEdge