springboot对返回值作统一处理方式(@RestControllerAdvice+ResponseBodyAdvice接口)
前后端分离的情况下,后端API返回的结果,一般都是一个JSON,类似下面:
{
"code": 0,
"msg": "message",
"data": object
}
如果我们按上面统一的格式处理返回值,那么可以按下面方法:
public class Response<T> {
private int code;
private String msg;
private T data;
public Response(int code, String msg, T data) {
this.code = code;
this.msg = msg;
this.data = data;
}
public static <T> Response<T> success(T data) {
return new Response<>(0, "success", data);
}
public static <T> Response<T> error(String msg) {
return new Response<>(-1, msg, null);
}
}
统一处理异常的返回情况:
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(Exception.class)
public Response<Object> handleException(Exception e) {
// 你可以自定义不同的异常处理逻辑
return Response.error(e.getMessage());
}
}
正常结果包装处理:
@RestControllerAdvice
public class GlobalResponseHandler implements ResponseBodyAdvice<Object> {
@Override
public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
return true;
}
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType,
Class<? extends HttpMessageConverter<?>> selectedConverterType,
ServerHttpRequest request, ServerHttpResponse response) {
if (body instanceof Response) {
return body;
}
return Response.success(body);
}
}
@RestController
public class TestController {
@GetMapping("/user/get")
public User get() {
return new User("小明","男");
}
}
上面的方法会对所有Controller所有方法,均按统一的格式返回,但如果不想对所有的都做类似处理,而是希望有选择性对某些类或者方法返回,可以考虑使用注解的方式实现:
首先,定义一个自定义注解,比如 @ApiResponseWrapper,用于标记需要统一包装返回值的类或方法。
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ApiResponseWrapper {
}
在全局返回值处理器中,通过检查方法或类上是否存在 @ApiResponseWrapper 注解,来决定是否进行包装。
@RestControllerAdvice
public class GlobalResponseHandler implements ResponseBodyAdvice<Object> {
@Override
public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
return returnType.getContainingClass().isAnnotationPresent(ApiResponseWrapper.class) ||
returnType.hasMethodAnnotation(ApiResponseWrapper.class);
}
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType,
Class<? extends HttpMessageConverter<?>> selectedConverterType,
ServerHttpRequest request, ServerHttpResponse response) {
if (body instanceof Response) {
return body;
}
return Response.success(body);
}
}
现在,你可以在需要统一包装返回值的类或方法上使用 @ApiResponseWrapper 注解。
在类上使用注解:
如果你在类上使用注解,类中的所有方法的返回值都会被包装。
@RestController
@RequestMapping("/api")
@ApiResponseWrapper
public class UserController {
@GetMapping("/user/{id}")
public User getUser(@PathVariable Long id) {
return new User(id, "John Doe");
}
}
在方法上使用注解:
如果你只想对特定方法进行包装,可以在方法上使用注解。
@RestController
@RequestMapping("/api")
public class UserController {
@GetMapping("/user/{id}")
@ApiResponseWrapper
public User getUser(@PathVariable Long id) {
return new User(id, "John Doe");
}
@GetMapping("/anotherEndpoint")
public String anotherEndpoint() {
return "This response will not be wrapped";
}
}
当然我们也可以使用请求、响应包装器来实现:
如果你需要对请求的body或者是响应的body进行拦截,Spring已经为我们提供了这样的包装器,可直接使用,如下示例:
@Component
public class ContentFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
ContentCachingRequestWrapper requestWrapper = new ContentCachingRequestWrapper(request) ;
ContentCachingResponseWrapper responseWrapper = new ContentCachingResponseWrapper(response) ;
filterChain.doFilter(requestWrapper, responseWrapper) ;
byte[] responseBody = responseWrapper.getContentAsByteArray();
MessageDigest md5Digest = MessageDigest.getInstance("MD5");
byte[] md5Hash = md5Digest.digest(responseBody);
String md5HashString = DatatypeConverter.printHexBinary(md5Hash);
responseWrapper.setHeader("X-API-SIGN", md5HashString) ;
responseWrapper.copyBodyToResponse();
}
}
上面示例中将响应body内容生成签名信息设置到响应header中,响应结果: