阿里四面:Spring Exception的原理你精通了吗?

360影视 国产动漫 2025-04-28 14:17 2

摘要:@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:

private void initExceptionHandlerAdviceCache {...List adviceBeans = ControllerAdviceBean.findAnnotatedBeans(getApplicationContext);for (ControllerAdviceBean adviceBean : adviceBeans) {Class beanType = adviceBean.getBeanType;if (beanType == null) {throw new IllegalStateException("Unresolvable type for ControllerAdviceBean: " + adviceBean);}ExceptionHandlerMethodResolver resolver = new ExceptionHandlerMethodResolver(beanType);if (resolver.hasExceptionMappings) {this.exceptionHandlerAdviceCache.put(adviceBean, resolver);}...}

所有被 @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

相关推荐