Spring框架接口之RequestBodyAdvice和ResponseBodyAdvice

Spring框架接口之RequestBodyAdvice和ResponseBodyAdvice

Spring框架接口之 RequestBodyAdvice 和 ResponseBodyAdvice

RequestBodyAdviceResponseBodyAdvice 是Spring MVC 中两个非常重要的高级特性,于Spring 4.2 版本引入,它们提供了在消息转换器(HttpMessageConverter) 执行前后进行拦截的切面能力,便于对请求体和响应体进行全局性的、非侵入式的处理。下面将详细介绍一下这两个接口。


RequestBodyAdvice

RequestBodyAdvice 用于在读取请求体之前(或读取得到的对象作为参数传入 @RequestBody 或 HttpEntity Controller 方法之前)进行自定义处理。

1、核心方法

方法 说明
boolean supports(...) 判断该 Advice 是否支持当前的请求(根据方法参数、注解类型等)。
HttpInputMessage beforeBodyRead(...) 在 body 被读取之前 调用。可以用于包装或替换原始的 HttpInputMessage(例如,对加密的请求体进行解密)。
Object afterBodyRead(...) 在 body 被读取并转换之后调用。可以对转换得到的 Java 对象进行后处理(例如,数据校验、属性填充)。
Object handleEmptyBody(...) 当请求体为空时调用。可以返回一个默认值。

2、典型应用场景

  1. 全局请求数据解密:客户端发送的数据是加密的,在 Controller 接收到明文对象之前,先在这里进行解密。

  2. 请求日志记录:记录下请求体的原始内容,用于调试或审计。

  3. 预处理或数据包装:将原始的请求数据包装成一个特定的上下文对象,再传递给 Controller。

  4. 默认值处理:当请求体为空时,提供一个默认的请求对象。


3、工作流程

HttpRequest -> beforeBodyRead (可操作原始流) -> HttpMessageConverter (转换流为对象) -> afterBodyRead (可操作转换后的对象) -> @RequestBody 参数


ResponseBodyAdvice

ResponseBodyAdvice 用于在执行完 @ResponseBodyResponseEntity 控制器方法后,响应体被处理之前对 Controller 方法值进行自定义处理。

1、核心方法

方法 说明
boolean supports(...) 判断该 Advice 是否支持当前的响应(根据返回值类型、方法注解等。
T beforeBodyWrite(...) 在 body 被写入响应之前调用。这是对返回体进行处理的主要方法,可以修改或替换返回的对象。

2、典型应用场景

  1. 统一响应体封装:这是最经典的用法。将 Controller 返回的各种类型(String, Object, List 等)统一包装成 Result/ApiResponse 格式(如 {code: 200, data: ..., message: "success"})。

  2. 全局响应数据加密:在响应写出到客户端之前,对数据进行加密。

  3. 响应日志记录:记录即将返回给客户端的响应体。

  4. 敏感信息过滤:在响应写出前,脱敏或过滤掉敏感数据(如密码、手机号)。


3、工作流程

Controller 返回值 -> beforeBodyWrite (可操作返回对象) -> HttpMessageConverter (将对象转换为流) -> HttpResponse


代码实践

1、使用 RequestBodyAdvice 进行请求解密

java 复制代码
@ControllerAdvice
public class DecryptRequestBodyAdvice implements RequestBodyAdvice {

    @Autowired
    private DecryptService decryptService; // 假设的解密服务

    @Override
    public boolean supports(MethodParameter methodParameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
        // 只处理带有 @Decrypt 注解的参数
        return methodParameter.hasParameterAnnotation(Decrypt.class);
    }

    @Override
    public HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter parameter, Type targetType,
                                          Class<? extends HttpMessageConverter<?>> converterType) throws IOException {
        // 1. 读取加密的请求体
        String encryptedBody = StreamUtils.copyToString(inputMessage.getBody(), StandardCharsets.UTF_8);

        // 2. 进行解密
        String decryptedBody = decryptService.decrypt(encryptedBody);

        // 3. 将解密后的字符串重新封装成新的 InputMessage 并返回
        return new ByteArrayHttpInputMessage(decryptedBody.getBytes(), inputMessage.getHeaders());
    }

    // 其他方法可以默认实现...
    @Override
    public Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType,
                               Class<? extends HttpMessageConverter<?>> converterType) {
        return body;
    }

    @Override
    public Object handleEmptyBody(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType,
                                 Class<? extends HttpMessageConverter<?>> converterType) {
        return body;
    }

    // 内部类,用于创建新的 HttpInputMessage
    static class ByteArrayHttpInputMessage implements HttpInputMessage {
        private final byte[] body;
        private final HttpHeaders headers;

        public ByteArrayHttpInputMessage(byte[] body, HttpHeaders headers) {
            this.body = body;
            this.headers = headers;
        }

        @Override
        public InputStream getBody() {
            return new ByteArrayInputStream(body);
        }

        @Override
        public HttpHeaders getHeaders() {
            return headers;
        }
    }
}

2、使用 ResponseBodyAdvice 实现统一响应包装

定义一个统一响应类

java 复制代码
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Result<T> {
    private Integer code;
    private String message;
    private T data;

    public static <T> Result<T> success(T data) {
        return new Result<>(200, "成功", data);
    }
}
java 复制代码
// 使用 @ControllerAdvice 注解,使其成为一个全局组件
@RestControllerAdvice(basePackages = "com.example.controller")
public class GlobalResponseAdvice implements ResponseBodyAdvice<Object> {

    /**
     * 判断是否支持增强处理。
     * 这里我们排除掉本身就是 Result 类型的返回和一些特殊类型(如 String,需要单独处理)。
     */
    @Override
    public boolean supports(MethodParameter returnType, Class converterType) {
        // 检查方法上是否有不需要包装的注解(自定义)
        // 如果方法或类上有 @IgnoreResponseAdvice 注解,则不做处理
        if (returnType.getMethod().isAnnotationPresent(IgnoreResponseAdvice.class)
                || returnType.getContainingClass().isAnnotationPresent(IgnoreResponseAdvice.class)) {
            return false;
        }
        return true;
    }

    /**
     * 在写入响应体之前进行处理
     */
    @Override
    public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType,
                                  Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {

        // 如果返回体已经是 Result 类型,则直接返回(避免重复包装)
        if (body instanceof Result) {
            return body;
        }
        // 如果返回的是 String 类型,需要特殊处理
        // 因为 StringHttpMessageConverter 会直接写入字符串,不会转成JSON
        if (body instanceof String) {
            // 通常需要手动将 Result 对象序列化成 JSON 字符串
            // 这里需要确保 Jackson 等库在类路径上
            try {
                return new ObjectMapper().writeValueAsString(Result.success(body));
            } catch (JsonProcessingException e) {
                throw new RuntimeException(e);
            }
        }

        // 最普遍的情况:将原始数据包装成 Result 对象
        return Result.success(body);
    }
}

补充

RequestBodyAdvice 接口有实现类 RequestBodyAdviceAdapter (抽象类),在使用时,如 Interceptor (拦截器)处理请求时可以既继承 RequestBodyAdviceAdapter类,又实现 ResponseBodyAdvice 接口,实现对请求前后同时处理。

相关推荐
野犬寒鸦6 分钟前
从零起步学习并发编程 || 第四章:synchronized底层源码级讲解及项目实战应用案例
java·服务器·开发语言·jvm·后端·学习·面试
!停7 分钟前
数据结构二叉树——堆
java·数据结构·算法
virus59458 小时前
悟空CRM mybatis-3.5.3-mapper.dtd错误解决方案
java·开发语言·mybatis
计算机毕设VX:Fegn08958 小时前
计算机毕业设计|基于springboot + vue蛋糕店管理系统(源码+数据库+文档)
数据库·vue.js·spring boot·后端·课程设计
NE_STOP9 小时前
spring6-注解式开发
spring
没差c9 小时前
springboot集成flyway
java·spring boot·后端
三水不滴9 小时前
Redis 过期删除与内存淘汰机制
数据库·经验分享·redis·笔记·后端·缓存
时艰.9 小时前
Java 并发编程之 CAS 与 Atomic 原子操作类
java·开发语言
编程彩机10 小时前
互联网大厂Java面试:从Java SE到大数据场景的技术深度解析
java·大数据·spring boot·面试·spark·java se·互联网大厂
笨蛋不要掉眼泪10 小时前
Spring Boot集成LangChain4j:与大模型对话的极速入门
java·人工智能·后端·spring·langchain