news 2026/9/27 7:34:42

springboot request wrapper + request context holder

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
springboot request wrapper + request context holder

内容简述

目的

解决servlet request InputStream body只能读取一次的问题
设置请求级别的线程变量 ThreadLocal
注意:Fitler 执行循序,以免在wrapper 初始化前,body已经消费

Request wrapper

wrapper

重写和 body 读取相关的 3 个核心方法:
- getInputStream()
- getReader()- getContentLength()

javaimport java.io.*; public class RepeatableHttpServletRequestWrapper extends HttpServletRequestWrapper { /** 缓存请求体字节数组 */ private byte[] body; /** * Constructs a request object wrapping the given request. * * @param request The request to wrap * @throws IllegalArgumentException if the request is null */ public RepeatableHttpServletRequestWrapper(HttpServletRequest request) throws IOException { super(request); this.body = toByteArray(request.getInputStream()); } /** * 将输入流读取为字节数组 * * @param input 输入流 * @return 字节数组 * @throws IOException 读取失败时抛出 */ private static byte[] toByteArray(InputStream input) throws IOException { ByteArrayOutputStream buffer = new ByteArrayOutputStream(); byte[] data = new byte[4096]; int n; while ((n = input.read(data, 0, data.length)) != -1) { buffer.write(data, 0, n); } return buffer.toByteArray(); } @Override public ServletInputStream getInputStream() { ByteArrayInputStream input = new ByteArrayInputStream(body); return new ServletInputStream() { @Override public boolean isFinished() { return false; } @Override public boolean isReady() { return false; } @Override public void setReadListener(ReadListener listener) { } @Override public int read(){ return input.read(); } }; } @Override public BufferedReader getReader(){ return new BufferedReader(new InputStreamReader(getInputStream())); } @Override public int getContentLength() { return body.length; } /** * 获取缓存的请求体字节数组 * * @return 请求体字节数组 */ public byte[] getBody() { return body; } }

filter

importjakarta.servlet.FilterChain;importjakarta.servlet.ServletException;importjakarta.servlet.http.HttpServletRequest;importjakarta.servlet.http.HttpServletResponse;importorg.springframework.web.filter.OncePerRequestFilter;importjava.io.IOException;publicclassRepeatableReadFilterextendsOncePerRequestFilter{@OverrideprotectedvoiddoFilterInternal(HttpServletRequestrequest,HttpServletResponseresponse,FilterChainfilterChain)throwsServletException,IOException{if(request.getContentType()!=null){RepeatableHttpServletRequestWrapperwrapperRequest=newRepeatableHttpServletRequestWrapper(request);filterChain.doFilter(wrapperRequest,response);}else{filterChain.doFilter(request,response);}}}

Request context holder

自定义Request context Object

importlombok.*;@Getter@Setter@NoArgsConstructor@AllArgsConstructor@ToStringpublicclassRequestContextHolderObject<T>{privateStringUUID;privateTt;}

实现RequestContextHolder

publicclassRequestContextHolder{privatestaticfinalInheritableThreadLocal<Object>threadLocal=newInheritableThreadLocal<>();publicstatic<T>voidset(RequestContextHolderObject<T>commonHeader){threadLocal.set(commonHeader);}@SuppressWarnings("unchecked")publicstatic<T>RequestContextHolderObject<T>get(){return(RequestContextHolderObject<T>)threadLocal.get();}publicstaticvoidremove(){threadLocal.remove();}publicstaticStringgetUUID(){RequestContextHolderObject<Object>objectRequestContextHolderObject=get();if(objectRequestContextHolderObject==null){returnnull;}returnobjectRequestContextHolderObject.getUUID();}}

Filter 实现

publicclassCustomerRequestContextFilterextendsOncePerRequestFilter{@OverrideprotectedvoiddoFilterInternal(HttpServletRequestrequest,HttpServletResponseresponse,FilterChainfilterChain)throwsServletException,IOException{// 继承 MDC 中有 traceId,没有生成新的try{StringtraceId=MDC.get("traceId");Stringuuid=StringUtil.isEmptyString(traceId)?UUID.randomUUID().toString():traceId;RequestContextHolder.set(newRequestContextHolderObject<Object>(uuid,"test"));filterChain.doFilter(request,response);}finally{RequestContextHolder.remove();}}}

子线程或者线程池传递

  1. 修改RequestContextHolder 使用TTL
    TransmittableThreadLocal (TTL)
  2. 自定义线程池,添加传递Decorator 实现
importcom.ft.mall.common.web.requestholder.RequestContextHolder;importcom.ft.mall.common.web.requestholder.RequestContextHolderObject;importorg.slf4j.MDC;importorg.springframework.context.annotation.Bean;importorg.springframework.core.task.TaskDecorator;importorg.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;importjava.util.Map;importjava.util.concurrent.Executor;publicclassSimpleExecutor{@Bean("asyncExecutor")publicExecutorasyncExecutor(){ThreadPoolTaskExecutorexecutor=newThreadPoolTaskExecutor();executor.setCorePoolSize(10);executor.setMaxPoolSize(20);executor.setQueueCapacity(100);executor.setThreadNamePrefix("async-");// 配置MDC上下文传递装饰器executor.setTaskDecorator(newMdcTaskDecorator());executor.initialize();returnexecutor;}/** * MDC上下文传递装饰器 */staticclassMdcTaskDecoratorimplementsTaskDecorator{@OverridepublicRunnabledecorate(Runnablerunnable){// 父线程拷贝MDC上下文Map<String,String>contextMap=MDC.getCopyOfContextMap();RequestContextHolderObject<Object>objectRequestContextHolderObject=RequestContextHolder.get();return()->{try{// 子线程设置MDCif(contextMap!=null){MDC.setContextMap(contextMap);RequestContextHolder.set(objectRequestContextHolderObject);}runnable.run();}finally{MDC.clear();RequestContextHolder.remove();}};}}}

filter 注册

importcom.ft.mall.common.web.filter.RepeatableReadFilter;importjakarta.servlet.Filter;importorg.springframework.boot.web.servlet.FilterRegistrationBean;importorg.springframework.context.annotation.Bean;importorg.springframework.context.annotation.Configuration;importorg.springframework.core.Ordered;importorg.springframework.web.servlet.config.annotation.WebMvcConfigurer;@ConfigurationpublicclassWebConfigimplementsWebMvcConfigurer{/** * 注册可重复读请求体过滤器 * * <p>拦截所有路径,优先级最高,确保在 Spring 拦截器和其他 Filter 之前包装请求。 * * @return Filter 注册 Bean */@BeanpublicFilterRegistrationBean<Filter>repeatableReadFilter(){FilterRegistrationBean<Filter>registration=newFilterRegistrationBean<>();registration.setFilter(newRepeatableReadFilter());registration.addUrlPatterns("/*");registration.setOrder(Ordered.HIGHEST_PRECEDENCE);registration.setName("repeatbleFilter");returnregistration;}@BeanpublicFilterRegistrationBean<Filter>requestContextHolderFilter(){FilterRegistrationBean<Filter>registration=newFilterRegistrationBean<>();registration.setFilter(newCustomerRequestContextFilter());registration.addUrlPatterns("/*");registration.setOrder(Ordered.HIGHEST_PRECEDENCE+2);registration.setName("requestContextHolderFilter");returnregistration;}}
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/27 7:31:32

MySQL复杂查询与Union操作

在日常开发中,数据查询是数据库操作的核心部分,尤其是在处理多表数据时,单一的查询方式无法满足业务的复杂需求。因此,掌握复杂查询技术变得至关重要。 本教程将介绍如何使用UNION操作进行多表数据合并查询,并通过CASE语句实现条件选择。通过对这些技巧的学习,可以轻松处…

作者头像 李华
网站建设 2026/9/27 7:28:51

CppNet 预处理器深度解析:Stride 中面向 Clang 的 C C/C++ 宏预处理器

游戏开发图形学VR 【免费下载链接】stride Stride (formerly Xenko), a free and open-source cross-platform C# game engine. 项目地址&#xff1a; https://gitcode.com/gh_mirrors/st/stride 点击查看 免费下载 CppNet 是 Stride 引擎仓库中附带的一个 C 语言预处理器&…

作者头像 李华
网站建设 2026/9/27 7:27:53

让大模型稳定输出 JSON:Schema 校验、失败重试与降级的三层防线

背景&#xff1a;崩掉程序的往往不是答案错&#xff0c;而是格式 我们那个桌面工具的主流程很朴素&#xff1a;用户在聊天窗口里发一句自然语言&#xff0c;程序把它交给模型&#xff0c;要求返回一段 JSON&#xff0c;说明"用户想干什么、涉及哪些参数"&#xff0c;…

作者头像 李华
网站建设 2026/9/27 7:26:02

第2篇 Prometheus 服务发现:动态目标管理实战

随着容器化与微服务架构的普及&#xff0c;被监控对象的形态发生了根本变化。过去以物理机、虚拟机为主体的固定目标&#xff0c;如今逐步被 Kubernetes 编排下的 Pod、Service 等实例所取代。这些实例的 IP 由集群动态分配&#xff0c;重启即变、扩缩容以秒计&#xff0c;生命…

作者头像 李华
网站建设 2026/9/27 7:25:28

智能轨道插座系统技术选型维度与供应商能力评估

在建筑配电柔性化升级过程中&#xff0c;智能轨道插座凭借取电点位可调、布线集成度高的技术特性&#xff0c;在家装、办公、商铺、厂房等场景的应用规模持续扩大。当前市场上产品技术水平参差不齐&#xff0c;部分产品存在长期运行接触不良、安全防护配置不达标、售后服务缺失…

作者头像 李华