在springboot中实现一个拦截器(handlerinterceptor),用于获取请求的入参并将其转化为 java 对象。
方式一:拦截器配合过滤器
1. 设计思路
在 web 开发中,当 http 请求发送到服务器时,spring 会通过一个链条处理这个请求,这个链条包括很多组件,比如:拦截器(handlerinterceptor
)、过滤器(filter
)、控制器(controller)、参数解析器(比如 spring mvc 的 @requestparam
或 @requestbody
等注解)等。
输入流的问题
当客户端通过 post
请求发送数据时,数据通常是包含在请求体中的(比如表单数据或者 json 数据)。spring 的 httpservletrequest
提供了 getinputstream()
方法来读取请求体中的数据。
问题: httpservletrequest.getinputstream()
只能读取一次。也就是说,当你在拦截器中调用了 getinputstream()
读取数据时,流就被消费掉了,后续的组件(例如,spring 的参数解析器)再调用 getinputstream()
就无法读取到数据了,因为流已经被关闭了。
解决方案
解决这个问题的思路是:在拦截器中读取请求体的数据时,不直接从 httpservletrequest
中读取,而是通过包装(httpservletrequestwrapper
)的方式,重新实现 getinputstream()
,将读取的数据缓存下来,确保后续的处理链依然能够访问到请求体的内容。
2. 如何实现
- 创建一个
httpservletrequestwrapper
类:它将重写getinputstream()
方法,让流的数据可以多次读取。通过这个类缓存请求体的内容。 - 创建一个
filter
:用于包装请求,将httpservletrequest
包装成我们自己的httpservletrequestwrapper
。 - 在拦截器中获取请求体:在
handlerinterceptor
中获取请求体并进行解析。
3. 实现步骤
3.1 创建 httpservletrequestwrapper
这个类主要作用是缓存请求体内容,并且重写 getinputstream()
,让它能够多次读取。
import javax.servlet.http.httpservletrequest; import javax.servlet.http.httpservletrequestwrapper; import java.io.bytearrayinputstream; import java.io.ioexception; import java.io.inputstream; public class cachedbodyhttpservletrequestwrapper extends httpservletrequestwrapper { private byte[] requestbody; public cachedbodyhttpservletrequestwrapper(httpservletrequest request) throws ioexception { super(request); // 从 inputstream 读取数据,缓存请求体内容 inputstream inputstream = request.getinputstream(); this.requestbody = inputstream.readallbytes(); // 将流中的数据读取到字节数组中 } @override public inputstream getinputstream() throws ioexception { // 返回缓存的数据 return new bytearrayinputstream(requestbody); } public byte[] getrequestbody() { return requestbody; } }
3.2 创建 filter 以包装 httpservletrequest
这个过滤器的作用是将原始的 httpservletrequest
替换为我们自定义的 cachedbodyhttpservletrequestwrapper
。
import javax.servlet.filter; import javax.servlet.filterchain; import javax.servlet.filterconfig; import javax.servlet.servletexception; import javax.servlet.servletrequest; import javax.servlet.servletresponse; import javax.servlet.annotation.webfilter; import javax.servlet.http.httpservletrequest; import java.io.ioexception; @webfilter("/*") // 这个过滤器会拦截所有请求 public class requestwrapperfilter implements filter { @override public void init(filterconfig filterconfig) throws servletexception { // 初始化操作 } @override public void dofilter(servletrequest request, servletresponse response, filterchain chain) throws ioexception, servletexception { // 仅对 httpservletrequest 进行包装 if (request instanceof httpservletrequest) { cachedbodyhttpservletrequestwrapper wrappedrequest = new cachedbodyhttpservletrequestwrapper((httpservletrequest) request); // 将包装后的请求传递给下一个过滤器 chain.dofilter(wrappedrequest, response); } else { // 对非 httpservletrequest 请求不做任何处理 chain.dofilter(request, response); } } @override public void destroy() { // 销毁操作 } }
3.3 创建拦截器 handlerinterceptor 以处理请求参数
接下来,在 spring 的拦截器中获取请求体并解析成 java 对象。这个拦截器将会在请求进入控制器之前进行拦截。
import org.springframework.web.servlet.handlerinterceptor; import javax.servlet.http.httpservletrequest; import javax.servlet.http.httpservletresponse; import java.io.ioexception; public class requestbodyinterceptor implements handlerinterceptor { @override public boolean prehandle(httpservletrequest request, httpservletresponse response, object handler) throws ioexception { if (request instanceof cachedbodyhttpservletrequestwrapper) { // 获取包装后的请求体 cachedbodyhttpservletrequestwrapper wrappedrequest = (cachedbodyhttpservletrequestwrapper) request; string requestbody = new string(wrappedrequest.getrequestbody(), "utf-8"); // 打印或处理请求体内容 system.out.println("request body: " + requestbody); // 将请求体转换成 java 对象 myrequestobject myrequestobject = new objectmapper().readvalue(requestbody, myrequestobject.class); system.out.println("parsed java object: " + myrequestobject); } return true; // 返回 true,表示继续处理请求 } }
3.4 注册拦截器和过滤器
- 在 spring boot 中注册
filter
:
提示 :在spring boot项目中,filter
会自动注册到应用上下文中,可以不手动注册。
import org.springframework.boot.web.servlet.filterregistrationbean; import org.springframework.context.annotation.bean; import org.springframework.context.annotation.configuration; @configuration public class filterconfig { @bean public filterregistrationbean<requestwrapperfilter> loggingfilter() { filterregistrationbean<requestwrapperfilter> registrationbean = new filterregistrationbean<>(); registrationbean.setfilter(new requestwrapperfilter()); registrationbean.addurlpatterns("/api/*"); // 这里根据需要配置拦截的 url return registrationbean; } }
- 在 spring boot 中注册
handlerinterceptor
:
import org.springframework.beans.factory.annotation.autowired; import org.springframework.context.annotation.bean; import org.springframework.context.annotation.configuration; import org.springframework.web.servlet.config.annotation.interceptorregistry; import org.springframework.web.servlet.config.annotation.webmvcconfigurer; @configuration public class webconfig implements webmvcconfigurer { @autowired private requestbodyinterceptor requestbodyinterceptor; @override public void addinterceptors(interceptorregistry registry) { registry.addinterceptor(requestbodyinterceptor).addpathpatterns("/api/*"); // 根据需要配置路径 } }
4. 总结
通过以上步骤,你可以实现一个能够多次读取 httpservletrequest.getinputstream()
数据的机制。基本思路是:
- 创建一个
httpservletrequestwrapper
类来缓存请求体内容; - 通过
filter
来包装httpservletrequest
; - 在
handlerinterceptor
中获取请求体并进行处理。
这样,无论在拦截器还是后续的参数解析过程中,都会能够多次访问请求体数据。
方式二:拦截器中使用包装类contentcachingrequestwrapper
在 http 请求中,httpservletrequest
的请求体(post 请求中的 json 数据)是一次性的流,读取完一次之后,如果没有特殊处理,就不能再次读取它。
contentcachingrequestwrapper
是 spring 框架提供的一个包装类,它的作用是“包装”原始的 httpservletrequest
对象,使得请求体内容可以被多次读取。
使用contentcachingrequestwrapper
,省去方法一中创建的 httpservletrequestwrapper和requestwrapperfilter。
import org.springframework.web.servlet.handlerinterceptor; import javax.servlet.http.httpservletrequest; import javax.servlet.http.httpservletresponse; import java.io.ioexception; public class requestbodyinterceptor implements handlerinterceptor { @override public boolean prehandle(httpservletrequest request, httpservletresponse response, object handler) throws ioexception { if (httpmethod.post.matches(request.getmethod())) { // 包装请求,使其可以多次读取请求体 contentcachingrequestwrapper wrappedrequest = new contentcachingrequestwrapper(request); // 读取请求体,明确指定字符编码为 utf-8 string requestbody = new string(wrappedrequest.getcontentasbytearray(), "utf-8"); // 打印或处理请求体内容 system.out.println("request body: " + requestbody); // 将请求体转换成 java 对象 myrequestobject myrequestobject = new objectmapper().readvalue(requestbody, myrequestobject.class); system.out.println("parsed java object: " + myrequestobject); } return true; // 返回 true,表示继续处理请求 } }
总结
contentcachingrequestwrapper
是一种非常有用的工具,允许缓存并多次读取请求体内容,尤其需要在拦截器中处理请求体数据时,它非常有效。
到此这篇关于如何使用拦截器获取请求的入参并将其转化为java对象的文章就介绍到这了,更多相关拦截器获取请求入参并转化java对象内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
发表评论