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
 

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐