@RestControllerAdvice
@RestControllerAdvice 是 Spring Framework 提供的一个组合注解,专门用于实现 RESTful API 的全局异常处理、数据绑定和数据预处理。它是 @ControllerAdvice 和 @ResponseBody 的组合,简化了 REST 异常处理的实现。
作用
- 全局异常处理:集中处理控制器抛出的异常
 
- 统一响应格式:确保所有错误响应格式一致
 
- 减少重复代码:避免在每个控制器中重复异常处理逻辑
 
- REST 专用:专为 RESTful API 设计,自动将返回值转为 JSON/XML
 
基本使用
1 2 3 4 5 6 7 8 9 10 11 12 13
   | @Data @AllArgsConstructor public class ErrorResponse {     private int status;          private String error;          private String message;          private String path;          private LocalDateTime timestamp = LocalDateTime.now(); }
   | 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
   | @RestControllerAdvice @Slf4j public class GlobalExceptionHandler {
           @ExceptionHandler(ResourceNotFoundException.class)     @ResponseStatus(HttpStatus.NOT_FOUND)     public ErrorResponse handleResourceNotFound(ResourceNotFoundException ex,          HttpServletRequest request) {         log.error("Resource not found: {}", ex.getMessage());         return new ErrorResponse(             HttpStatus.NOT_FOUND.value(),             "Not Found",             ex.getMessage(),             request.getRequestURI());     }
      @ExceptionHandler(MethodArgumentNotValidException.class)     @ResponseStatus(HttpStatus.BAD_REQUEST)     public ErrorResponse handleValidationErrors(MethodArgumentNotValidException ex,         HttpServletRequest request) {         List<String> errors = ex.getBindingResult()             .getFieldErrors()             .stream()             .map(FieldError::getDefaultMessage)             .collect(Collectors.toList());                      log.error("Validation errors: {}", errors);         return new ErrorResponse(             HttpStatus.BAD_REQUEST.value(),             "Validation Error",             errors.toString(),             request.getRequestURI());     }
           @ExceptionHandler(Exception.class)     @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)     public ErrorResponse handleAllExceptions(Exception ex,         HttpServletRequest request) {         log.error("Internal server error: {}", ex.getMessage(), ex);         return new ErrorResponse(             HttpStatus.INTERNAL_SERVER_ERROR.value(),             "Internal Server Error",             "An unexpected error occurred",             request.getRequestURI());     } }
   | 
 
其他
- 限定控制器的生效范围
 
1 2 3 4 5 6 7 8
   |  @RestControllerAdvice("com.example.controllers")
 
  @RestControllerAdvice(annotations = RestController.class)
 
  @RestControllerAdvice(assignableTypes = {UserController.class, ProductController.class})
 
  | 
 
- 处理数据绑定异常
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
   | @ExceptionHandler(MethodArgumentNotValidException.class) public ResponseEntity<ErrorResponse> handleValidationExceptions(MethodArgumentNotValidException ex) {     List<String> errors = ex.getBindingResult()         .getFieldErrors()         .stream()         .map(error -> error.getField() + ": " + error.getDefaultMessage())         .collect(Collectors.toList());              ErrorResponse error = new ErrorResponse(         HttpStatus.BAD_REQUEST.value(),         "Validation failed",         errors.toString());              return new ResponseEntity<>(error, HttpStatus.BAD_REQUEST); }
   | 
 
- 处理自定义业务异常
 
1 2 3 4 5 6 7 8 9
   | @ExceptionHandler(BusinessException.class) public ResponseEntity<ErrorResponse> handleBusinessException(BusinessException ex) {     ErrorResponse error = new ErrorResponse(         ex.getErrorCode(),         ex.getErrorType(),         ex.getMessage());              return new ResponseEntity<>(error, ex.getHttpStatus()); }
   | 
 
ResponseBodyAdvice
作用
ResponseBodyAdvice 是 spring mvc 提供的一个增强接口,用于在返回对象被 HttpMessageConverter 执行序列化之前对其进行一些自定义的操作。
1 2 3 4 5 6 7 8 9 10 11
   | public interface ResponseBodyAdvice <T> {
           boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType);
           @Nullable     T beforeBodyWrite(@Nullable T body, MethodParameter returnType, MediaType selectedContentType,             Class<? extends HttpMessageConverter<?>> selectedConverterType,             ServerHttpRequest request, ServerHttpResponse response); }
  | 
 
- supports:判断是否要执行 beforeBodyWrite 方法,true 为执行,false 不执行。通过该方法可以选择哪些类或哪些方法的 response 要进行处理,其他的不进行处理
 
- beforeBodyWrite:对 response 方法进行具体操作处理
 
基本使用
1 2 3 4 5 6 7 8 9 10
   | @Data @NoArgsConstructor @AllArgsConstructor public class BaseResponse <T> implements Serializable {     private int code;          private String msg;          private T data; }
   | 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
   | @Component @Slf4j @RestControllerAdvice(annotations = {RestController.class}) 
  public class ResponseAdvice implements ResponseBodyAdvice {
      
 
 
 
      @Override     public boolean supports(MethodParameter returnType, Class converterType) {         log.info(returnType.getMethod().getDeclaringClass().getName());         log.info(converterType.toString());         return true;                       }          
 
 
      @Override     public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {         log.info(selectedContentType.getType());                  ObjectMapper objectMapper = new ObjectMapper();                  if (body instanceof String) {             try {                 return objectMapper.writeValueAsString(new BaseResponse(0, "操作成功", body));             } catch (JsonProcessingException e) {                 e.printStackTrace();             }         }         if (body instanceof Page) {             HashMap map = new LinkedHashMap();             map.put("data", ((Page<?>) body).getRecords());             map.put("current", ((Page<?>) body).getCurrent());             map.put("size", ((Page<?>) body).getSize());             map.put("total", ((Page<?>) body).getTotal());             return new BaseResponse(0, "操作成功", map);         }         return new BaseResponse(0, "操作成功", body);     } }
   | 
 
存在问题
返回 String 类型的 ContentType 是 “text/plain”,对应的转换器是 StringHttpMessageConverter,由于我们将返回结果都封装成 BaseResponse 导致由其转换成 String 类型会引发类型错误
解决方法有两个:
- 设置接口的返回类型
 
1 2 3 4
   | @GetMapping(value = "/ok", produces = "application/json;charset=utf-8") public String ok() {     return "ok"; }
   | 
 
- 通过 SpringBoot 内置提供 Jackson 序列化 ObjectMapper 实现实体信息的序列化
 
1 2 3 4 5 6 7 8
   | ObjectMapper objectMapper = new ObjectMapper(); if (body instanceof String) {     try {         return objectMapper.writeValueAsString(new Response(0, "操作成功", body));     } catch (JsonProcessingException e) {         e.printStackTrace();     } }
   | 
 
转换器
使用枚举为请求参数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
   | @Getter public enum GenderEnum {     
 
      MALE(0),     
 
      FEMALE(1);       
 
      private Integer code;       GenderEnum(int code) {         this.code = code;     } }
   | 
 
1 2 3 4
   | @Data public class QueryRequest {     private GenderEnum gender; }
   | 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
   | @Slf4j @RestController @RequestMapping("/enum") public class EnumTestController {       @GetMapping("/get")     public Dict testGet(QueryRequest request) {         log.info("【get-request】= {}", JSONUtil.toJsonStr(request));         return Dict.create().set("get-request", request);     }       @PostMapping("/post")     public Dict testPost(@RequestBody QueryRequest request) {         log.info("【post-request】= {}", JSONUtil.toJsonStr(request));         return Dict.create().set("post-request", request);     } }
   | 
 
转换器
gender 只能接收到 MALE 、FEMALE 这样的参数,除此以外,均会报类型不匹配的错误信息,此时是无法处理 0 、1 这样的参数的
需求:
- 接收到 MALE 、FEMALE 这样的参数,可以自动转为对应的枚举值;
 
- 接收到 0 、1 这样的参数,也可以自动转为对应的枚举值
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
   | public class IntegerCodeToGenderEnumConverter implements Converter<Integer, GenderEnum> {     private Map<Integer, GenderEnum> enumMap = Maps.newHashMap();       public IntegerCodeToGenderEnumConverter() {         for (GenderEnum genderEnum : GenderEnum.values()) {             enumMap.put(genderEnum.getCode(), genderEnum);         }     }       @Override     public GenderEnum convert(Integer source) {         GenderEnum genderEnum = enumMap.get(source);         if (ObjectUtil.isNull(genderEnum)) {             throw new IllegalArgumentException("无法匹配对应的枚举类型");         }         return genderEnum;     } }
  | 
 
1 2 3 4 5 6 7 8 9 10 11
   | @Configuration public class WebMvcConfig implements WebMvcConfigurer {       
 
      @Override     public void addFormatters(FormatterRegistry registry) {         registry.addConverter(new IntegerCodeToGenderEnumConverter());     } }
   | 
 
转换器工厂
1 2 3 4 5 6 7 8
   | public interface BaseEnum {     
 
 
 
      Integer getCode(); }
  | 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
   | @Getter public enum GenderEnum implements BaseEnum {     
 
      MALE(0),     
 
      FEMALE(1);       
 
      private Integer code;       GenderEnum(int code) {         this.code = code;     } }
   | 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
   | public class IntegerToEnumConverter<T extends BaseEnum> implements Converter<Integer, T> {     private Map<Integer, T> enumMap = Maps.newHashMap();       public IntegerToEnumConverter(Class<T> enumType) {         T[] enums = enumType.getEnumConstants();         for (T e : enums) {             enumMap.put(e.getCode(), e);         }     }       @Override     public T convert(Integer source) {         T t = enumMap.get(source);         if (Objects.isNull(t)) {             throw new IllegalArgumentException("无法匹配对应的枚举类型");         }         return t;     } }
  | 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
   | public class IntegerCodeToEnumConverterFactory implements ConverterFactory<Integer, BaseEnum> {     private static final Map<Class, Converter> CONVERTERS = Maps.newHashMap();       
 
 
 
 
      @Override     public <T extends BaseEnum> Converter<Integer, T> getConverter(Class<T> targetType) {         Converter<Integer, T> converter = CONVERTERS.get(targetType);         if (converter == null) {             converter = new IntegerToEnumConverter<>(targetType);             CONVERTERS.put(targetType, converter);         }         return converter;     } }
  | 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
   | public class StringToEnumConverter<T extends BaseEnum> implements Converter<String, T> {     private Map<String, T> enumMap = Maps.newHashMap();       public StringToEnumConverter(Class<T> enumType) {         T[] enums = enumType.getEnumConstants();         for (T e : enums) {             enumMap.put(e.getCode().toString(), e);         }     }       @Override     public T convert(String source) {         T t = enumMap.get(source);         if (Objects.isNull(t)) {             throw new IllegalArgumentException("无法匹配对应的枚举类型");         }         return t;     } }
  | 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
   | public class StringCodeToEnumConverterFactory implements ConverterFactory<String, BaseEnum> {     private static final Map<Class, Converter> CONVERTERS = Maps.newHashMap();       
 
 
 
 
      @Override     public <T extends BaseEnum> Converter<String, T> getConverter(Class<T> targetType) {         Converter<String, T> converter = CONVERTERS.get(targetType);         if (converter == null) {             converter = new StringToEnumConverter<>(targetType);             CONVERTERS.put(targetType, converter);         }         return converter;     } }
  | 
 
1 2 3 4 5 6 7 8 9 10 11 12
   | @Configuration public class WebMvcConfig implements WebMvcConfigurer {       
 
      @Override     public void addFormatters(FormatterRegistry registry) {         registry.addConverterFactory(new IntegerCodeToEnumConverterFactory());         registry.addConverterFactory(new StringCodeToEnumConverterFactory());     } }
   |