RestControllerAdvice和ControllerAdvice使用详解
背景
在日常工作中,经常需要对系统抛出的各种异常进行一个处理,这个时候就可以使用@ControllerAdvice注解了,@RestControllerAdvice其实就是在@ControllerAdvice加了一个@ResponseBody注解,用来将返回值写入到响应体
基本使用方法
-
一般是定义一个异常处理类,在上面标注注解,就可以进行全局异常处理,异常处理类的注解如下:
@RestControllerAdvice public class ServerExceptionHandler { @ExceptionHandler(RuntimeException.class) public String handler() { return "server"; } }
-
我们定义一个简单的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也可以做到。
- 从下面的注解定义中我们可以看到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 — 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 — 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 {};
}
-
例子
// 只处理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。
-
原理分析
// 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注解调整优先级,如果没有指定会使用首字母顺序,所以在使用中尽量将通用的异常处理优先级降低一些, 笔者在测试的过程遇到过异常被其他的异常处理器提前处理了,导致自定义的特殊处理器没有作用。