spring @ControllerAdvice源码分析

@ControllerAdvice的实现主要在ExceptionHandlerExceptionResolver中

初始化

ExceptionHandlerExceptionResolve实现了InitializingBean的afterPropertiesSet接口,在这个接口中调用了initExceptionHandlerAdviceCache方法,initExceptionHandlerAdviceCache方法调用了ControllerAdviceBean类findAnnotatedBeans方法查询所有的ControllerAdvice注解,并添加到adviceBeans中,并调用了排序接口进行排序,并返回所有的注解标记的bean集合。

图1 查询所有的ControllerAdvice注解


public static List<ControllerAdviceBean> findAnnotatedBeans(ApplicationContext context) {
   List<ControllerAdviceBean> adviceBeans = new ArrayList<>();
   for (String name : BeanFactoryUtils.beanNamesForTypeIncludingAncestors(context, Object.class)) {
      if (!ScopedProxyUtils.isScopedTarget(name)) {
         ControllerAdvice controllerAdvice = context.findAnnotationOnBean(name, ControllerAdvice.class);
         if (controllerAdvice != null) {
            // Use the @ControllerAdvice annotation found by findAnnotationOnBean()
            // in order to avoid a subsequent lookup of the same annotation.
            adviceBeans.add(new ControllerAdviceBean(name, context, controllerAdvice));
         }
      }
   }
   OrderComparator.sort(adviceBeans);
   return adviceBeans;
}

ExceptionHandlerExceptionResolve的initExceptionHandlerAdviceCache方法,获取到所有的ControllerAdviceBean后,遍历并初始化ExceptionHandlerMethodResolve类,在把他们直接的关系保存在exceptionHandlerAdviceCache中

图2


private void initExceptionHandlerAdviceCache() {
   if (getApplicationContext() == null) {
      return;
   }
   //调用了图一的代码
   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);
      }
      //调用查找类中包含@ExceptionHandler注解,并进行初始化,具体查看图3
      ExceptionHandlerMethodResolver resolver = new ExceptionHandlerMethodResolver(beanType);
      if (resolver.hasExceptionMappings()) {
          //存放ControllerAdviceBean与resolver映射关系,转换异常是会有用,具体查看图6
         this.exceptionHandlerAdviceCache.put(adviceBean, resolver);
      }
      if (ResponseBodyAdvice.class.isAssignableFrom(beanType)) {
         this.responseBodyAdvice.add(adviceBean);
      }
   }
   }
}

ExceptionHandlerMethodResolve类的初始化,查找类中包含@ExceptionHandler注解的方法,并把他们添加到mappedMethods map中
key为异常类型,value为具体method
图3

public ExceptionHandlerMethodResolver(Class<?> handlerType) {
   for (Method method : MethodIntrospector.selectMethods(handlerType, EXCEPTION_HANDLER_METHODS)) {
      for (Class<? extends Throwable> exceptionType : detectExceptionMappings(method)) {
         addExceptionMapping(exceptionType, method);
      }
   }
}

private void addExceptionMapping(Class<? extends Throwable> exceptionType, Method method) {
   Method oldMethod = this.mappedMethods.put(exceptionType, method);
   if (oldMethod != null && !oldMethod.equals(method)) {
      throw new IllegalStateException("Ambiguous @ExceptionHandler method mapped for [" +
            exceptionType + "]: {" + oldMethod + ", " + method + "}");
   }
}

以上步骤初始化就完成了。

异常包装处理

首先HandlerExceptionResolver类间接的实现了HandlerExceptionResolver类的resolveException接口,具体方法名为doResolveHandlerMethodException,这样当系统出现异常后,spring DispatcherServlet类的doDispatch方法会调用processDispatchResult方法,而此方法会调用resolveException接口:
DispatcherServle类 图4


@Nullable
protected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response,
      @Nullable Object handler, Exception ex) throws Exception {
    ...
   ModelAndView exMv = null;
   if (this.handlerExceptionResolvers != null) {
      for (HandlerExceptionResolver resolver : this.handlerExceptionResolvers) {
          //调用resolveException接口
         exMv = resolver.resolveException(request, response, handler, ex);
         if (exMv != null) {
            break;
         }
      }
   }
   if (exMv != null) {
    ....
      return exMv;
   }

   throw ex;
}


通过调用resolveException接口调用到了ExceptionHandlerExceptionResolver的doResolveHandlerMethodException接口,此接口进行异常的包装
图5


@Override
@Nullable
protected ModelAndView doResolveHandlerMethodException(HttpServletRequest request,
      HttpServletResponse response, @Nullable HandlerMethod handlerMethod, Exception exception) {
    //查找对应的异常处理方法 ,具体查看图6     
   ServletInvocableHandlerMethod exceptionHandlerMethod = getExceptionHandlerMethod(handlerMethod, exception);
   ...
   ServletWebRequest webRequest = new ServletWebRequest(request, response);
   ModelAndViewContainer mavContainer = new ModelAndViewContainer();

   try {
      Throwable cause = exception.getCause();
      if (cause != null) {
         // 调用方法执行,把转换后的结果存放在response对象中
         exceptionHandlerMethod.invokeAndHandle(webRequest, mavContainer, exception, cause, handlerMethod);
      }
      else {
         // 调用方法执行,把转换后的结果存放在response对象中
         exceptionHandlerMethod.invokeAndHandle(webRequest, mavContainer, exception, handlerMethod);
      }
   }
   catch (Throwable invocationEx) {
       ....
      return null;
   }

   if (mavContainer.isRequestHandled()) {
      return new ModelAndView();
   }
   else {
        ...
      return mav;
   }
}


遍历ControllerAdviceBean,假如我们项目中有2个@ControllerAdvice修饰的类,而且里面申明的异常都一样,那么就看放入exceptionHandlerAdviceCache顺序了,谁进入的早,那么谁的方法就返回,具体可查看图2源码

图6


@Nullable
protected ServletInvocableHandlerMethod getExceptionHandlerMethod(
      @Nullable HandlerMethod handlerMethod, Exception exception) {
   ...
   for (Map.Entry<ControllerAdviceBean, ExceptionHandlerMethodResolver> entry : this.exceptionHandlerAdviceCache.entrySet()) {
      ControllerAdviceBean advice = entry.getKey();
      if (advice.isApplicableToBeanType(handlerType)) {
         ExceptionHandlerMethodResolver resolver = entry.getValue();
         Method method = resolver.resolveMethod(exception);
         if (method != null) {
            return new ServletInvocableHandlerMethod(advice.resolveBean(), method);
         }
      }
   }

   return null;
}
<think>好的,我现在需要解决用户提到的关于@ControllerAdvice("com.example.springboot.controller")和org.example.springboot.common.Result包名冲突导致异常处理器未生效的问题。首先,我得理解用户的问题背景。 用户在使用Spring Boot时,可能自定义了一个全局异常处理类,并使用了@ControllerAdvice注解指定了基础包。同时,项目中导入了Result类,这个类的包名可能与控制器所在的包有重叠,导致Spring在扫描组件时出现问题,使得异常处理器没有被正确注册。 接下来,我需要分析可能的原因。首先,@ControllerAdvice的basePackages参数如果设置不正确,可能会限制异常处理器的生效范围。如果指定的包名"com.example.springboot.controller"下没有异常处理类,或者Result类所在的包org.example.springboot.common与这个基础包有冲突,可能会导致组件扫描时漏掉异常处理器。 另外,包名冲突的问题可能是指Result类所在的包路径和控制器所在的包路径有部分重叠,导致Spring在扫描时出现混淆。例如,如果@ControllerAdvice指定的包和Result类的包有共同的父包,但结构不同,可能会导致组件扫描未能正确识别异常处理类。 然后,我需要考虑解决方案。首先,检查@ControllerAdvice注解的包路径是否正确,确保它包含了异常处理类所在的包。如果异常处理类不在指定的包下,调整basePackages参数的值。其次,如果Result类所在的包与控制器包有冲突,可能需要重构包结构,将通用类如Result放在独立的包中,避免与业务代码的包重叠。此外,检查组件扫描的配置,确保Spring能够正确扫描到所有必要的包。 最后,可能需要验证异常处理器是否生效,可以通过触发一个异常来测试,或者检查Spring启动时的日志,查看是否有异常处理器的注册信息。如果还存在问题,可能需要检查是否有其他配置干扰,比如重复的@ComponentScan注解或者过滤器、拦截器的影响。 总结起来,解决步骤应该是:确认注解包路径正确性、调整包结构避免冲突、检查组件扫描配置、验证异常处理器注册情况。通过这些步骤逐步排查,应该能解决用户的问题。</think>关于@ControllerAdvice("com.example.springboot.controller")与Result类包名冲突导致异常处理器失效的问题,以下是详细分析及解决方案: 🔍 问题本质分析: 1. 包扫描范围冲突:`@ControllerAdvice("com.example.springboot.controller")`会将扫描范围限定在指定包内 2. 类路径混淆:`org.example.springboot.common.Result`的父包`org.example.springboot`与`com.example.springboot`存在首段包名差异 3. 组件扫描机制:Spring默认扫描主启动类所在包及其子包,跨包组件需要显式声明 ⚠️ 可能导致的后果: - 异常处理器未被Spring容器扫描到 - 全局异常处理类未被正确注册 - 自定义Result类可能被错误注入 🛠️ 解决方案分步说明: 1. **验证ControllerAdvice配置** ```java // 正确指定控制器所在包(注意实际包路径) @ControllerAdvice(basePackages = "com.example.springboot.controller") public class GlobalExceptionHandler { // 异常处理方法... } ``` 2. **检查包结构合理性** 推荐结构: ``` src/main/java ├── com.example.springboot // 主包 │ ├── controller // 控制器层 │ ├── service // 服务层 │ ├── common // 通用类(建议) │ │ └── Result.java // 统一响应体 └── org.example.springboot // 冲突包(建议重构) ``` 3. **调整Result类位置(推荐)** ```java // 将Result类迁移到项目主包下 package com.example.springboot.common; public class Result<T> { // 响应体实现... } ``` 4. **配置显式组件扫描(可选)** ```java @SpringBootApplication @ComponentScan({ "com.example.springboot.controller", "com.example.springboot.common" }) public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } } ``` 5. **验证异常处理器生效** ```java // 在控制器中制造测试异常 @RestController @RequestMapping("/test") public class TestController { @GetMapping("/error") public Result testError() { throw new RuntimeException("测试异常"); } } ``` 📝 排查清单: 1. 检查启动日志中是否出现全局异常处理器的注册信息 2. 确认`GlobalExceptionHandler`类是否被Spring管理(查看`/actuator/beans`端点) 3. 验证包名是否严格匹配(注意大小写敏感) 4. 检查是否存在多个`@ComponentScan`注解冲突 💡 替代方案: 如果Result类不可修改包路径,可尝试: ```java // 使用全限定名指定扫描包 @ControllerAdvice( basePackages = { "com.example.springboot.controller", "org.example.springboot.common" } ) ``` 建议优先保持项目包结构的统一性,将通用组件放在主包下的common子包中,可以避免此类包路径冲突问题。如果使用第三方Result类,建议通过maven/gradle依赖方式引入而不是直接复制源码。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值