2026/1/13 3:07:10
网站建设
项目流程
wordpress 手机发文章,搜索引擎优化的英语简称,沈阳专业音响公司,衡水企业网站制作公司在SpringBoot做Web开发时#xff0c;HttpServletRequest、HttpServletResponse这些Servlet原生对象绝对是高频工具——毕竟处理HTTP请求全靠它们。这些对象都跟单次请求绑在一起#xff0c;用起来不算复杂#xff0c;但在子线程、异步场景下容易踩坑。
一、Servlet原生Reque…在SpringBoot做Web开发时HttpServletRequest、HttpServletResponse这些Servlet原生对象绝对是高频工具——毕竟处理HTTP请求全靠它们。这些对象都跟单次请求绑在一起用起来不算复杂但在子线程、异步场景下容易踩坑。一、Servlet原生Request相关对象怎么用实际效果Servlet规范里定义了一组跟HTTP请求、响应紧密相关的核心对象SpringBoot直接帮我们封装好了不用额外配置就能用。核心就是这几个HttpServletRequest、HttpServletResponse、HttpSession、ServletContext、Principal注HttpSession是会话级的、ServletContext是全局应用级的但平时都跟Request对象搭配着用所以放一起说。1. 先搞懂每个对象是干嘛的HttpServletRequest请求的“信息包”——客户端IP、请求方法GET/POST、URL、传过来的参数、Cookie这些都在里面拿HttpServletResponse响应的“操作器”——给客户端回传数据、设置响应头、重定向、设置Cookie都靠它HttpSession用户的“专属储物柜”——存储登录状态、用户信息这种跨请求的状态数据比如登录后存个用户名后续请求都能拿到ServletContext应用的“全局公告板”——存应用级的常量、资源路径比如应用版本号、上下文路径整个应用都能访问Principal当前登录用户的“身份证”——需要结合Spring Security这类认证框架用能拿到登录用户的用户名等信息2. 两种常用用法直接上代码看效果这两种用法都很简单实际开发里选第一种最稳妥不容易出问题。用法1Controller方法参数直接拿推荐不用搞任何复杂配置直接在Controller的方法里写这些对象当参数SpringMVC会自动把当前请求对应的对象传进来。importorg.springframework.web.bind.annotation.GetMapping;importorg.springframework.web.bind.annotation.RestController;importjavax.servlet.http.HttpServletRequest;importjavax.servlet.http.HttpServletResponse;importjavax.servlet.http.HttpSession;importjavax.servlet.ServletContext;importjava.security.Principal;RestControllerpublicclassRequestScopeController{GetMapping(/test/request-params)publicStringtestRequestParams(HttpServletRequestrequest,HttpServletResponseresponse,HttpSessionsession,ServletContextservletContext,Principalprincipal){// 1. 从request拿请求信息StringclientIprequest.getRemoteAddr();// 客户端IPStringrequestMethodrequest.getMethod();// 请求方法StringrequestUrirequest.getRequestURI();// 请求路径StringuserNamerequest.getParameter(username);// 拿到传过来的username参数// 2. 用response设置响应信息response.setHeader(X-Request-Id,REQ-System.currentTimeMillis());// 加个自定义响应头response.setCharacterEncoding(UTF-8);// 确保响应中文不乱码// 3. 用session存会话信息session.setAttribute(login_user,userName!null?userName:游客);StringsessionUser(String)session.getAttribute(login_user);// 4. 用servletContext拿应用信息StringcontextPathservletContext.getContextPath();// 应用路径servletContext.setAttribute(app_version,1.0.0);// 存个应用版本StringappVersion(String)servletContext.getAttribute(app_version);// 5. 拿认证用户信息没登录就是nullStringauthUserprincipal!null?principal.getName():未登录;// 拼返回结果returnString.format(请求信息IP%s, 方法%s, 路径%s, 参数username%s\n会话信息当前用户%s\n应用信息路径%s, 版本%s\n认证信息登录用户%s,clientIp,requestMethod,requestUri,userName,sessionUser,contextPath,appVersion,authUser);}}实际测试效果在浏览器里访问http://localhost:8080/test/request-params?usernamezhangsan会看到返回结果请求信息IP0:0:0:0:0:0:0:1, 方法GET, 路径/test/request-params,参数usernamezhangsan 会话信息当前用户zhangsan 应用信息路径/, 版本1.0.0认证信息登录用户未登录用法2Autowired注入只推荐在Controller用也可以直接在Controller里用Autowired注入这些对象。可能有人会问Controller是单例的注入请求级的对象会不会有线程安全问题放心Spring帮我们做了特殊处理——注入的是动态代理对象每次用的时候都会从当前请求线程里拿最新的实例不会乱。importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.web.bind.annotation.GetMapping;importorg.springframework.web.bind.annotation.RestController;importjavax.servlet.http.HttpServletRequest;RestControllerpublicclassAutowiredRequestController{AutowiredprivateHttpServletRequestrequest;GetMapping(/test/autowired-request)publicStringtestAutowiredRequest(){Stringrefererrequest.getHeader(Referer);// 拿请求来源比如从哪个页面跳过来的StringuserAgentrequest.getHeader(User-Agent);// 拿浏览器信息returnString.format(请求来源%s\n浏览器信息%s,referer,userAgent);}}实际测试效果访问http://localhost:8080/test/autowired-request返回结果请求来源null 浏览器信息Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36这里请求来源是null因为是直接在浏览器输入地址访问的不是从其他页面跳转的。二、子线程/异步场景怎么用这些对象不踩坑重点注意这些Request相关对象默认只跟处理请求的主线程绑在一起。如果直接在子线程里拿大概率会拿到null——因为子线程里没有请求上下文。那想在子线程里用怎么办核心思路就一个把主线程的请求上下文“传递”给子线程再绑到子线程上。1. 核心原理简单说Spring里有个叫RequestContextHolder的工具类底层是用ThreadLocal实现的专门用来存当前线程的请求上下文ServletRequestAttributes。主线程处理请求时Spring已经把上下文存进去了我们要做的就是在主线程里把这个上下文拿出来传给子线程再让子线程把它存到自己的ThreadLocal里这样子线程就能拿到Request对象了。2. 两种常见场景直接上可运行代码场景1普通子线程Thread/Runnableimportorg.springframework.web.bind.annotation.GetMapping;importorg.springframework.web.bind.annotation.RestController;importorg.springframework.web.context.request.RequestContextHolder;importorg.springframework.web.context.request.ServletRequestAttributes;importjavax.servlet.http.HttpServletRequest;RestControllerpublicclassAsyncRequestController{GetMapping(/test/thread-request)publicStringtestThreadRequest()throwsInterruptedException{// 第一步在主线程里拿到请求上下文必须在主线程拿ServletRequestAttributesattributes(ServletRequestAttributes)RequestContextHolder.getRequestAttributes();HttpServletRequestmainRequestattributes.getRequest();StringmainThreadMsg主线程拿到的请求路径mainRequest.getRequestURI()\n;// 第二步创建子线程把上下文传进去并绑定ThreadthreadnewThread(()-{// 把主线程的上下文绑到子线程上RequestContextHolder.setRequestAttributes(attributes);try{// 现在子线程就能正常拿Request对象了HttpServletRequestchildRequest((ServletRequestAttributes)RequestContextHolder.getRequestAttributes()).getRequest();StringchildThreadMsg子线程拿到的请求路径childRequest.getRequestURI()\n;StringchildThreadParam子线程拿到的username参数childRequest.getParameter(username)\n;System.out.println(childThreadMsgchildThreadParam);// 打印到控制台}finally{// 关键子线程执行完一定要清除上下文避免内存泄漏RequestContextHolder.resetRequestAttributes();}});// 启动子线程thread.start();thread.join();// 等子线程执行完再返回returnmainThreadMsg子线程已经执行完啦去看控制台输出;}}测试效果访问http://localhost:8080/test/thread-request?usernamezhangsan接口返回主线程拿到的请求路径/test/thread-request 子线程已经执行完啦去看控制台输出控制台输出子线程拿到的请求路径/test/thread-request子线程拿到的username参数zhangsan场景2Spring异步方法Async实际开发中更常用Spring的Async注解实现异步用法差不多就是要把上下文从Controller传给异步方法。注意要先在启动类上加EnableAsync开启异步功能哦// 第一步写个异步服务类importorg.springframework.scheduling.annotation.Async;importorg.springframework.stereotype.Service;importorg.springframework.web.context.request.RequestContextHolder;importorg.springframework.web.context.request.ServletRequestAttributes;importjavax.servlet.http.HttpServletRequest;ServicepublicclassAsyncRequestService{// 异步方法接收主线程传过来的上下文AsyncpublicvoidhandleRequestInAsync(ServletRequestAttributesattributes){// 绑定上下文到异步线程RequestContextHolder.setRequestAttributes(attributes);try{HttpServletRequestrequest((ServletRequestAttributes)RequestContextHolder.getRequestAttributes()).getRequest();System.out.println(异步方法拿到的请求方法request.getMethod());System.out.println(异步方法拿到的客户端IPrequest.getRemoteAddr());}finally{// 记得清除上下文RequestContextHolder.resetRequestAttributes();}}}// 第二步Controller调用异步方法importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.web.bind.annotation.GetMapping;importorg.springframework.web.bind.annotation.RestController;importorg.springframework.web.context.request.RequestContextHolder;importorg.springframework.web.context.request.ServletRequestAttributes;RestControllerpublicclassAsyncController{AutowiredprivateAsyncRequestServiceasyncRequestService;GetMapping(/test/async-request)publicStringtestAsyncRequest(){// 主线程拿上下文传给异步方法ServletRequestAttributesattributes(ServletRequestAttributes)RequestContextHolder.getRequestAttributes();asyncRequestService.handleRequestInAsync(attributes);return异步方法已经触发啦去看控制台输出;}}测试效果访问http://localhost:8080/test/async-request接口返回异步方法已经触发啦去看控制台输出控制台输出异步方法拿到的请求方法GET异步方法拿到的客户端IP0:0:0:0:0:0:0:13. 避坑提醒必看上下文一定要在主线程里拿子线程里直接拿肯定是null子线程/异步线程执行完必须调用RequestContextHolder.resetRequestAttributes()清除上下文不然ThreadLocal会一直持有引用导致内存泄漏尽量别在子线程里操作HttpServletResponse比如往响应里写数据——主线程可能已经把响应返回给客户端了这时候子线程再操作就会报错子线程执行时间别太长不然会一直占用请求上下文资源影响应用性能。最后总结一下Servlet原生的Request相关对象优先用Controller方法参数直接拿简单又安全Controller层也能Autowired注入Spring的动态代理会帮我们保证线程安全子线程里用这些对象记住“主线程拿上下文→子线程绑上下文→用完清上下文”三步就不会踩坑异步场景下直接传上下文比传Request对象更稳妥符合Spring的使用规范。