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


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

    一、问题描述

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

    1. public static HttpServletRequest getRequest() {
    2. ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes)RequestContextHolder.getRequestAttributes();
    3. HttpServletRequest request = servletRequestAttributes.getRequest();
    4. return request ;
    5. }

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

    二、模拟实现

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

    1. @RequestMapping("/req")
    2. public String req(){
    3. ExecutorService executor = Executors.newFixedThreadPool(2);
    4. executor.submit(()->{
    5. log.info(Thread.currentThread().getName() + " start ===>");
    6. String token = null;
    7. try {
    8. Thread.sleep(1000);
    9. token = RequestUtil.getRequest().getHeader("token");
    10. } catch (InterruptedException e) {
    11. e.printStackTrace();
    12. }catch (Exception e){
    13. /**
    14. * 注意: 需要增加 catch Exception 异常 ;
    15. * 否则: RequestUtil.getRequest() 的 NPE 异常无法抛出!
    16. */
    17. e.printStackTrace();
    18. }
    19. log.info(Thread.currentThread().getName() + " end token ===>{}", token);
    20. });
    21. return "ok";
    22. }

            2、输出结果如下:

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

    三、解决

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

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

            2、完整代码参考如下:

    1. @RequestMapping("/req/fix")
    2. public String reqFix(){
    3. // 设置request 对象在,子线程(异步线程)中可以共享
    4. RequestContextHolder.setRequestAttributes(RequestContextHolder.getRequestAttributes(),true);
    5. HttpServletRequest request = RequestUtil.getRequest();
    6. ExecutorService executor = Executors.newFixedThreadPool(2);
    7. executor.submit(()->{
    8. log.info(Thread.currentThread().getName() + " start ===>");
    9. String token = null;
    10. try {
    11. Thread.sleep(1000);
    12. token = request.getHeader("token");
    13. } catch (InterruptedException e) {
    14. e.printStackTrace();
    15. }catch (Exception e){
    16. e.printStackTrace();
    17. }
    18. log.info(Thread.currentThread().getName() + " end token ===> {}", token);
    19. });
    20. return "token=" + RequestUtil.getRequest().getHeader("token");
    21. }

    四、总结

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

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

    1. public static void main(String[] args) {
    2. ExecutorService executor = Executors.newSingleThreadExecutor();
    3. executor.submit(()->{
    4. try {
    5. Thread.sleep(1000);
    6. /**
    7. * 未正确的捕获异常:
    8. * InterruptedException 无法捕获 xx 异常
    9. */
    10. int a = 3/ 0;
    11. } catch (InterruptedException e) {
    12. e.printStackTrace();
    13. }
    14. System.out.println(Thread.currentThread().getName()+" ===> 异步线程执行结束!");
    15. });
    16. System.out.println("main 执行结束 ");
    17. }

            1.2、输出结果:

    main 执行结束

            1.3、未正确的捕获异常:

    1. public static void main(String[] args) {
    2. ExecutorService executor = Executors.newSingleThreadExecutor();
    3. executor.submit(()->{
    4. try {
    5. Thread.sleep(1000);
    6. /**
    7. * 未正确的捕获异常:
    8. * InterruptedException 无法捕获 xx 异常
    9. */
    10. int a = 3/ 0;
    11. } catch (InterruptedException e) {
    12. e.printStackTrace();
    13. }
    14. System.out.println(Thread.currentThread().getName()+" ===> 异步线程执行结束!");
    15. });
    16. System.out.println("main 执行结束 ");
    17. }

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

    1. public static void main(String[] args) {
    2. ExecutorService executor = Executors.newSingleThreadExecutor();
    3. executor.submit(()->{
    4. try {
    5. Thread.sleep(1000);
    6. /**
    7. * 未正确的捕获异常:
    8. * InterruptedException 无法捕获 ArithmeticException 异常
    9. */
    10. int a = 3/ 0;
    11. } catch (InterruptedException e) {
    12. e.printStackTrace();
    13. }catch (Exception e){
    14. e.printStackTrace();
    15. }
    16. System.out.println(Thread.currentThread().getName()+" ===> 异步线程执行结束!");
    17. });
    18. System.out.println("main 执行结束 ");
    19. }

            2.1、输出结果:

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

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

    1. // 主线程可以正常获取到参数, 异步线程中获取到的是null
    2. 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
     

  • 相关阅读:
    vue的学习与应用
    亚马逊云科技 Build On 2022 - AIot 第二季物联网专场实验心得
    【Linux】线程互斥
    win11不兼容,咋办啊大家,我的世界下载16位不兼容我试了好多种办法不管用,希望有懂得,可以帮助一下
    如何看待服装订单外流现象?
    MicroPython——有点东西,但是不多
    PLSQL数据库Mybatis学习Day02
    从CNN(卷积神经网络),又名CAM获取热图
    三、基于图像分类预训练编码及图神经网络的预测模型 【框图+源码】
    数据结构 | 顺序表SeqList【增、删、查、改~】
  • 原文地址:https://blog.csdn.net/HaHa_Sir/article/details/127044489