Spring Boot 异步线程静态获取request对象为空 RequestContextHolder 为空 Java 异步线程获取request为空

19 篇文章 0 订阅
18 篇文章 0 订阅

Spring Boot 异步线程静态获取request对象为空 RequestContextHolder 为空 Java 异步线程获取request为空

一、问题描述

        在Spring Boot的web项目中,采用静态获取request对象时,发现无法获取到request对象,而获取的 RequestContextHolder 对象为空,抛出 NPE 异常 ...

public static HttpServletRequest getRequest() {
		ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes)RequestContextHolder.getRequestAttributes();
		HttpServletRequest request = servletRequestAttributes.getRequest();
		return request ;

}

        经过排查代码,发现是在异步线程中,静态获取request对象,导致获取不到,从而抛出NPE异常...

二、模拟实现

        1、演示:异步线程中无法获取到request对象,抛出NPE异常

@RequestMapping("/req")
public String req(){
    ExecutorService executor = Executors.newFixedThreadPool(2);
    executor.submit(()->{
        log.info(Thread.currentThread().getName() + " start ===>");
        String token = null;
        try {
            Thread.sleep(1000);
            token = RequestUtil.getRequest().getHeader("token");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }catch (Exception e){
            /**
             *  注意: 需要增加 catch Exception 异常 ;
             *  否则: RequestUtil.getRequest() 的 NPE 异常无法抛出!
             */
             e.printStackTrace();
        }
       log.info(Thread.currentThread().getName() + " end  token  ===>{}", token);
    });
    return "ok";
}

        2、输出结果如下:

INFO]  com.runcode.springboottourist.RequController:40  : pool-8-thread-1 start ===> 
java.lang.NullPointerException
	at com.runcode.springboottourist.util.RequestUtil.getRequest(RequestUtil.java:29)
	at com.runcode.springboottourist.RequController.lambda$req$0(RequController.java:44)
	at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
	at java.util.concurrent.FutureTask.run(FutureTask.java:266)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
	at java.lang.Thread.run(Thread.java:745)
[INFO]  com.runcode.springboottourist.RequController:54  : pool-8-thread-1 end  token  ===>null 

三、解决

        1、只需要设置 request 对象可以在子线程中共享即可,在 主线程代码部分设置即可。

RequestContextHolder.setRequestAttributes(RequestContextHolder.getRequestAttributes(),true);

        2、完整代码参考如下:

@RequestMapping("/req/fix")
public String reqFix(){
    // 设置request 对象在,子线程(异步线程)中可以共享
    RequestContextHolder.setRequestAttributes(RequestContextHolder.getRequestAttributes(),true);
    HttpServletRequest request = RequestUtil.getRequest();

    ExecutorService executor = Executors.newFixedThreadPool(2);
    executor.submit(()->{
        log.info(Thread.currentThread().getName() + " start ===>");
        String token = null;
        try {
            Thread.sleep(1000);
            token = request.getHeader("token");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }catch (Exception e){
            e.printStackTrace();
        }
        log.info(Thread.currentThread().getName() + " end  token  ===> {}", token);
    });
    return "token=" + RequestUtil.getRequest().getHeader("token");
}

四、总结

        1、在写异步线程代码时,一定要注意异常情况的捕获和处理;若未正确的捕获或处理异常,会导致程序没有达到预期的执行结果,且没有任何异常输出,造成出现问题,难以排查的情况。

        1.1、未正确的处理异常情况:

public static void main(String[] args) {
    ExecutorService executor = Executors.newSingleThreadExecutor();
    executor.submit(()->{
        try {
            Thread.sleep(1000);
            /**
             * 未正确的捕获异常:
             * InterruptedException 无法捕获 xx 异常
             */
            int a = 3/ 0;
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println(Thread.currentThread().getName()+" ===> 异步线程执行结束!");
    });

    System.out.println("main 执行结束 ");
}

        1.2、输出结果:

main 执行结束

        1.3、未正确的捕获异常:

public static void main(String[] args) {
    ExecutorService executor = Executors.newSingleThreadExecutor();
    executor.submit(()->{
        try {
            Thread.sleep(1000);
            /**
             * 未正确的捕获异常:
             * InterruptedException 无法捕获 xx 异常
             */
            int a = 3/ 0;
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println(Thread.currentThread().getName()+" ===> 异步线程执行结束!");
    });

    System.out.println("main 执行结束 ");
}

        2、正确的处理异常情况: 最后一级增加 Exception 捕获

public static void main(String[] args) {
    ExecutorService executor = Executors.newSingleThreadExecutor();
    executor.submit(()->{
        try {
            Thread.sleep(1000);
            /**
             * 未正确的捕获异常:
             * InterruptedException 无法捕获 ArithmeticException 异常
             */
            int a = 3/ 0;
        } catch (InterruptedException e) {
            e.printStackTrace();
        }catch (Exception e){
            e.printStackTrace();
        }

        System.out.println(Thread.currentThread().getName()+" ===> 异步线程执行结束!");
    });

    System.out.println("main 执行结束 ");
}

        2.1、输出结果:

main 执行结束 
java.lang.ArithmeticException: / by zero
	at com.runcode.springboottourist.RequController.lambda$main$3(RequController.java:154)
	at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
	at java.util.concurrent.FutureTask.run(FutureTask.java:266)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
	at java.lang.Thread.run(Thread.java:745)
pool-1-thread-1 ===> 异步线程执行结束!

        3、本文仅仅是解决:异步线程中无法获取request对象的问题;对于主线程结束后,异步线程获取到的 request 对象,会存在request 对象获取到的方法参数都为空的情况,例如:

// 主线程可以正常获取到参数, 异步线程中获取到的是null 

RequestUtil.getRequest().getHeader("token");

        建议解决办法: 主线程中获取到,以参数形式传递到子线程、存到redis中、重写request对象等方法进行尝试解决。

        4、RequestContextHolder 方法的实现,点进去源码进行查看,里面有2个 ThreadLocal 对象,是可以解决 父子线程,变量共享的问题,请自行研究。


了解更多: 

        SpringMVC中静态获取request对象 Spring中获取 HttpServletRequest对象 SpringBoot中静态获取request_HaHa_Sir的博客-CSDN博客_静态获取request



​​​​​​​https://blog.csdn.net/HaHa_Sir/article/details/127044832
 

  • 4
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
异步线程获取到的request为空,这是因为异步线程和原始的请求线程不在同一个线程中,异步线程无法直接访问原始的请求线程request对象。解决这个问题的方法有两种: 1. 使用Callable接口:可以将异步任务封装成一个Callable对象,并将原始的request对象作为参数传递给Callable对象。在异步任务中,可以通过Future对象获取到Callable对象的返回值,从而获取request对象。 代码示例: ``` @RestController public class MyController { @Autowired private AsyncService asyncService; @RequestMapping(&quot;/test&quot;) public String test(HttpServletRequest request) throws Exception { Callable<String> task = () -> { // 异步任务中获取request对象 HttpServletRequest asyncRequest = AsyncRequestContextHolder.getRequest(); // 处理业务逻辑 return &quot;success&quot;; }; Future<String> future = asyncService.execute(task); String result = future.get(); return result; } } @Service public class AsyncService { @Autowired private AsyncTaskExecutor taskExecutor; public <T> Future<T> execute(Callable<T> task) { AsyncRequestContextCallable<T> callable = new AsyncRequestContextCallable<>(task); return taskExecutor.submit(callable); } } public class AsyncRequestContextCallable<T> implements Callable<T> { private final Callable<T> task; private final HttpServletRequest request; public AsyncRequestContextCallable(Callable<T> task) { this.task = task; this.request = AsyncRequestContextHolder.getRequest(); } @Override public T call() throws Exception { AsyncRequestContextHolder.setRequest(request); try { return task.call(); } finally { AsyncRequestContextHolder.resetRequest(); } } } public class AsyncRequestContextHolder { private static final ThreadLocal<HttpServletRequest> requestHolder = new ThreadLocal<>(); public static void setRequest(HttpServletRequest request) { requestHolder.set(request); } public static HttpServletRequest getRequest() { return requestHolder.get(); } public static void resetRequest() { requestHolder.remove(); } } ``` 2. 使用ServletRequestAttributes:可以使用Spring提供的ServletRequestAttributes类来获取request对象。这个类是一个请求属性的存储器,可以在任何线程中存储和获取请求属性。在异步任务中,可以通过ServletRequestAttributes获取到原始的request对象。 代码示例: ``` @RestController public class MyController { @Autowired private AsyncService asyncService; @RequestMapping(&quot;/test&quot;) public String test(HttpServletRequest request) throws Exception { Callable<String> task = () -> { // 异步任务中获取request对象 ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); HttpServletRequest asyncRequest = attributes.getRequest(); // 处理业务逻辑 return &quot;success&quot;; }; Future<String> future = asyncService.execute(task); String result = future.get(); return result; } } @Service public class AsyncService { @Autowired private AsyncTaskExecutor taskExecutor; public <T> Future<T> execute(Callable<T> task) { return taskExecutor.submit(() -> { RequestAttributes attributes = RequestContextHolder.getRequestAttributes(); try { RequestContextHolder.setRequestAttributes(attributes, true); return task.call(); } finally { RequestContextHolder.resetRequestAttributes(); } }); } } ``` 以上两种方法都可以解决异步线程获取request对象为空的问题,具体选择哪种方法取决于具体的业务需求和开发习惯。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值