RestControllerAdvice和ControllerAdvice使用详解

RestControllerAdvice和ControllerAdvice使用详解

背景

​ 在日常工作中,经常需要对系统抛出的各种异常进行一个处理,这个时候就可以使用@ControllerAdvice注解了,@RestControllerAdvice其实就是在@ControllerAdvice加了一个@ResponseBody注解,用来将返回值写入到响应体

基本使用方法
  1. 一般是定义一个异常处理类,在上面标注注解,就可以进行全局异常处理,异常处理类的注解如下:

    @RestControllerAdvice
    public class ServerExceptionHandler {
    
        @ExceptionHandler(RuntimeException.class)
        public String handler() {
            return "server";
        }
    
    }
    
  2. 我们定义一个简单的conroller来做下测试

    @RestController
    @ServerExceptionHandlerGroup
    @RequestMapping("/exception")
    public class ServerExceptionDemoController {
    
        @Autowired
        ExceptionDemoService exceptionDemoService;
    
        @RequestMapping("/server")
        public String server() {
            exceptionDemoService.handler();
            return "ok";
        }
    
    }
    
    @Service
    public class ExceptionDemoService {
    
        // 抛出一个异常
        public void handler(){
            throw new RuntimeException();
        }
    
    }
    

    请求相应的接口/exception/server,响应的结果为: server

进阶使用方法

​ 在系统的开发中,自己系统返回的响应值一般都是一样的json格式,单有时候会对接第三服务,他们自定义的json和系统自定义的json格式不一致,这个时候需要对不同接口调用发生异常之后处理成不同的json响应格式。这种场景下需要怎么处理呢?其实使用RestControllerAdvice也可以做到。

  1. 从下面的注解定义中我们可以看到basePackages,assignableTypes,annotations等相关的注解,如果指定的对应的值,他们会处理相应指定条件下的Controller接口调用的异常
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@ControllerAdvice
@ResponseBody
public @interface RestControllerAdvice {

   /**
    * Alias for the {@link #basePackages} attribute.
    * <p>Allows for more concise annotation declarations &mdash; for example,
    * {@code @RestControllerAdvice("org.my.pkg")} is equivalent to
    * {@code @RestControllerAdvice(basePackages = "org.my.pkg")}.
    * @see #basePackages
    */
   @AliasFor(annotation = ControllerAdvice.class)
   String[] value() default {};

   /**
    * Array of base packages.
    * <p>Controllers that belong to those base packages or sub-packages thereof
    * will be included &mdash; for example,
    * {@code @RestControllerAdvice(basePackages = "org.my.pkg")} or
    * {@code @RestControllerAdvice(basePackages = {"org.my.pkg", "org.my.other.pkg"})}.
    * <p>{@link #value} is an alias for this attribute, simply allowing for
    * more concise use of the annotation.
    * <p>Also consider using {@link #basePackageClasses} as a type-safe
    * alternative to String-based package names.
    */
   @AliasFor(annotation = ControllerAdvice.class)
   String[] basePackages() default {};

   /**
    * Type-safe alternative to {@link #basePackages} for specifying the packages
    * in which to select controllers to be advised by the {@code @RestControllerAdvice}
    * annotated class.
    * <p>Consider creating a special no-op marker class or interface in each package
    * that serves no purpose other than being referenced by this attribute.
    */
   @AliasFor(annotation = ControllerAdvice.class)
   Class<?>[] basePackageClasses() default {};

   /**
    * Array of classes.
    * <p>Controllers that are assignable to at least one of the given types
    * will be advised by the {@code @RestControllerAdvice} annotated class.
    */
   @AliasFor(annotation = ControllerAdvice.class)
   Class<?>[] assignableTypes() default {};

   /**
    * Array of annotations.
    * <p>Controllers that are annotated with at least one of the supplied annotation
    * types will be advised by the {@code @RestControllerAdvice} annotated class.
    * <p>Consider creating a custom composed annotation or use a predefined one,
    * like {@link RestController @RestController}.
    */
   @AliasFor(annotation = ControllerAdvice.class)
   Class<? extends Annotation>[] annotations() default {};

}
  1. 例子

    // 只处理ServerExceptionHandlerGroup标记的controller
    @RestControllerAdvice(annotations = {ServerExceptionHandlerGroup.class})
    public class ServerExceptionHandler {
    
        @ExceptionHandler(RuntimeException.class)
        public String handler() {
            return "server";
        }
    
    }
    
    // 只处理ClientExceptionHandlerGroup标记的controller
    @RestControllerAdvice(annotations = {ClientExceptionHandlerGroup.class})
    public class ClientExceptionHandler {
    
        @ExceptionHandler(RuntimeException.class)
        public String handler() {
            return "client";
        }
    
    }
    
    @Target({ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface ServerExceptionHandlerGroup {
    
    }
    
    @Target({ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface ClientExceptionHandlerGroup {
    
    }
    
    @RestController
    @ClientExceptionHandlerGroup
    @RequestMapping("/exception")
    public class ClientExceptionDemoController {
    
        @Autowired
        ExceptionDemoService exceptionDemoService;
    
        @RequestMapping("/client")
        public String client() {
            exceptionDemoService.handler();
            return "ok";
        }
    }
    
    @RestController
    @ServerExceptionHandlerGroup
    @RequestMapping("/exception")
    public class ServerExceptionDemoController {
    
        @Autowired
        ExceptionDemoService exceptionDemoService;
    
        @RequestMapping("/server")
        public String server() {
            exceptionDemoService.handler();
            return "ok";
        }
    }
    

    调用/exception/server返回server,调用/exception/client。这里可能有一个疑问,controller调用service抛出的错误会进行区别处理吗?从例子中可以看出是可以的,在例子中抛出异常的类都是exceptionDemoService。

  2. 原理分析

    // org.springframework.web.method.HandlerTypePredicate#test
    // 是否可以处理这种handler抛出的异常
    @Override
    public boolean test(@Nullable Class<?> controllerType) {
       if (!hasSelectors()) { // 没有标记对应的值true
          return true;
       }
       else if (controllerType != null) {
          for (String basePackage : this.basePackages) {
             if (controllerType.getName().startsWith(basePackage)) {
                return true;
             }
          }
          for (Class<?> clazz : this.assignableTypes) {
             if (ClassUtils.isAssignable(clazz, controllerType)) {
                return true;
             }
          }
          for (Class<? extends Annotation> annotationClass : this.annotations) {
             if (AnnotationUtils.findAnnotation(controllerType, annotationClass) != null) {
                return true;
             }
          }
       }
       return false;
    }
    

    从代码中可以看出来,这些条件是或的关系,满足一个就可以了,还有一点需要注意的就是,系统中的异常处理handler是有优先级的概念,可以使用@order注解调整优先级,如果没有指定会使用首字母顺序,所以在使用中尽量将通用的异常处理优先级降低一些, 笔者在测试的过程遇到过异常被其他的异常处理器提前处理了,导致自定义的特殊处理器没有作用。

  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值