赞
踩
一、问题描述
在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 对象,是可以解决 父子线程,变量共享的问题,请自行研究。
了解更多:
https://blog.csdn.net/HaHa_Sir/article/details/127044832
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。