目录
[1. 在 Controller 方法中作为参数注入](#1. 在 Controller 方法中作为参数注入)
[2.使用 RequestContextHolder](#2.使用 RequestContextHolder)
3、使用@AutoWrite自动注入HttpServletRequest
[(1) 启用异步支持](#(1) 启用异步支持)
[(2)在你想异步执行的方法上加 @Async](#(2)在你想异步执行的方法上加 @Async)
大家好,我是jstart千语。我们做项目时,通常要使用到HttpServletRequest来进行对请求响应的消息进行处理,本篇给大家带来三种获取HttpServletRequest的方式。
1. 在 Controller 方法中作为参数注入
SpringMVC会自动注入:
java
@RestController
public class MyController {
@GetMapping("/example")
public String example(HttpServletRequest request) {
String clientIp = request.getRemoteAddr();
return "Client IP: " + clientIp;
}
}
2.使用 RequestContextHolder
如果你不在 Controller 中,而是在 Service、Util 类等位置想获取当前的请求对象,可以使用:
java
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) {
// 获取 HttpServletRequest
HttpServletRequest request = attributes.getRequest();
// 使用请求信息(如获取 Header、参数等)
String userAgent = request.getHeader("User-Agent");
String paramValue = request.getParameter("paramName");
// 获取 HttpServletResponse
HttpServletResponse response = attributes.getResponse();
}
}
}
(1)失效问题
注意点:
RequestContextHolder 使用的是 ThreadLocal 存储当前请求的上下文信息。一旦你离开当前请求线程(例如新开线程),这些上下文信息就不会自动传递过去。如:
java
@RequestMapping("/async-test")
public String asyncTest() {
new Thread(() -> {
HttpServletRequest request = ((ServletRequestAttributes)
RequestContextHolder.getRequestAttributes()).getRequest(); // 值为 null
}).start();
return "OK";
}
(2)解决方案一:
提前取出你想要的值,然后以参数形式传入线程内部,这样就不会有上下文丢失的问题。
java
@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:
java
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 {
@Override
public Runnable decorate(Runnable runnable) {
RequestAttributes context = RequestContextHolder.getRequestAttributes();
return () -> {
try {
RequestContextHolder.setRequestAttributes(context);
runnable.run();
} finally {
RequestContextHolder.resetRequestAttributes();
}
};
}
}
2、配置线程池使用这个装饰器:
java
@Configuration
@EnableAsync
public class AsyncConfig {
@Bean
public 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相同。
使用示例:
java
@Component
public class LogService {
@Autowired
private HttpServletRequest request;
public void printLog() {
System.out.println("请求地址: " + request.getRequestURI());
}
}
跨线程调用失效问题:
- 使用自动注入的方式,因为注入的是一个代理对象。
- 代理对象是和线程绑定的,调用HttpServletRequest调用方法()如getRequestURI()),会通过RequestContextHolder.getRequestAttributes(),找 当前线程绑定的 request 对象
- 所以如果将主线程的HttpServletRequest赋值给了其他线程使用,也是使用不到的
失效问题举例详解:1、把request对象放入全局变量:
javapublic void storeRequestObject() { globalMap.put("lastRequest", request); }
2、另一个线程取出来使用:
java// 假设这是另一个线程: HttpServletRequest req = globalMap.get("lastRequest"); String uri = req.getRequestURI(); // ❌ 此时 request 对应的 ThreadLocal 是空的,报错!
你把 request 这个代理对象存进去后,其他线程如果取出来用,就会出错。因为 这个线程没有设置自己的 RequestContextHolder,调用时会拿不到实际的 request 实例,就会报错
解决:完成线程之间共享存储真正的 request 信息,而不是 request 对象
javapublic void storeRequestInfo() { String uri = request.getRequestURI(); // 当前线程获取 globalMap.put("lastRequestUri", uri); // 只存具体信息,不存对象 }
补充:什么是@Async:
@Async 是 Spring 提供的一个注解,用来让你的方法异步执行(非阻塞)。它背后是线程池 + AOP 实现的。你只需要加个注解,Spring 就会帮你把方法在新线程里执行,非常适合处理不需要立刻返回的任务,比如发送邮件、日志记录、异步通知等等。
(1) 启用异步支持
在你的 Spring Boot 启动类或者配置类上加上:
java
@EnableAsync
@SpringBootApplication
public class MyApp {
public static void main(String[] args) {
SpringApplication.run(MyApp.class, args);
}
}
(2)在你想异步执行的方法上加 @Async
java
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
@Service
public class MyService {
@Async
public void doAsyncTask() {
System.out.println("开始执行异步任务,线程名:" + Thread.currentThread().getName());
try {
Thread.sleep(3000); // 模拟耗时任务
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("异步任务完成");
}
}
(3)调用这个方法(注意!不要在同一个类中自调用)
java
@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 所需依赖)
XML
<!-- pom.xml -->
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
b.自定义 TaskDecorator:让请求上下文穿透到异步线程
java
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 {
@Override
public Runnable decorate(Runnable runnable) {
RequestAttributes context = RequestContextHolder.getRequestAttributes();
return () -> {
try {
RequestContextHolder.setRequestAttributes(context);
runnable.run();
} finally {
RequestContextHolder.resetRequestAttributes();
}
};
}
}
c.配置异步线程池并应用装饰器
java
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 并获取请求信息
java
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 提交异步任务
java
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.测试结果示例
bash
http://localhost:8080/start-async
控制台输出类似:
bash
【异步线程】处理请求 URI: /start-async
【异步线程】客户端 IP: 127.0.0.1
【异步线程】任务处理完毕