SpringMVC框架中@ControllerAdvice和@ExceptionHandler注解使用原理

目录

一、简介

二、@ControllerAdvice注解的处理获取

1.@ControllerAdvice注解

2.ControllerAdviceBean类

三、搭配@ExceptionHandler注解统一处理异常

1.@ExceptionHandler注解

2.ExceptionHandlerExceptionResolver异常解析类

3.ExceptionHandlerMethodResolver解析@ExceptionHandler注解

4.系统抛出异常调用链

4.1 DispatcherServlet入口

4.2 HandlerExceptionResolver异常解析类

4.3 ExceptionHandlerExceptionResolver获取调用异常处理方法


一、简介

@ControllerAdvice注解从名字上就可以看出来这个是针对Controller的切面增强处理注解,类似与@Controller和@RestController一样,@ControllerAdvice也有对应的@RestControllerAdvice注解用来返回序列化之后的对象。

@ControllerAdvice实际上也是一个Component,因为该注解同时也被@Component注解,搭配这个注解通常有一个注解和两个接口:

  • @ExceptionHandler注解:统一处理从Controller抛出的异常,在@ControllerAdvice中可以拥有多个@ExceptionHandler和实现方法,SpringMVC将会自动判断哪个异常优先处理;
  • RequestBodyAdvice接口:用来对Request请求方法体和对应方法参数进行处理,如果方法参数有多个,将会顺序调用到接口方法中来;
  • ResponseBodyAdvice接口:类似RequestBodyAdvice,只是处理的对象变成了Response响应,处理的对象包括请求体和返回类型。

对于@ExceptionHandler的使用,可以参照(一)Java使用奇技淫巧之枚举+自定义异常+断言减少if-throw异常抛出文章,而其它两个接口则可以对请求体和返回体进行统一的处理,如整个项目需要对请求和返回进行加解密,则可以使用这两个接口对请求体和响应进行切面处理。

另外需要注意的是,@ControllerAdvice注解是一定需要搭配其它的注解或者接口使用的,否则就算这个注解类被识别到,没有具体的处理方法,SpringMVC是不会进行任何操作的。

二、@ControllerAdvice注解的处理获取

1.@ControllerAdvice注解

在分析具体实现原理前先看到其源码:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface ControllerAdvice {
   // 和下面的basePackages代表的是同一个意思,只针对某些包下的类
   @AliasFor("basePackages")
   String[] value() default {};
   @AliasFor("value")
   String[] basePackages() default {};
   // 只处理某些类所在包路径
   Class<?>[] basePackageClasses() default {};
   // 处理某些类和这些类的子类
   Class<?>[] assignableTypes() default {};
   // 处理被某些注解过的类
   Class<? extends Annotation>[] annotations() default {};
}

2.ControllerAdviceBean类

这个类便是@ControllerAdvice注解对应的实体类,在这个类里面将会完成从bean工厂中获取所有被@ControllerAdvice注解过的bean,并读取注解属性将其全部返回的操作。部分关键源码如下:

public class ControllerAdviceBean implements Ordered {
   // 这个bean指的就是被@ControllerAdvice注解的类对象
   private final Object bean;
   // bean工厂,要从bean工厂中获取bean和判断bean的类型
   @Nullable
   private final BeanFactory beanFactory;
   // 这个类有兴趣的可以去看下,代码不多,大致作用便是存放并且判断注解的属性
   private final HandlerTypePredicate beanTypePredicate;
   private ControllerAdviceBean(Object bean, 
           @Nullable BeanFactory beanFactory) {
      this.bean = bean;
      this.beanFactory = beanFactory;
      Class<?> beanType;
      // 设置bean和beanType等属性
      if (bean instanceof String) {
         String beanName = (String) bean;
         beanType = this.beanFactory.getType(beanName);
         this.order = initOrderFromBeanType(beanType);
      } else {
         beanType = bean.getClass();
         this.order = initOrderFromBean(bean);
      }
      // 从bean类型获取ControllerAdvice注解
      ControllerAdvice annotation = (beanType != null ?
              AnnotatedElementUtils.findMergedAnnotation(beanType, 
                  ControllerAdvice.class) : null);
      // 如果注解不为空则将注解的属性通过HandlerTypePredicate的构造器放入到
      // HandlerTypePredicate对象中,以便后续判断注解属性
      if (annotation != null) {
         this.beanTypePredicate = HandlerTypePredicate.builder()
               .basePackage(annotation.basePackages())
               .basePackageClass(annotation.basePackageClasses())
               .assignableType(annotation.assignableTypes())
               .annotation(annotation.annotations())
               .build();
      } else {
         // 创建一个默认的HandlerTypePredicate对象
         this.beanTypePredicate = HandlerTypePredicate.forAnyHandlerType();
      }
   }
   public boolean isApplicableToBeanType(@Nullable Class<?> beanType) {
      // 这个便是外部调用判断注解属性是否满足要求的方法
      return this.beanTypePredicate.test(beanType);
   }
   public static List<ControllerAdviceBean> findAnnotatedBeans(
           ApplicationContext context) {
      // 这个方法大致作用便是从bean工厂中获取所有被@ControllerAdvice注解过的
      // bean对象,因为ControllerAdvice中也有@Component注解,当获得bean之后
      // 再把这个bean使用ControllerAdviceBean的构造函数实例化
      return Arrays.stream(BeanFactoryUtils
              .beanNamesForTypeIncludingAncestors(context, Object.class))
              .filter(name -> context.findAnnotationOnBean(name, 
                      ControllerAdvice.class) != null)
              .map(name -> new ControllerAdviceBean(name, context))
              .collect(Collectors.toList());
   }
}

三、搭配@ExceptionHandler注解统一处理异常

其大致流程图为:

1.@ExceptionHandler注解

先看下其注解源码:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ExceptionHandler {
   Class<? extends Throwable>[] value() default {};
}

可以看到这里面只有一个value属性,可以支持同一个注解处理多个异常类。

2.ExceptionHandlerExceptionResolver异常解析类

在DispatcherServlet中,当调用Controller中的方法抛出异常时,则会调用这个类来判断异常的类型,进而选择@ExceptionHandler注解中对应异常的处理方法。其部分关键源码如下:

public class ExceptionHandlerExceptionResolver 
        extends AbstractHandlerMethodExceptionResolver
        implements ApplicationContextAware, InitializingBean {
    @Override
    public void afterPropertiesSet() {
       // 这个方法便是调用ControllerAdviceBean类的findAnnotatedBeans方法
       // 来获取bean工厂中所有被@ControllerAdvice注解过的bean对象
       initExceptionHandlerAdviceCache();
       if (this.argumentResolvers == null) {
          // 如果参数解析器为空则获取默认的
          List<HandlerMethodArgumentResolver> resolvers = 
                  getDefaultArgumentResolvers();
          this.argumentResolvers = 
                  new HandlerMethodArgumentResolverComposite()
                  .addResolvers(resolvers);
       }
       if (this.returnValueHandlers == null) {
          // 如果处理返回值的处理器为空则获取默认的,经常使用的
          // RequestResponseBodyMethodProcessor便是在这里面添加的
          List<HandlerMethodReturnValueHandler> handlers = 
                  getDefaultReturnValueHandlers();
          this.returnValueHandlers = 
                  new HandlerMethodReturnValueHandlerComposite()
                  .addHandlers(handlers);
       }
    }
    private void initExceptionHandlerAdviceCache() {
       // 如果spring上下文为空则无需进行下面的操作,因为都是基于bean工厂操作的
       if (getApplicationContext() == null) {
          return;
       }
       // 调用ControllerAdviceBean的方法,获取bean工厂中所有@ControllerAdvice
       // 注解bean对象
       List<ControllerAdviceBean> adviceBeans = ControllerAdviceBean
               .findAnnotatedBeans(getApplicationContext());
       // 进行排序,排序的依据便是根据@Order等排序类来完成
       AnnotationAwareOrderComparator.sort(adviceBeans);
       // 对获取到的切面对象进行遍历
       for (ControllerAdviceBean adviceBean : adviceBeans) {
          Class<?> beanType = adviceBean.getBeanType();
          if (beanType == null) {
             throw new IllegalStateException();
          }
          // ExceptionHandlerMethodResolver 类的构造函数将会判断这个bean中的
          // 方法是否含有@ExceptionHandler注解,如果有则添加到mappedMethods
          // 集合中
          ExceptionHandlerMethodResolver resolver = 
                  new ExceptionHandlerMethodResolver(beanType);
          // 判断前面解析后得到的mappedMethods集合是否为空
          if (resolver.hasExceptionMappings()) {
             // 如果不为空则将异常处理方法添加到exceptionHandlerAdviceCache
             // 缓存中
             this.exceptionHandlerAdviceCache.put(adviceBean, resolver);
          }
          // 如果beanType实现了ResponseBodyAdvice接口则把切面对象添加到
          // responseBodyAdvice集合中,这里判断ResponseBodyAdvice接口是因为
          // 我们可以把ExceptionHandler看成一个特殊的只处理异常的控制器,所有
          // 抛出的异常都会在这里捕捉,并且处理然后像普通的控制器方法一样返回
          // 因此这里可以使用ResponseBodyAdvice切面来处理返回数据体,但是
          // 对这个这个接口实现本篇暂不介绍,下篇再分析
          if (ResponseBodyAdvice.class.isAssignableFrom(beanType)) {
             this.responseBodyAdvice.add(adviceBean);
          }
       }
    }
}

3.ExceptionHandlerMethodResolver解析@ExceptionHandler注解

前面我们说了ExceptionHandlerMethodResolver将会解析被@ExceptionHandler注解的方法,因此这里稍微分析一下其组成和解析方式,部分关键源码如下:

public class ExceptionHandlerMethodResolver {
    // 用来解析方法注解@ExceptionHandler使用lambda实现的的MethodFilter对象
    public static final MethodFilter EXCEPTION_HANDLER_METHODS = method ->
      AnnotatedElementUtils.hasAnnotation(method, ExceptionHandler.class);
    // 从传入的方法或者类解析获取到的被@ExceptionHandler注解的方法
    private final Map<Class<? extends Throwable>, Method> mappedMethods = 
            new HashMap<>(16);
    // 这个方法将会缓存具体的异常和对应的方法处理对象,以防止每次都要判断
    private final Map<Class<? extends Throwable>, Method> 
            exceptionLookupCache = new ConcurrentReferenceHashMap<>(16);
    public ExceptionHandlerMethodResolver(Class<?> handlerType) {
       // 在ExceptionHandlerExceptionResolver类中解析bean类型时调用的方法
       // 对传入的class对象方法使用MethodFilter进行遍历获取注解方法
       for (Method method : 
               MethodIntrospector.selectMethods(handlerType, 
                       EXCEPTION_HANDLER_METHODS)) {
          // 遍历判断处理的异常是否在@ExceptionHandler注解中
          for (Class<? extends Throwable> exceptionType : 
                  detectExceptionMappings(method)) {
             // 将获取到的处理异常方法添加到mappedMethods集合中
             addExceptionMapping(exceptionType, method);
          }
       }
    }
    @Nullable
    public Method resolveMethod(Exception exception) {
       // 这个方法将在调用控制器方法抛出异常时使用系统抛出的异常类型
       // 获取处理这种异常的方法
       return resolveMethodByThrowable(exception);
    }
    @Nullable
    public Method resolveMethodByThrowable(Throwable exception) {
       // 先调用一次方法来使用exception异常对象获取一次
       Method method = resolveMethodByExceptionType(exception.getClass());
       if (method == null) {
          // 如果使用传入进来的异常对象exception获取的方法为空,则使用cause
          // 对象再判断一次
          Throwable cause = exception.getCause();
          if (cause != null) {
             // 再调用判断一次,如果为空则说明这种异常不需要处理
             method = resolveMethodByExceptionType(cause.getClass());
          }
       }
       return method;
    }
    @Nullable
    public Method resolveMethodByExceptionType(
            Class<? extends Throwable> exceptionType) {
       // 先根据异常的类型从缓存中获取对应的处理方法
       Method method = this.exceptionLookupCache.get(exceptionType);
       if (method == null) {
          // 如果没有缓存则调用方法具体获取一遍再放入缓存中
          method = getMappedMethod(exceptionType);
          this.exceptionLookupCache.put(exceptionType, method);
       }
       return method;
    }
    @Nullable
    private Method getMappedMethod(
            Class<? extends Throwable> exceptionType) {
       // 等下用来存储已经匹配到的异常类型
       List<Class<? extends Throwable>> matches = new ArrayList<>();
       // 从mappedMethods集合中获取从@ExceptionHandler注解解析出来的异常类型
       for (Class<? extends Throwable> mappedException : 
               this.mappedMethods.keySet()) {
          // 如果@ExceptionHandler注解的异常类型是系统抛出的异常类型是父类接口
          // 则将@ExceptionHandler注解的异常类型添加到matches集合中
          if (mappedException.isAssignableFrom(exceptionType)) {
             matches.add(mappedException);
          }
       }
       // 如果不为空
       if (!matches.isEmpty()) {
          // ExceptionDepthComparator实际上是根据异常的深度来排序的,如果
          // 匹配到的异常深度越高则排名越靠前,排序后的第一位则一定是离系统
          // 抛出异常最近的异常类型,直接取第一位即可
          matches.sort(new ExceptionDepthComparator(exceptionType));
          return this.mappedMethods.get(matches.get(0));
       } else {
          return null;
       }
    }
}

4.系统抛出异常调用链

在我们使用Controller时,系统抛出异常是再常见不过的事情了,而SpringMVC如何捕获到系统的异常再使用@ControllerAdvice注解的异常切面增强器来对异常进行处理的呢?在了解SpringMVC调用链时需要有DispatcherServlet调用链的基础,可以跳转到(四)SpringMVC原理解析之运行流程源码分析文章了解。接下来看下其部分关键源码。

4.1 DispatcherServlet入口

部分源码如下:

public class DispatcherServlet extends FrameworkServlet {
    private void processDispatchResult(HttpServletRequest request, 
            HttpServletResponse response, 
            @Nullable HandlerExecutionChain mappedHandler, 
            @Nullable ModelAndView mv,
            @Nullable Exception exception) throws Exception {
       // 由于在关于SpringMVC源码分析已经大致分析过处理异常的地方
       // 因此这里直接从开始解析异常处理的地方分析
       boolean errorView = false;
       // 如果异常不为空,则说明在Controller或者其它控制器中抛出了异常
       if (exception != null) {
          // 如果异常类型是ModelAndView异常,则直接获取ModelAndView对象即可
          if (exception instanceof ModelAndViewDefiningException) {
             mv = ((ModelAndViewDefiningException) exception)
                     .getModelAndView();
          } else {
             // 其它类型的异常则需要获取handler处理器进行相应的处理
             Object handler = (mappedHandler != null ? 
                     mappedHandler.getHandler() : null);
             // 处理异常
             mv = processHandlerException(request, response, handler, 
                     exception);
             errorView = (mv != null);
          }
       }
       // 对获取到的mv判断是否需要渲染视图,略过
       ...
    }
    @Nullable
    protected ModelAndView processHandlerException(HttpServletRequest 
            request, HttpServletResponse response,
            @Nullable Object handler, Exception ex) throws Exception {
       // 成功和错误响应可能使用不同的内容类型
       request.removeAttribute(
               HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);
       ModelAndView exMv = null;
       // 如果处理异常的集合对象不为空
       if (this.handlerExceptionResolvers != null) {
          // 遍历handlerExceptionResolvers异常处理集合
          for (HandlerExceptionResolver resolver : 
                  this.handlerExceptionResolvers) {
             // 调用解析异常方法,如果返回不为空则退出,resolveException将是
             // 具体解析异常的地方
             exMv = resolver.resolveException(request, response, 
                     handler, ex);
             if (exMv != null) {
                break;
             }
          }
       }
       // 对获取到的异常ModelAndView进行判断处理,略过
       ...
    }
}

4.2 HandlerExceptionResolver异常解析类

部分源码如下:

public abstract class AbstractHandlerExceptionResolver 
        implements HandlerExceptionResolver, Ordered {
    @Override
    @Nullable
    public ModelAndView resolveException(
          HttpServletRequest request, HttpServletResponse response, 
          @Nullable Object handler, Exception ex) {
       // 判断这个类型的handler处理器是否支持
       if (shouldApplyTo(request, handler)) {
          prepareResponse(ex, response);
          // 调用解析异常方法
          ModelAndView result = doResolveException(request, response, 
                  handler, ex);
          return result;
       }
       else {
          return null;
       }
    }
}
public abstract class AbstractHandlerMethodExceptionResolver 
        extends AbstractHandlerExceptionResolver {
    @Override
    @Nullable
    protected final ModelAndView doResolveException(
          HttpServletRequest request, HttpServletResponse response, 
          @Nullable Object handler, Exception ex) {
       return doResolveHandlerMethodException(request, response, 
               (HandlerMethod) handler, ex);
    }
}

4.3 ExceptionHandlerExceptionResolver获取调用异常处理方法

这个类继承了AbstractHandlerMethodExceptionResolver类,部分源码如下:

public class ExceptionHandlerExceptionResolver 
        extends AbstractHandlerMethodExceptionResolver
        implements ApplicationContextAware, InitializingBean {
    @Override
    @Nullable
    protected ModelAndView doResolveHandlerMethodException(
            HttpServletRequest request, HttpServletResponse response,
            @Nullable HandlerMethod handlerMethod, Exception exception) {
       // 调用getExceptionHandlerMethod方法获取具体处理方法对象,这里是关键
       ServletInvocableHandlerMethod exceptionHandlerMethod = 
               getExceptionHandlerMethod(handlerMethod, exception);
       if (exceptionHandlerMethod == null) {
          return null;
       }
       // 中间可略过,无非是设置argumentResolvers和returnValueHandlers对象
       try {
          Throwable cause = exception.getCause();
          // 根据cause异常是否为空来判断具体调用哪个方法,调用进
          // ServletInvocableHandlerMethod对象的invokeAndHandle方法就和
          // 正常调用controller方法一样了
          if (cause != null) {
             exceptionHandlerMethod.invokeAndHandle(webRequest, 
                     mavContainer, exception, cause, handlerMethod);
          }
          else {
             exceptionHandlerMethod.invokeAndHandle(webRequest, 
                     mavContainer, exception, handlerMethod);
          }
       }
       // 后面可略过
       ...
    }
    @Nullable
    protected ServletInvocableHandlerMethod getExceptionHandlerMethod(
          @Nullable HandlerMethod handlerMethod, Exception exception) {
       Class<?> handlerType = null;
       // 如果handlerMethod不为空则先根据handlerMethod对象来获取
       if (handlerMethod != null) {
          handlerType = handlerMethod.getBeanType();
          // 从缓存中先获取
          ExceptionHandlerMethodResolver resolver = 
                  this.exceptionHandlerCache.get(handlerType);
          // 如果为空在实例化一个ExceptionHandlerMethodResolver对象
          if (resolver == null) {
             resolver = new ExceptionHandlerMethodResolver(handlerType);
             this.exceptionHandlerCache.put(handlerType, resolver);
          }
          // 调用前面说过的resolveMethod方法来获取具体解析异常的方法
          Method method = resolver.resolveMethod(exception);
          if (method != null) {
             // 不为空则返回封装了方法的ServletInvocableHandlerMethod对象
             return new ServletInvocableHandlerMethod(
                     handlerMethod.getBean(), method);
          }
          // 如果这里判断失败了则会使用后面的Advice来判断,因此需要先使其支持
          // advice切面代理
          if (Proxy.isProxyClass(handlerType)) {
             handlerType=AopUtils.getTargetClass(handlerMethod.getBean());
          }
       }
       // 循环遍历获取到的exceptionHandlerAdviceCache异常解析切面对象
       for (Map.Entry<ControllerAdviceBean, ExceptionHandlerMethodResolver>
               entry : this.exceptionHandlerAdviceCache.entrySet()) {
          ControllerAdviceBean advice = entry.getKey();
          // 调用前面说过的isApplicableToBeanType方法判断是否满足要求
          if (advice.isApplicableToBeanType(handlerType)) {
             ExceptionHandlerMethodResolver resolver = entry.getValue();
             // 使用resolveMethod方法解析出具体处理异常的方法对象
             Method method = resolver.resolveMethod(exception);
             if (method != null) {
                // 封装成ServletInvocableHandlerMethod对象返回
                return new ServletInvocableHandlerMethod(
                        advice.resolveBean(), method);
             }
          }
       }
       return null;
    }
}

当在ExceptionHandlerExceptionResolver类中获取了具体的HandlerMethod并且调用了ServletInvocableHandlerMethod类的invokeAndHandle方法执行完后就标志着@ExceptionHandler注解的方法已经被执行过,系统的异常处理已被捕获,随后将会把方法返回的数据返回给前端。

 

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
`@RequestMapping`是Spring MVC框架的一个重要注解,用于将HTTP请求映射到控制器的方法上。这个注解用于简化RESTful风格的Web服务开发,使得路由和处理逻辑更加清晰和模块化。下面是一些关于`@RequestMapping`实验小结的关键点: 1. **路径映射**:`@RequestMapping`用于指定一个或多个HTTP请求方法(GET, POST, PUT, DELETE等),以及对应的URL路径。例如,`@RequestMapping(value = "/users", method = RequestMethod.GET)`表示该方法处理所有发送到"/users"路径的GET请求。 2. **请求参数**:可以通过`@RequestParam`或`@PathVariable`注解处理请求参数,如查询参数、路径变量等。如`@RequestParam(name="id") Long userId`会从请求获取名为"id"的查询参数。 3. **返回类型**:`@ResponseBody`可以用来标记返回值为JSON或XML响应体,而`@ModelAttribute`用于处理表单提交的模型属性。 4. **异常处理**:可以使用`@ExceptionHandler`注解来捕获特定的异常,并提供定制的错误处理。 5. **分组和扫描器**:为了方便管理,可以通过`@RequestMapping`的`@ControllerAdvice`或`@Controller`的`@RequestMapping(basePath = "/api/v1")`来对多个控制器进行分组或定义统一的路径前缀。 相关问题-- 1. `@RequestMapping`如何处理不同类型的HTTP请求? 2. 如何在Spring MVC使用`@RequestParam`和`@PathVariable`? 3. `@ResponseBody`和`@ModelAttribute`的区别是什么? 4. 如何在Spring MVC实现全局异常处理? 5. 分组和扫描器在Spring MVC的应用场景是什么?
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值