当前位置: 首页 > news >正文

【SpringBoot】HttpServletRequest获取使用及失效问题(包含@Async异步执行方案)

目录

1. 在 Controller 方法中作为参数注入

2.使用 RequestContextHolder

(1)失效问题 

(2)解决方案一:

 (3)解决方案二:

3、使用@AutoWrite自动注入HttpServletRequest

跨线程调用失效问题:

补充:什么是@Async:

(1) 启用异步支持

(2)在你想异步执行的方法上加 @Async

(3)调用这个方法(注意!不要在同一个类中自调用)

(4)注意事项

(5)完整示例:


        大家好,我是jstart千语。我们做项目时,通常要使用到HttpServletRequest来进行对请求响应的消息进行处理,本篇给大家带来三种获取HttpServletRequest的方式。

1. 在 Controller 方法中作为参数注入

SpringMVC会自动注入:


@RestController
public class MyController {@GetMapping("/example")public String example(HttpServletRequest request) {String clientIp = request.getRemoteAddr();return "Client IP: " + clientIp;}
}

2.使用 RequestContextHolder

如果你不在 Controller 中,而是在 Service、Util 类等位置想获取当前的请求对象,可以使用:

import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;public class MyService {public void doSomething() {// 获取当前请求的上下文ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();if (attributes != null) {// 获取 HttpServletRequestHttpServletRequest request = attributes.getRequest();// 使用请求信息(如获取 Header、参数等)String userAgent = request.getHeader("User-Agent");String paramValue = request.getParameter("paramName");// 获取 HttpServletResponseHttpServletResponse response = attributes.getResponse();}}
}

(1)失效问题 

注意点:

        RequestContextHolder 使用的是 ThreadLocal 存储当前请求的上下文信息。一旦你离开当前请求线程(例如新开线程),这些上下文信息就不会自动传递过去。如:

@RequestMapping("/async-test")
public String asyncTest() {new Thread(() -> {HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest(); // 值为 null}).start();return "OK";
}

(2)解决方案一:

提前取出你想要的值,然后以参数形式传入线程内部,这样就不会有上下文丢失的问题。

@RequestMapping("/async-test")
public String asyncTest(HttpServletRequest request) {// 主线程中先获取你需要的信息String uri = request.getRequestURI();String clientIp = request.getRemoteAddr();// 把值作为参数传给异步线程new Thread(() -> {System.out.println("异步线程中访问 URI: " + uri);System.out.println("异步线程中客户端 IP: " + clientIp);}).start();return "OK";
}

 (3)解决方案二:

如果用的是 @Async,可以启用上下文传递。

Spring 5.3 开始提供了 TaskDecorator,可以用它将当前的请求上下文“包装”起来传给异步线程。

 1、定义一个TaskDecorator:

import org.springframework.core.task.TaskDecorator;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;public class ContextCopyingDecorator implements TaskDecorator {@Overridepublic Runnable decorate(Runnable runnable) {RequestAttributes context = RequestContextHolder.getRequestAttributes();return () -> {try {RequestContextHolder.setRequestAttributes(context);runnable.run();} finally {RequestContextHolder.resetRequestAttributes();}};}
}

2、配置线程池使用这个装饰器:

@Configuration
@EnableAsync
public class AsyncConfig {@Beanpublic TaskExecutor taskExecutor() {ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();executor.setTaskDecorator(new ContextCopyingDecorator());executor.setCorePoolSize(5);executor.initialize();return executor;}
}



3、使用@AutoWrite自动注入HttpServletRequest

说明:

        Spring 注入的是一个代理对象(HttpServletRequest 是 request scope 的 bean),这个代理在每个请求到达时会根据当前线程,自动定位到当前线程的真实请求对象。

        通过自动注入的HttpServletRequest本质上也是一个RequestContextHolder,代理内部每次调用方法(比如 getRequestURI())时,都会通过 RequestContextHolder.getRequestAttributes() 找 当前线程绑定的 request 对象。

        所以自动注入的方式不适用的场景跟使用RequestContextHolder相同

使用示例:

@Component
public class LogService {@Autowiredprivate HttpServletRequest request;public void printLog() {System.out.println("请求地址: " + request.getRequestURI());}
}

跨线程调用失效问题:

  1. 使用自动注入的方式,因为注入的是一个代理对象。
  2. 代理对象是和线程绑定的,调用HttpServletRequest调用方法()如getRequestURI()),会通过RequestContextHolder.getRequestAttributes(),找 当前线程绑定的 request 对象
  3. 所以如果将主线程的HttpServletRequest赋值给了其他线程使用,也是使用不到的

失效问题举例详解:

1、把request对象放入全局变量:

public void storeRequestObject() {globalMap.put("lastRequest", request); }

2、另一个线程取出来使用:

// 假设这是另一个线程:
HttpServletRequest req = globalMap.get("lastRequest");
String uri = req.getRequestURI(); // ❌ 此时 request 对应的 ThreadLocal 是空的,报错!

你把 request 这个代理对象存进去后,其他线程如果取出来用,就会出错。因为 这个线程没有设置自己的 RequestContextHolder,调用时会拿不到实际的 request 实例,就会报错

解决:完成线程之间共享

存储真正的 request 信息,而不是 request 对象

public void storeRequestInfo() {String uri = request.getRequestURI(); // 当前线程获取globalMap.put("lastRequestUri", uri); // 只存具体信息,不存对象
}



补充:什么是@Async:

        @Async 是 Spring 提供的一个注解,用来让你的方法异步执行(非阻塞)。它背后是线程池 + AOP 实现的。你只需要加个注解,Spring 就会帮你把方法在新线程里执行,非常适合处理不需要立刻返回的任务,比如发送邮件、日志记录、异步通知等等。

(1) 启用异步支持

在你的 Spring Boot 启动类或者配置类上加上:

@EnableAsync
@SpringBootApplication
public class MyApp {public static void main(String[] args) {SpringApplication.run(MyApp.class, args);}
}

(2)在你想异步执行的方法上加 @Async

import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;@Service
public class MyService {@Asyncpublic void doAsyncTask() {System.out.println("开始执行异步任务,线程名:" + Thread.currentThread().getName());try {Thread.sleep(3000); // 模拟耗时任务} catch (InterruptedException e) {e.printStackTrace();}System.out.println("异步任务完成");}
}

(3)调用这个方法(注意!不要在同一个类中自调用)

@RestController
public class TestController {private final MyService myService;public TestController(MyService myService) {this.myService = myService;}@GetMapping("/start-task")public String startTask() {myService.doAsyncTask(); // 异步执行,不会阻塞这个接口的返回return "任务已提交";}
}

(4)注意事项

  • @Async 方法必须是 public 的。
  • @Async 方法不能是自己类内部调用(会失效),必须是通过 Spring 容器的代理调用(也就是从别的类调它)。
  • 返回值可以是 void、Future<T>、CompletableFuture<T> 等。



(5)完整示例:

示例结构:

  • @Async 异步方法
  • 使用 RequestContextHolder 获取请求信息
  • 配置线程池 + 自定义 TaskDecorator
  • 测试 Controller 发起异步请求

a.引入依赖(spring-boot-starter-web 和 spring-boot-starter 已包含 @Async 所需依赖)

<!-- pom.xml -->
<dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency>
</dependencies>

b.自定义 TaskDecorator:让请求上下文穿透到异步线程

import org.springframework.core.task.TaskDecorator;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;public class ContextCopyingTaskDecorator implements TaskDecorator {@Overridepublic Runnable decorate(Runnable runnable) {RequestAttributes context = RequestContextHolder.getRequestAttributes();return () -> {try {RequestContextHolder.setRequestAttributes(context);runnable.run();} finally {RequestContextHolder.resetRequestAttributes();}};}
}

c.配置异步线程池并应用装饰器

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.task.TaskExecutor;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;@Configuration
@EnableAsync
public class AsyncConfig {@Bean("customTaskExecutor")public TaskExecutor taskExecutor() {ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();executor.setCorePoolSize(5);executor.setMaxPoolSize(10);executor.setQueueCapacity(25);executor.setThreadNamePrefix("async-exec-");executor.setTaskDecorator(new ContextCopyingTaskDecorator());executor.initialize();return executor;}
}

d. 异步服务类中使用 @Async 并获取请求信息

import jakarta.servlet.http.HttpServletRequest;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;@Service
public class AsyncService {@Async("customTaskExecutor")public void processAsyncTask() {HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();String uri = request.getRequestURI();String clientIp = request.getRemoteAddr();System.out.println("【异步线程】处理请求 URI: " + uri);System.out.println("【异步线程】客户端 IP: " + clientIp);// 模拟耗时操作try {Thread.sleep(3000);} catch (InterruptedException e) {Thread.currentThread().interrupt();}System.out.println("【异步线程】任务处理完毕");}
}

e.Controller 提交异步任务

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;@RestController
public class TestController {private final AsyncService asyncService;public TestController(AsyncService asyncService) {this.asyncService = asyncService;}@GetMapping("/start-async")public String startAsyncTask() {asyncService.processAsyncTask(); // 调用异步方法return "异步任务已提交,主线程立即返回";}
}

f.测试结果示例

http://localhost:8080/start-async

控制台输出类似:

【异步线程】处理请求 URI: /start-async
【异步线程】客户端 IP: 127.0.0.1
【异步线程】任务处理完毕

相关文章:

  • VLA论文精读(十四)PointVLA: Injecting the 3D World into Vision-Language-Action Models
  • k8s之 kube-prometheus监控
  • 4U带屏基于DSP/ARM+FPGA+AI的电力故障录波装置设计方案,支持全国产化
  • [FPGA基础] 时钟篇
  • CentOS7安装MySQL教程
  • 排序模型(Learning to Rank)
  • 检测IP地址欺诈风险“Scamalytics”
  • 深度解析算法之位运算
  • 无人船 | 图解基于PID控制的路径跟踪算法(以全驱动无人艇WAMV为例)
  • Floyd算法求解最短路径问题——从零开始的图论讲解(3)
  • 信息学奥赛一本通 1504:【例 1】Word Rings | 洛谷 SP2885 WORDRING - Word Rings
  • 聊透多线程编程-线程互斥与同步-12. C# Monitor类实现线程互斥
  • 华为数字化转型“三阶十二步法“:战略驱动、系统布局与敏捷落地的实践框架
  • spark和Hadoop的区别与联系
  • 前端框架开发编译阶段与运行时的核心内容详解Tree Shaking核心实现原理详解
  • 主流大模型(如OpenAI、阿里云通义千问、Anthropic、Hugging Face等)调用不同API的参数说明及对比总结
  • 解决方案评测|告别复杂配置!基于阿里云云原生应用开发平台CAP快速部署Bolt.diy
  • springboot对接阿里云大模型
  • 红队专题-漏洞挖掘-代码审计-反序列化
  • Semaphore的核心机制
  • 宁波一季度GDP为4420.5亿元,同比增长5.6%
  • 吸引更多开发者,上海智元发布行业首款具身智能一站式开发平台
  • 沙龙 | 新书分享:中国电商崛起的制度密码
  • 延安市委副书记马月逢已任榆林市委副书记、市政府党组书记
  • 广东音像城清退,发烧友紧急“淘宝”,曾见证广州音乐黄金期
  • 俄官员称乌克兰未遵守停火,乌方暂无回应