1分到5分技巧
新闻动态

你的位置:1分到5分技巧 > 新闻动态 >

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

发布日期:2025-05-21 02:26    点击次数:135

本文已收录在Github,关注我,紧跟本系列专栏文章,咱们下篇再续!

魔都架构师 | 全网30W技术追随者

大厂分布式系统/数据中台实战专家

主导交易系统百万级流量调优 & 车联网平台架构

AIGC应用开发先行者 | 区块链落地实践者

以技术驱动创新,我们的征途是改变世界!

实战干货:编程严选网

1 错误场景

public class UserController { public UserController { log.info("construct"); } @GetMapping("/reg/{name}") @ResponseBody public String saveUser(String name) { log.info("register JavaEdge success!"); return "OK"; }}

验证请求的Token合法性的Filter。Token校验失败时,直接抛自定义异常,移交给Spring处理:

@WebFilter@Component@Slf4jpublic class PermissionFilter implements Filter { @Override public 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) @ResponseBody public 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加载并暴露ControllerAdvice

WebMvcConfigurationSupport#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 AbstractHandlerMethodExceptionResolver implements ApplicationContextAware, InitializingBean { @Override public void afterPropertiesSet { initExceptionHandlerAdviceCache; ... }

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。

initHandlerExceptionResolvers

当第一次客户端请求发生时,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); } } ...}

3.2 ControllerAdvice如何被Spring消费并处理异常?

3.2.1 DispatcherServlet

① doDispatch

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); } // ...}

② processDispatchResult

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); } }}

③ processHandlerException

protected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) throws Exception { // Success and error responses may use different content types request.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



Powered by 1分到5分技巧 @2013-2022 RSS地图 HTML地图