当前位置: 首页 > 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
【异步线程】任务处理完毕

http://www.xdnf.cn/news/61057.html

相关文章:

  • 使用IntersectionObserver实现目标元素可见度的交互
  • web原生API AbortController网络请求取消方法使用介绍:防止按钮重复点击提交得最佳方案
  • 数码管静态显示一位字符(STC89C52单片机)
  • QT 的.pro 转 vsproject 工程
  • C++ 2025 展望:现代编程需求与新兴技术驱动下的变革
  • 目标检测篇---R-CNN梳理
  • 多线程出bug不知道如何调试?java线程几种常见状态
  • 讯联桌面TV版apk下载-讯联桌面安卓电视版免费下载安装教程
  • Python-Django系列—部件
  • 天翼云手机断开连接2小时关机
  • 2025大模型十大安全威胁(OWASP TOP 10 LLM 2025).pdf
  • 基于MuJoCo物理引擎的机器人学习仿真框架robosuite
  • Python常用的第三方模块【openpyxl库】读写Excel文件
  • 机器学习-08-推荐算法-案例
  • C# .NET如何自动实现依赖注入(DI)
  • AI大模型如何重塑科研:从灵感生成到学术写作的全链路变革
  • CH585单片机的LCD外设怎么驱动段式LCD
  • 【matlab】地图上的小图
  • 基于Java的不固定长度字符集在指定宽度和自适应模型下图片绘制生成实战
  • Linux学习笔记协议篇(六):SPI FLASH设备驱动
  • 华为VRP系统知识总结及案例试题
  • 下载pycharm遇到的问题及解决方法
  • 深度学习3.5图像分类数据集
  • elastic/go-elasticsearch与olivere/elastic
  • 乐家桌面安卓版2025下载-乐家桌面软件纯净版安装分享码大全
  • 【scikit-learn基础】--『监督学习』之 均值聚类
  • GPT,Genini, Claude Llama, DeepSeek,Qwen,Grok,选对LLM大模型真的可以事半功倍!
  • 发布事件和Insert数据库先后顺序
  • GeoJSON 格式详解与使用指南
  • Macbook IntelliJ IDEA终端无法运行mvn命令