全局性处理注解@RestControllerAdvice

在Spring框架中,@RestControllerAdvice是一个特殊的注解,用于定义全局异常处理、数据绑定配置以及数据预处理。这个注解通常用于处理控制器(controller)层的全局问题,比如全局异常处理、全局数据绑定等。

@RestControllerAdvice注解可以应用于一个类,这个类可以包含以下类型的处理方法:

  1. 异常处理方法
    使用@ExceptionHandler注解来处理控制器抛出的异常。你可以指定一个或多个异常类型来处理。
  2. 数据绑定方法
    使用@InitBinder注解来初始化WebDataBinder,这通常用于注册自定义属性编辑器(Property Editor)。
  3. 模型填充方法
    使用@ModelAttribute注解来添加额外的模型属性,这些属性将被添加到模型中,对所有控制器方法都是可见的。
  4. 响应体包装器
    使用@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接口中几个关键的方法:

  1. beforeBodyWrite
    这个方法在响应体写入之前调用,允许你修改响应体或添加响应头。
  2. supports
    这个方法用于判断当前的ResponseBodyAdvice是否支持给定的控制器的返回值。
  3. 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();

// 检查是否是ResponseEntity<JSONObject>类型
boolean isResponseEntityJSONObject = returnType instanceof ParameterizedType &&
((ParameterizedType) returnType).getRawType() == ResponseEntity.class &&
((ParameterizedType) returnType).getActualTypeArguments().length > 0 &&
((ParameterizedType) returnType).getActualTypeArguments()[0] == JSONObject.class;

// 检查是否是ResponseModel类型
boolean isResponseModel = methodParameter.getParameterType().equals(ResponseModel.class);

// 检查方法是否有NotControllerResponseAdvice注解
boolean hasNotControllerResponseAdvice = methodParameter.hasMethodAnnotation(NotControllerResponseAdvice.class);

// 如果是ResponseEntity<JSONObject>类型,或者ResponseModel类型,或者方法有NotControllerResponseAdvice注解,则不进行包装
return !(isResponseEntityJSONObject || isResponseModel || hasNotControllerResponseAdvice);
}

@Override
public Object beforeBodyWrite(Object data, MethodParameter returnType, MediaType mediaType, Class<? extends HttpMessageConverter<?>> aClass, ServerHttpRequest request, ServerHttpResponse response) {
// String类型不能直接包装
if (returnType.getGenericParameterType().equals(String.class)) {
ObjectMapper objectMapper = new ObjectMapper();
try {
// 将数据包装在ResponseModel里后转换为json串进行返回
return objectMapper.writeValueAsString(new ResponseModel(data));
} catch (JsonProcessingException e) {
throw new APIException(StatusCode.RESPONSE_PACK_ERROR, e.getMessage());
}
}
// 否则直接包装成ResponseModel返回
return new ResponseModel(data);
}
}
import lombok.Data;
@Data
public class ResponseModel {
// 状态码
private int code;

// 状态信息
private String msg;

// 返回对象
private Object data;

// 手动设置返回vo
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包装,返回一个包含statusdata的新对象。这在实际开发中非常有用,特别是当你想要统一API响应格式时。

请注意,@ControllerAdvice注解等同于@RestControllerAdvice,但@RestControllerAdvice更明确地表示它适用于@RestController注解的类。两者在功能上是相同的。

选择性地应用

@RestControllerAdvice注解可以有选择性地应用于特定的包或组件。例如,你可以使用basePackages属性来指定哪些包下的控制器应该被这个@RestControllerAdvice类所影响。

@RestControllerAdvice(basePackages = {"com.example.web"})
public class WebExceptionHandler {
// ...
}

这样,只有com.example.web包下的控制器会使用这个异常处理器。如果你不指定basePackages,那么默认情况下,Spring会查找所有包下的控制器。