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

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

本文已收录在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
@Slf4j
public 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();
    }
}
@RestControllerAdvice
public 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:

@Bean
public HandlerExceptionResolver handlerExceptionResolver(
    @Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager) {
  List<HandlerExceptionResolver> 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<ControllerAdviceBean> 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. */
@Nullable
private List<HandlerExceptionResolver> handlerExceptionResolvers;
private void initHandlerExceptionResolvers(ApplicationContext context) {
  this.handlerExceptionResolvers = null;
  if (this.detectAllHandlerExceptionResolvers) {
    // Find all HandlerExceptionResolvers in the ApplicationContext, including ancestor contexts.
    Map<String, HandlerExceptionResolver> 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
@Slf4j
public class PermissionFilter implements Filter {
    @Autowired
    @Qualifier("handlerExceptionResolver")
    private HandlerExceptionResolver resolver;

然后,在 doFilter 捕获异常并移交 HandlerExceptionResolver:

@Override
public 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 请求,日志:

响应体:

{
 "code": 403
}
目录
相关文章
|
缓存 Java 开发者
【Spring】原理:Bean的作用域与生命周期
本文将围绕 Spring Bean 的作用域与生命周期展开深度剖析,系统梳理作用域的类型与应用场景、生命周期的关键阶段与扩展点,并结合实际案例揭示其底层实现原理,为开发者提供从理论到实践的完整指导。
1226 22
|
人工智能 Java 开发者
【Spring】原理解析:Spring Boot 自动配置
Spring Boot通过“约定优于配置”的设计理念,自动检测项目依赖并根据这些依赖自动装配相应的Bean,从而解放开发者从繁琐的配置工作中解脱出来,专注于业务逻辑实现。
3167 0
|
XML Java 测试技术
《深入理解Spring》:IoC容器核心原理与实战
Spring IoC通过控制反转与依赖注入实现对象间的解耦,由容器统一管理Bean的生命周期与依赖关系。支持XML、注解和Java配置三种方式,结合作用域、条件化配置与循环依赖处理等机制,提升应用的可维护性与可测试性,是现代Java开发的核心基石。
|
存储 人工智能 自然语言处理
RAG 调优指南:Spring AI Alibaba 模块化 RAG 原理与使用
通过遵循以上最佳实践,可以构建一个高效、可靠的 RAG 系统,为用户提供准确和专业的回答。这些实践涵盖了从文档处理到系统配置的各个方面,能够帮助开发者构建更好的 RAG 应用。
7538 117
|
XML Java 应用服务中间件
【SpringBoot(一)】Spring的认知、容器功能讲解与自动装配原理的入门,带你熟悉Springboot中基本的注解使用
SpringBoot专栏开篇第一章,讲述认识SpringBoot、Bean容器功能的讲解、自动装配原理的入门,还有其他常用的Springboot注解!如果想要了解SpringBoot,那么就进来看看吧!
981 2
|
Java 关系型数据库 数据库
深度剖析【Spring】事务:万字详解,彻底掌握传播机制与事务原理
在Java开发中,Spring框架通过事务管理机制,帮我们轻松实现了这种“承诺”。它不仅封装了底层复杂的事务控制逻辑(比如手动开启、提交、回滚事务),还提供了灵活的配置方式,让开发者能专注于业务逻辑,而不用纠结于事务细节。
1368 1
|
XML Java 开发者
Spring Boot开箱即用可插拔实现过程演练与原理剖析
【11月更文挑战第20天】Spring Boot是一个基于Spring框架的项目,其设计目的是简化Spring应用的初始搭建以及开发过程。Spring Boot通过提供约定优于配置的理念,减少了大量的XML配置和手动设置,使得开发者能够更专注于业务逻辑的实现。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,为开发者提供一个全面的理解。
481 0
|
前端开发 Java 数据库连接
Spring核心原理剖析与解说
每个部分都是将一种巨大并且复杂的技术理念传达为更易于使用的接口,而这就是Spring的价值所在,它能让你专注于开发你的应用,而不必从头开始设计每一部分。
463 32
|
缓存 安全 Java
Spring 框架核心原理与实践解析
本文详解 Spring 框架核心知识,包括 IOC(容器管理对象)与 DI(容器注入依赖),以及通过注解(如 @Service、@Autowired)声明 Bean 和注入依赖的方式。阐述了 Bean 的线程安全(默认单例可能有安全问题,需业务避免共享状态或设为 prototype)、作用域(@Scope 注解,常用 singleton、prototype 等)及完整生命周期(实例化、依赖注入、初始化、销毁等步骤)。 解析了循环依赖的解决机制(三级缓存)、AOP 的概念(公共逻辑抽为切面)、底层动态代理(JDK 与 Cglib 的区别)及项目应用(如日志记录)。介绍了事务的实现(基于 AOP
583 0
|
Java 开发者 Spring
Spring框架 - 深度揭秘Spring框架的基础架构与工作原理
所以,当你进入这个Spring的世界,看似一片混乱,但细看之下,你会发现这里有个牢固的结构支撑,一切皆有可能。不论你要建设的是一座宏大的城堡,还是个小巧的花园,只要你的工具箱里有Spring,你就能轻松搞定。
638 9