Spring 拦截器:获取 Request 请求体中的 JSON 数据并转换为 Java 对象

在这里插入图片描述

博主 默语带您 Go to New World.
个人主页—— 默语 的博客👦🏻 优秀内容
《java 面试题大全》
《java 专栏》
《idea技术专区》
《spring boot 技术专区》
《MyBatis从入门到精通》
《23种设计模式》
《经典算法学习》
《spring 学习》
《MYSQL从入门到精通》数据库是开发者必会基础之一~
🍩惟余辈才疏学浅,临摹之作或有不妥之处,还请读者海涵指正。☕🍭
🪁 吾期望此文有资助于尔,即使粗浅难及深广,亦备添少许微薄之助。苟未尽善尽美,敬请批评指正,以资改进。!💻⌨


默语是谁?

大家好,我是 默语,别名默语博主,擅长的技术领域包括Java、运维和人工智能。我的技术背景扎实,涵盖了从后端开发到前端框架的各个方面,特别是在Java 性能优化、多线程编程、算法优化等领域有深厚造诣。

目前,我活跃在CSDN、掘金、阿里云和 51CTO等平台,全网拥有超过10万的粉丝,总阅读量超过1400 万。统一 IP 名称为 默语 或者 默语博主。我是 CSDN 博客专家、阿里云专家博主和掘金博客专家,曾获博客专家、优秀社区主理人等多项荣誉,并在 2023 年度博客之星评选中名列前 50。我还是 Java 高级工程师、自媒体博主,北京城市开发者社区的主理人,拥有丰富的项目开发经验和产品设计能力。希望通过我的分享,帮助大家更好地了解和使用各类技术产品,在不断的学习过程中,可以帮助到更多的人,结交更多的朋友.


我的博客内容涵盖广泛,主要分享技术教程、Bug解决方案、开发工具使用、前沿科技资讯、产品评测与使用体验。我特别关注云服务产品评测、AI 产品对比、开发板性能测试以及技术报告,同时也会提供产品优缺点分析、横向对比,并分享技术沙龙与行业大会的参会体验。我的目标是为读者提供有深度、有实用价值的技术洞察与分析。


Spring 拦截器:获取 Request 请求体中的 JSON 数据并转换为 Java 对象


摘要

在 Spring 的 Web 应用中,我们常使用拦截器(HandlerInterceptor)来处理请求的统一逻辑。通常情况下,拦截器可以轻松获取请求头和 URL 参数,但若需要获取请求体中的 JSON 数据并将其转换为 Java 对象,则需要一些特殊处理。本文将从基础概念、实现步骤、完整代码示例等方面,详细解析如何实现这一功能,适合小白快速上手。


引言

Spring 拦截器的核心功能是拦截 HTTP 请求,以实现如权限校验、日志记录等功能。然而,当请求体中包含 JSON 数据时,默认情况下,拦截器无法直接读取请求体。这是因为请求体的流(InputStream)在读取后会被关闭,导致后续的控制器无法再次获取请求体内容。

为了实现拦截器读取请求体并转换为 Java 对象的需求,我们需要:

  1. 解决 流只能读取一次 的问题。
  2. 使用工具类将 JSON 数据解析为 Java 对象。
  3. 确保读取请求体不会影响后续流程。

正文

1. Spring 拦截器基础

1.1 什么是 Spring 拦截器?

Spring 拦截器是一个基于 AOP 的组件,用于在请求到达控制器之前或响应返回客户端之后执行自定义逻辑。

常见场景:

  • 权限校验
  • 日志记录
  • 请求参数处理

Spring 提供的核心接口是 HandlerInterceptor,它包括以下方法:

  • preHandle:请求到达控制器之前执行。
  • postHandle:控制器处理完成后执行,但尚未返回视图。
  • afterCompletion:视图渲染完成后执行。
1.2 配置拦截器

以下是一个简单的拦截器配置:

@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new MyInterceptor())
                .addPathPatterns("/api/**"); // 拦截路径
    }
}

2. 为什么拦截器无法直接读取请求体?

Spring 的 HttpServletRequest 提供了 getInputStream 方法来读取请求体。然而,一旦请求体的流被读取,流将被关闭,无法重复读取。

举例:

@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    InputStream inputStream = request.getInputStream();
    byte[] body = inputStream.readAllBytes(); // 读取请求体
    System.out.println(new String(body)); // 打印请求体
    return true;
}

运行时,尽管 preHandle 中能成功读取请求体,但控制器中将无法再次读取,导致以下错误:

java.io.IOException: Stream closed

3. 如何解决这个问题?

3.1 创建包装类(HttpServletRequestWrapper

通过扩展 HttpServletRequestWrapper,我们可以缓存请求体内容,确保它可以被多次读取。

示例代码:

import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.*;

public class CachedBodyHttpServletRequest extends HttpServletRequestWrapper {

    private final byte[] cachedBody;

    public CachedBodyHttpServletRequest(HttpServletRequest request) throws IOException {
        super(request);
        InputStream requestInputStream = request.getInputStream();
        this.cachedBody = requestInputStream.readAllBytes(); // 缓存请求体
    }

    @Override
    public ServletInputStream getInputStream() {
        return new CachedBodyServletInputStream(this.cachedBody);
    }

    @Override
    public BufferedReader getReader() {
        return new BufferedReader(new InputStreamReader(getInputStream()));
    }

    private static class CachedBodyServletInputStream extends ServletInputStream {
        private final ByteArrayInputStream buffer;

        public CachedBodyServletInputStream(byte[] cachedBody) {
            this.buffer = new ByteArrayInputStream(cachedBody);
        }

        @Override
        public boolean isFinished() {
            return buffer.available() == 0;
        }

        @Override
        public boolean isReady() {
            return true;
        }

        @Override
        public void setReadListener(ReadListener readListener) {
            throw new UnsupportedOperationException();
        }

        @Override
        public int read() {
            return buffer.read();
        }
    }
}

3.2 在拦截器中读取请求体并转换为 Java 对象

我们可以利用 Jackson 库将 JSON 数据解析为 Java 对象。

拦截器实现:

import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@Component
public class MyInterceptor implements HandlerInterceptor {

    private final ObjectMapper objectMapper = new ObjectMapper();

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        if (request.getContentType() != null && request.getContentType().contains("application/json")) {
            CachedBodyHttpServletRequest wrappedRequest = new CachedBodyHttpServletRequest(request);
            String requestBody = wrappedRequest.getReader().lines().reduce("", String::concat);
            System.out.println("请求体 JSON 字符串: " + requestBody);

            // 将 JSON 转换为 Java 对象
            MyRequestBody myRequestBody = objectMapper.readValue(requestBody, MyRequestBody.class);
            System.out.println("转换后的 Java 对象: " + myRequestBody);

            return true;
        }
        return true;
    }
}

// 示例 Java 对象
class MyRequestBody {
    private String name;
    private int age;

    // Getters 和 Setters
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "MyRequestBody{name='" + name + "', age=" + age + "}";
    }
}

3.3 注册拦截器

将拦截器添加到 Spring MVC 配置中:

@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new MyInterceptor())
                .addPathPatterns("/api/**");
    }
}

4. 注意事项

  1. 性能影响:缓存请求体可能会占用较多内存,特别是在请求体较大时,应谨慎使用。
  2. 支持 JSON 格式:确保请求的 Content-Typeapplication/json
  3. 流读取顺序:包装后的请求体流可供拦截器和控制器多次读取,但流的顺序依旧需要保证。

总结

本文详细解析了如何在 Spring 拦截器中获取请求体中的 JSON 数据,并转换为 Java 对象。通过自定义 HttpServletRequestWrapper,我们实现了请求体的缓存读取,结合 Jackson 库实现了 JSON 到 Java 对象的高效转换。

对于小白用户来说,这种实现方式不仅简单易懂,还能帮助你快速掌握 Spring Web 应用中的核心技能。


参考资料

  1. Spring 官方文档
  2. Jackson JSON 官方文档

想深入学习更多技术?加我的微信吧!

如果你对 Spring 或其他技术有疑问,欢迎添加我的微信一起探讨!备注 Spring 学习,我会拉你进群,一起进步!

在这里插入图片描述


🪁🍁 希望本文能够给您带来一定的帮助🌸文章粗浅,敬请批评指正!🍁🐥

如对本文内容有任何疑问、建议或意见,请联系作者,作者将尽力回复并改进📓;(联系微信:Solitudemind )

点击下方名片,加入IT技术核心学习团队。一起探索科技的未来,共同成长。

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

默 语

你的鼓励将是我创作的最大动力!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值