一、ResponseBodyAdvice简介
ResponseBodyAdvice是spring中的一个接口,实现此接口可以处理controller的返回值,前提是加了@ResponseBody注解(包括@RestController),源码如下:
package org.springframework.web.servlet.mvc.method.annotation;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.lang.Nullable;
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:返回true执行beforeBodyWrite方法,返回false则不执行。
- beforeBodyWrite:处理返回值
二、背景
将接口返回的用户数据(AccountDO)中的姓名脱敏。
接口如下:
import com.example.cryptotypehandler.common.Result;
import com.example.cryptotypehandler.domain.AccountDO;
import com.example.cryptotypehandler.service.AccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController
@RequestMapping("/test")
public class TestController {
private final AccountService accountService;
@Autowired
public TestController(AccountService accountService) {
this.accountService = accountService;
}
@PostMapping("/getAccount")
public Result<List<AccountDO>> getAccount(@RequestBody AccountDO accountDO) {
return Result.success(accountService.getAccount(accountDO));
}
}
用户实体类:
import com.baomidou.mybatisplus.annotation.*;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
@Getter
@Setter
@ToString
@TableName("account")
public class AccountDO {
@TableId(type = IdType.ASSIGN_ID)
private Long id;
/**
* 用户名
*/
private String userName;
/**
* 密码
*/
private String password;
}
通用http返回类:
import lombok.Data;
@Data
public class Result<T> {
public Result(boolean success, int code) {
this.setSuccess(success);
this.setCode(code);
}
public Result(boolean success, int code, T data) {
this.setSuccess(success);
this.setCode(code);
this.setData(data);
}
public Result(boolean success, int code, String msg) {
this.setSuccess(success);
this.setCode(code);
this.setMsg(msg);
this.setData(data);
}
public Result(boolean success, int code, String msg, T data) {
this.setSuccess(success);
this.setCode(code);
this.setMsg(msg);
this.setData(data);
}
/**
* 请求是否成功
* true:成功
* false:失败
*/
private boolean success;
/**
* 状态码
* 成功:200
* 失败:其他
*/
private int code;
/**
* 失败状态码描述
* 如果成功不返回
* 失败返回状态码对应的msg消息
*/
private String msg;
/**
* 请求数据的结果
*/
private T data;
public static <T> Result<T> success() {
return new Result<T>(true, 200);
}
public static <T> Result<T> success(String msg) {
return new Result<T>(true, 200, msg);
}
public static <T> Result<T> success(String msg, T data) {
return new Result<T>(true, 200, msg, data);
}
public static <T> Result<T> success(T data) {
return new Result<T>(true, 200, data);
}
public static <T> Result<T> fail(HttpStatusEnum httpStatusEnum) {
return new Result<T>(false, httpStatusEnum.code(), httpStatusEnum.reasonPhraseUS());
}
public static <T> Result<T> fail(HttpStatusEnum httpStatusEnum, String msg) {
return new Result<T>(false, httpStatusEnum.code(), msg);
}
}
三、示例
实现ResponseBodyAdvice接口,需要加上@RestControllerAdvice注解,泛型使用返回值对应的即可;
package com.example.cryptotypehandler.aspect;
import cn.hutool.core.util.DesensitizedUtil;
import com.example.cryptotypehandler.common.Result;
import com.example.cryptotypehandler.domain.AccountDO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
import java.util.List;
@Slf4j
@RestControllerAdvice
public class TestAdvice implements ResponseBodyAdvice<Result<List<AccountDO>>> {
@Override
public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
//笔者这里泛型为Result<List<AccountDO>>,不需要做特殊处理,直接返回true
return true;
}
@Override
public Result<List<AccountDO>> beforeBodyWrite(Result<List<AccountDO>> body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
if (body != null) {
List<AccountDO> data = body.getData();
//使用hutool提供的脱敏工具类DesensitizedUtil进行脱敏
List<AccountDO> collect = data.stream().peek(accountDO -> accountDO.setUserName(DesensitizedUtil.chineseName(accountDO.getUserName()))).toList();
body.setData(collect);
}
//返回处理后的数据
return body;
}
}
运行程序,效果如下:
四、总结
ResponseBodyAdvice其实类似一个返回的切面,可以在返回前再次进行数据处理,上面只是一个小小的应用场景,有兴趣可以深入研究下,欢迎大家讨论~