关于SpringBoot如何使用RequestBodyAdvice进行统一参数处理,我们可以按照以下步骤来实现:
1.新建一个类,实现RequestBodyAdvice接口
在这个类中,我们可以定义在请求体内任意对象被反序列化之前需要执行的操作,比如参数加密或解密、参数校验等。
@ControllerAdvice
public class RequestBodyDecryptAdvice implements RequestBodyAdvice {
@Override
public boolean supports(MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) {
// 这里我们只做对象类型的判断
return Object.class.equals(type);
}
@Override
public Object handleEmptyBody(Object o, HttpInputMessage httpInputMessage, MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) {
return o;
}
@Override
public HttpInputMessage beforeBodyRead(HttpInputMessage httpInputMessage, MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) throws IOException {
return httpInputMessage;
}
@Override
public Object afterBodyRead(Object o, HttpInputMessage httpInputMessage, MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) {
// 在这里进行参数解密或校验等操作
return o;
}
}
可以看到,这里我们主要重写了RequestBodyAdvice接口中的四个方法:
-
boolean supports(MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass):该方法判断被注解的方法需要处理哪些对象类型的RequestBody参数。如果该方法返回true,则该RequestBody参数的处理交给下面其他三个方法。
-
HttpInputMessage beforeBodyRead(HttpInputMessage httpInputMessage, MethodParameter methodParameter, Type type, Class<?> aClass):该方法在被注解的方法中的RequestBody参数被反序列化前执行,在这里可以修改请求报文。
-
Object afterBodyRead(Object body, HttpInputMessage httpInputMessage, MethodParameter methodParameter, Type type, Class<?> aClass):该方法在RequestBody参数被反序列化后,但在被注解的方法执行之前执行。
-
Object handleEmptyBody(Object body, HttpInputMessage httpInputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType):该方法在请求体为空时触发,一般不做处理。
2.在ControllerAdvice中添加@Order注解
如果我们的项目中同时使用了多个RequestBodyAdvice,那么每个RequestBodyAdvice都需要通过@Order注解标注顺序,否则可能会引起处理冲突。
@ControllerAdvice
@Order(1)
public class RequestBodyAdvice1 implements RequestBodyAdvice {
//省略代码...
}
@ControllerAdvice
@Order(2)
public class RequestBodyAdvice2 implements RequestBodyAdvice {
//省略代码...
}
3.在被注解的方法上添加@RequestBody注解
@PostMapping("/user")
public User addUser(@RequestBody User user) {
return userService.addUser(user);
}
示例一:RequestBody参数加密
这里我们通过AES算法来对RequestBody进行加密处理:
@ControllerAdvice
public class RequestBodyEncryptAdvice implements RequestBodyAdvice {
@Override
public boolean supports(MethodParameter methodParameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
// 这里我们只做对象类型的判断
return Object.class.equals(targetType);
}
@Override
public Object handleEmptyBody(Object body, HttpInputMessage httpInputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
return body;
}
@Override
public HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) throws IOException {
byte[] body = IOUtils.readFully(inputMessage.getBody(), inputMessage.getHeaders().getContentLength());
byte[] decryptedBody = aesDecrypt(body, "password");
return new DecryptedHttpInputMessage(inputMessage.getHeaders(), decryptedBody);
}
@Override
public Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
return body;
}
/**
* 对body体进行aes解密
* @param encrypted 加密后的body体
* @param password 密钥
*/
private static byte[] aesDecrypt(byte[] encrypted, String password) {
try {
SecretKeySpec keySpec = new SecretKeySpec(md5(password), "AES");
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.DECRYPT_MODE, keySpec);
byte[] decrypted = cipher.doFinal(encrypted);
return decrypted;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* 使用md5算法对密码进行处理,生成16字节的密钥
*/
private static byte[] md5(String password) throws Exception {
MessageDigest md = MessageDigest.getInstance("md5");
byte[] bytes = md.digest(password.getBytes("utf-8"));
return Arrays.copyOf(bytes, 16);
}
/**
* 自定义HttpInputMessage,用于封装解密后的body体
*/
static class DecryptedHttpInputMessage implements HttpInputMessage {
private HttpHeaders headers;
private InputStream body;
public DecryptedHttpInputMessage(HttpHeaders headers, byte[] decryptedBody) {
this.body = new ByteArrayInputStream(decryptedBody);
this.headers = headers;
}
@Override
public InputStream getBody() throws IOException {
return this.body;
}
@Override
public HttpHeaders getHeaders() {
return this.headers;
}
}
}
在使用时,我们只需要在被注解的方法上添加 @RequestBody,并在需要加密的RequestBody类型上添加 @Encrypt注解即可:
@PostMapping("/hello")
public String hello(@RequestBody @Encrypt HelloRequest request) {
return request.getName() + " say hello to " + request.getTarget();
}
以上代码中,HelloRequest为需要加密的RequestBody类型,@Encrypt为自己定义的注解。
示例二:RequestBody参数验证
这里我们通过Spring提供的校验注解进行RequestBody参数校验:
@Data
public class UserRequest {
@NotBlank(message = "用户名不能为空")
private String username;
@NotNull(message = "年龄不能为空")
@Min(value = 18, message = "年龄必须大于18岁")
private Integer age;
}
@ControllerAdvice
public class RequestBodyValidationAdvice implements RequestBodyAdvice {
@Autowired
private Validator validator;
@Override
public boolean supports(MethodParameter methodParameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
// 这里我们只做对象类型的判断
return Object.class.equals(targetType);
}
@Override
public HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) throws IOException {
Object body = StreamUtils.copyToString(inputMessage.getBody(), Charset.forName("UTF-8"));
Set<ConstraintViolation<Object>> validations = validator.validate(JsonUtils.fromJson(body.toString(), Object.class));
if(validations.size() > 0)
throw new BindException(validations.iterator().next().getMessage());
return inputMessage;
}
/*
* 其余接口省略...
*/
}
在使用时,我们只需要在被注解的方法上添加 @RequestBody,并在需要加密的RequestBody类型上添加相关的校验注解即可:
@PostMapping("/user")
public User addUser(@RequestBody @Valid UserRequest request) {
return userService.addUser(User.builder().username(request.getUsername()).age(request.getAge()).build());
}
以上代码中,UserRequest为需要验证的RequestBody类型,@Valid为Spring提供的校验注解,方便我们快速从错误中找到需要校验出错的字段。
至此,使用RequestBodyAdvice进行统一参数处理的操作完成啦!