博主 默语带您 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 对象的需求,我们需要:
- 解决 流只能读取一次 的问题。
- 使用工具类将 JSON 数据解析为 Java 对象。
- 确保读取请求体不会影响后续流程。
正文
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. 注意事项
- 性能影响:缓存请求体可能会占用较多内存,特别是在请求体较大时,应谨慎使用。
- 支持 JSON 格式:确保请求的
Content-Type
是application/json
。 - 流读取顺序:包装后的请求体流可供拦截器和控制器多次读取,但流的顺序依旧需要保证。
总结
本文详细解析了如何在 Spring 拦截器中获取请求体中的 JSON 数据,并转换为 Java 对象。通过自定义 HttpServletRequestWrapper
,我们实现了请求体的缓存读取,结合 Jackson 库实现了 JSON 到 Java 对象的高效转换。
对于小白用户来说,这种实现方式不仅简单易懂,还能帮助你快速掌握 Spring Web 应用中的核心技能。
参考资料
想深入学习更多技术?加我的微信吧!
如果你对 Spring 或其他技术有疑问,欢迎添加我的微信一起探讨!备注 Spring 学习,我会拉你进群,一起进步!
🪁🍁 希望本文能够给您带来一定的帮助🌸文章粗浅,敬请批评指正!🍁🐥
如对本文内容有任何疑问、建议或意见,请联系作者,作者将尽力回复并改进📓;(联系微信:Solitudemind )
点击下方名片,加入IT技术核心学习团队。一起探索科技的未来,共同成长。