# 系列文6:ThreadLocal上下文管理,让业务层彻底告别Htt

张开发
2026/6/9 15:22:36 15 分钟阅读
# 系列文6:ThreadLocal上下文管理,让业务层彻底告别Htt
系列文6ThreadLocal上下文管理让业务层彻底告别HttpServletRequest非科班野生程序员深耕政务信息化20年这套自研Java Web框架支撑过省级新农保、全国首例跨省医保结算等核心民生系统18年稳定运行至今。本系列拆解10个核心架构决策全是政务场景踩坑后的实用解法不求优雅但求落地愿同赛道朋友少走弯路也欢迎懂行大佬轻拍指正。最后感谢豆包、智谱、OpenCode决策是我做的代码是我搓的文字是他们总结的。背景业务方法经常需要获取当前用户、数据库连接、操作权限等信息。最直接的方式是在每个方法里传HttpServletRequest从 session 中取。但这样业务代码就和 Servlet API 强耦合了。能不能让业务层完全不需要HttpServletRequest却能随时获取这些上下文信息我的方案用 ThreadLocal 把上下文绑定到当前线程业务层随时取用。AppContextContainer.javapackagecom.browise.core.context;publicclassAppContextContainerextendsThreadLocalAppContext{privatestaticAppContextContainercontainernewAppContextContainer();publicstaticAppContextgetAppContext(){if(container.get()!null)returncontainer.get();AppContextImpllocalAppContextImplnewAppContextImpl();container.set(localAppContextImpl);returnlocalAppContextImpl;}protectedAppContextinitialValue(){returnnull;}publicstaticvoidsetAppContext(AppContextparamAppContext){container.set(paramAppContext);}}非常简洁——继承ThreadLocalAppContext提供静态的getAppContext()和setAppContext()方法。getAppContext()如果当前线程已有上下文直接返回否则创建一个新的setAppContext()绑定上下文到当前线程initialValue()默认返回 nullAppContextImpl.java上下文实现类保存了整个请求周期需要的所有信息packagecom.browise.core.context;importjava.util.ArrayList;importjava.util.HashMap;importjava.util.List;importjavax.servlet.ServletContext;importcom.browise.core.util.bSession;importcom.browise.core.util.ds.DataStore;importcom.browise.login.User;publicclassAppContextImplimplementsAppContext{privatestaticfinallongserialVersionUID1L;privateUserusernull;privateListUserallUsernull;privateStringBAZ099;// bean idprivateStringBAZ098;// 方法名privateServletContextcontext;privatebSession sessionnull;privateListbSessionlistnewArrayListbSession();privateStringcurrDept;// 当前用户部门privateStringcurrYfsb;// 当前用户药费标识privatebooleanisBigfalse;// 是否大数据查询privateHashMapString,DataStoremapnewHashMapString,DataStore();privateStringtxtFileName;privateHashMapString,bSessionconnectMapnewHashMapString,bSession();// getter/setter 省略...OverridepublicvoiddelSession(){for(inti0;ithis.list.size();i){try{bSession slist.get(i);AppContextcontextAppContextContainer.getAppContext();System.out.println(关闭连接,未关闭的连接数context.getBAZ099(),context.getBAZ098());if(s.equals(this.session))list.remove(i);try{s.rollback();}catch(Exceptione){}s.close();if(s.equals(this.session))this.sessionnull;}catch(Exceptione){}}}}在 route.java 的 service() 中设置上下文每次请求进来在route.java的service()方法里把所有需要的信息绑定到 ThreadLocalAppContextcontextnewAppContextImpl();HttpSessionsessionrequest.getSession();// 设置用户信息context.setUser((User)session.getAttribute(user));// 当前用户部门StringpowerString.valueOf(session.getAttribute(power));if(!.equals(power)!null.equals(power)power!null){context.setCurrDetp(power);}// 当前用户药费标识StringyfsbString.valueOf(session.getAttribute(yfsb));if(!.equals(yfsb)!null.equals(yfsb)yfsb!null){context.setCurrYfsb(yfsb);}context.setBAZ098(method1.getName());// 方法名context.setBAZ099(mothodmap.getBeanid());// bean idcontext.setContext(this.getServletContext());context.setAllUser((ListUser)session.getAttribute(allUser));AppContextContainer.setAppContext(context);请求结束后在finally块里清理finally{context.delSession();}delSession() 的作用delSession()方法遍历所有数据库连接逐个关闭publicvoiddelSession(){for(inti0;ithis.list.size();i){try{bSession slist.get(i);AppContextcontextAppContextContainer.getAppContext();System.out.println(关闭连接,未关闭的连接数context.getBAZ099(),context.getBAZ098());if(s.equals(this.session))list.remove(i);try{s.rollback();}catch(Exceptione){}s.close();if(s.equals(this.session))this.sessionnull;}catch(Exceptione){}}}注意打印的日志输出的是 bean id 和方法名这样如果连接泄漏了看日志就知道是哪个业务方法的连接没关。业务层怎么用业务代码中完全不需要HttpServletRequest直接从 ThreadLocal 取// 获取当前用户UseruserAppContextContainer.getAppContext().getUser();// 获取数据库连接bSession sessionAppContextContainer.getAppContext().getSeesion();// 获取当前部门StringdeptAppContextContainer.getAppContext().getCurrDetp();// 获取所有用户ListUserallUserAppContextContainer.getAppContext().getAllUser();连接管理AppContextImpl内部维护了一个ListbSession来管理一次请求中打开的所有数据库连接。业务方法通过DBUtil.BeginTrans()获取连接时连接会被放入这个列表privateListbSessionlistnewArrayListbSession();OverridepublicvoidputSession(bSession s){list.add(s);}请求结束时delSession()统一关闭所有连接确保不会有连接泄漏。为什么这样做和 Spring 的RequestContextHolder一个思路但实现更简单业务代码完全不依赖 Servlet API连接生命周期和请求生命周期绑定不用担心连接泄漏上下文信息集中管理取用方便决策原则让业务层不需要关心从哪来的问题。用户信息从 session 来、数据库连接从连接池来——这些从哪来的细节应该在框架层处理业务方法只需要知道现在有什么。你的项目中是怎么管理请求上下文的ThreadLocal、RequestScoped Bean、还是其他方式欢迎评论区讨论。系列导航- 上一篇[系列文5解决Java编译痛点ASM字节码直读100%获取方法参数名]下一篇[系列文7零侵入提升查询性能MongoDB混合存储不用Redis也能抗]作者许彰午| 非科班野生程序员深耕政务信息化20年标签#Java #ThreadLocal #上下文管理 #数据库连接 #Servlet解耦 #政务信息化 #技术复盘

更多文章