在Spring框架中,@RestControllerAdvice
是一个特殊的注解,用于定义全局异常处理、数据绑定配置以及数据预处理。这个注解通常用于处理控制器(controller)层的全局问题,比如全局异常处理、全局数据绑定等。
@RestControllerAdvice
注解可以应用于一个类,这个类可以包含以下类型的处理方法:
- 异常处理方法:
使用@ExceptionHandler
注解来处理控制器抛出的异常。你可以指定一个或多个异常类型来处理。
- 数据绑定方法:
使用@InitBinder
注解来初始化WebDataBinder,这通常用于注册自定义属性编辑器(Property Editor)。
- 模型填充方法:
使用@ModelAttribute
注解来添加额外的模型属性,这些属性将被添加到模型中,对所有控制器方法都是可见的。
- 响应体包装器:
使用@ResponseBodyAdvice
接口来包装响应体,这可以用于修改返回给客户端的数据。
以下是一些使用@RestControllerAdvice
的示例:
全局异常处理:
@RestControllerAdvice public class GlobalExceptionHandler {
@ExceptionHandler(value = Exception.class) public ResponseEntity<String> handleAllExceptions(Exception ex, WebRequest request) { String bodyOfResponse = "发生了异常:" + ex.getMessage(); return new ResponseEntity<>(bodyOfResponse, new HttpHeaders(), HttpStatus.INTERNAL_SERVER_ERROR); } }
|
全局数据绑定:
@RestControllerAdvice public class GlobalBindingInitializer {
@InitBinder public void initBinder(WebDataBinder binder) { binder.registerCustomEditor(Date.class, new CustomDateEditor(new SimpleDateFormat("yyyy-MM-dd"), false)); } }
|
全局模型填充:
@RestControllerAdvice public class GlobalModelAttribute {
@ModelAttribute public void addAttributes(Model model) { model.addAttribute("attribute", "value"); } }
|
响应体包装器:
在Spring框架中,@ResponseBodyAdvice
接口允许你在响应体写入HTTP响应之前进行修改或增强。这个接口可以被用来实现多种功能,比如日志记录、修改响应数据、添加额外的头部信息等。
要使用@ResponseBodyAdvice
接口,你需要创建一个实现了该接口的类,并至少实现一个方法。Spring提供了几个方法的默认实现,你可以根据需要重写这些方法。
以下是@ResponseBodyAdvice
接口中几个关键的方法:
beforeBodyWrite
:
这个方法在响应体写入之前调用,允许你修改响应体或添加响应头。
supports
:
这个方法用于判断当前的ResponseBodyAdvice
是否支持给定的控制器的返回值。
beforeBodyWrite
方法的签名:
@Nullable default Object beforeBodyWrite(@Nullable Object body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) { return body; }
|
下面是一个使用@ResponseBodyAdvice
接口来包装响应体的示例:
import org.springframework.core.MethodParameter; import org.springframework.http.MediaType; import com.alibaba.fastjson.JSONObject; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.server.ServerHttpRequest; import org.springframework.http.server.ServerHttpResponse; import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
@RestControllerAdvice public class ControllerResponseAdvice implements ResponseBodyAdvice<Object> {
@Override public boolean supports(MethodParameter methodParameter, Class<? extends HttpMessageConverter<?>> converterType) { Type returnType = methodParameter.getGenericParameterType();
boolean isResponseEntityJSONObject = returnType instanceof ParameterizedType && ((ParameterizedType) returnType).getRawType() == ResponseEntity.class && ((ParameterizedType) returnType).getActualTypeArguments().length > 0 && ((ParameterizedType) returnType).getActualTypeArguments()[0] == JSONObject.class;
boolean isResponseModel = methodParameter.getParameterType().equals(ResponseModel.class);
boolean hasNotControllerResponseAdvice = methodParameter.hasMethodAnnotation(NotControllerResponseAdvice.class);
return !(isResponseEntityJSONObject || isResponseModel || hasNotControllerResponseAdvice); }
@Override public Object beforeBodyWrite(Object data, MethodParameter returnType, MediaType mediaType, Class<? extends HttpMessageConverter<?>> aClass, ServerHttpRequest request, ServerHttpResponse response) { if (returnType.getGenericParameterType().equals(String.class)) { ObjectMapper objectMapper = new ObjectMapper(); try { return objectMapper.writeValueAsString(new ResponseModel(data)); } catch (JsonProcessingException e) { throw new APIException(StatusCode.RESPONSE_PACK_ERROR, e.getMessage()); } } return new ResponseModel(data); } } import lombok.Data; @Data public class ResponseModel { private int code;
private String msg;
private Object data;
public ResponseModel(int code, String msg, Object data) { this.code = code; this.msg = msg; this.data = data; }
public ResponseModel(Object data) { this.code = HttpStatus.HTTP_OK; this.msg = "SUCCESS"; this.data = data; }
public ResponseModel(StatusCode statusCode, Object data) { this.code = statusCode.getCode(); this.msg = statusCode.getDesc(); this.data = data; }
public ResponseModel(StatusCode statusCode) { this.code = statusCode.getCode(); this.msg = statusCode.getDesc(); this.data = null; } }
|
在这个示例中,我们首先通过methodParameter.getGenericParameterType()获取方法的泛型返回类型。然后,我们检查这个返回类型是否是ResponseEntity类型。我们通过检查返回类型是否是ParameterizedType,并且其原始类型是否是ResponseEntity.class,以及其实际类型参数是否是JSONObject.class来进行判断。此外,我们还检查了返回类型是否是ResponseModel类型,以及方法是否有NotControllerResponseAdvice注解。如果满足这些条件之一,supports方法将返回false,表示不需要包装。请注意,这个示例假设你已经定义了ResponseModel类和NotControllerResponseAdvice注解。
在beforeBodyWrite
方法中,我们检查了响应体是否是一个String
类型,如果不是,则将数据包装在ResponseModel里后转换为json串进行返回。
这样,满足要求的响应体都会被这个Advice包装,返回一个包含status
和data
的新对象。这在实际开发中非常有用,特别是当你想要统一API响应格式时。
请注意,@ControllerAdvice
注解等同于@RestControllerAdvice
,但@RestControllerAdvice
更明确地表示它适用于@RestController
注解的类。两者在功能上是相同的。
选择性地应用
@RestControllerAdvice
注解可以有选择性地应用于特定的包或组件。例如,你可以使用basePackages
属性来指定哪些包下的控制器应该被这个@RestControllerAdvice
类所影响。
@RestControllerAdvice(basePackages = {"com.example.web"}) public class WebExceptionHandler { }
|
这样,只有com.example.web
包下的控制器会使用这个异常处理器。如果你不指定basePackages
,那么默认情况下,Spring会查找所有包下的控制器。