Springboot项目:解决@Async注解获取不到上下文信息问题

问题描述

springboot项目中,需要使用到异步调用某个方法,此时 第一个想到的就是 @Async 注解,但是 发现 方法执行报错了,具体报错如下:

java 复制代码
java.lang.NullPointerException
	at com.ruoyi.common.utils.ServletUtils.getRequest(ServletUtils.java:56)
	at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:782)
	at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:717)
	at org.springframework.web.client.RestTemplate.postForObject(RestTemplate.java:443)
	at com.ruoyi.web.ecs.service.impl.EcsCollectOperateServiceImpl.collect(EcsCollectOperateServiceImpl.java:42)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:90)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:55)
	at java.lang.reflect.Method.invoke(Method.java:508)
	at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:344)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:198)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163)
	at org.springframework.aop.interceptor.AsyncExecutionInterceptor.lambda$invoke$0(AsyncExecutionInterceptor.java:115)
	at java.util.concurrent.FutureTask.run(FutureTask.java:277)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1160)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635)
	at java.lang.Thread.run(Thread.java:825)

上面日志有点多,其实核心就是这一部分日志:

java 复制代码
java.lang.NullPointerException
	at com.ruoyi.common.utils.ServletUtils.getRequest(ServletUtils.java:56)

这块逻辑就是,使用spring底层提供的获取上下文信息的方法。

所以说明 获取不到上下文信息,结果导致报错

解决办法

  • 对自定义的配置类 进行改造,原来的配置类是这样的:
java 复制代码
import java.lang.reflect.Method;
import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

@Configuration
@EnableAsync
public class AsyncConfiguration {

	private static final Logger logger = LoggerFactory.getLogger(AsyncConfiguration.class);

	@Bean(name = "taskExecutor")
	public Executor getAsyncExecutor() {
        ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();

        // 核心线程数:线程池创建时候初始化的线程数
        taskExecutor.setCorePoolSize(10);

        // 最大线程数:线程池最大的线程数,只有在缓冲队列满了之后才会申请超过核心线程数的线程
        taskExecutor.setMaxPoolSize(20);

        // 缓冲队列:用来缓冲执行任务的队列
        taskExecutor.setQueueCapacity(50);

        // 允许线程的空闲时间60秒:当超过了核心线程之外的线程在空闲时间到达之后会被销毁
        taskExecutor.setKeepAliveSeconds(60);

        // 线程池名的前缀:设置好了之后可以方便我们定位处理任务所在的线程池
        taskExecutor.setThreadNamePrefix("HiTask-");

        // 缓冲队列满了之后的拒绝策略:由调用线程处理(一般是主线程)
        //taskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        taskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.DiscardPolicy());
        taskExecutor.initialize();

        return taskExecutor;
    }



	class MyAsyncExceptionHandler implements AsyncUncaughtExceptionHandler {

		@Override
		public void handleUncaughtException(Throwable throwable, Method method, Object... objects) {
			logger.error("MethodName={},Throwable={}",method.getName(),throwable.toString());
		}
	}
}
  • 改造之后:
java 复制代码
import java.lang.reflect.Method;
import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;

@Configuration
@EnableAsync
public class AsyncConfiguration {

	private static final Logger logger = LoggerFactory.getLogger(AsyncConfiguration.class);

	@Bean(name = "taskExecutor")
	public Executor getAsyncExecutor() {
        ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();

        // 核心线程数:线程池创建时候初始化的线程数
        taskExecutor.setCorePoolSize(10);

        // 最大线程数:线程池最大的线程数,只有在缓冲队列满了之后才会申请超过核心线程数的线程
        taskExecutor.setMaxPoolSize(20);

        // 缓冲队列:用来缓冲执行任务的队列
        taskExecutor.setQueueCapacity(50);

        // 允许线程的空闲时间60秒:当超过了核心线程之外的线程在空闲时间到达之后会被销毁
        taskExecutor.setKeepAliveSeconds(60);

        // 线程池名的前缀:设置好了之后可以方便我们定位处理任务所在的线程池
        taskExecutor.setThreadNamePrefix("HiTask-");

        // 缓冲队列满了之后的拒绝策略:由调用线程处理(一般是主线程)
        //taskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        taskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.DiscardPolicy());
        taskExecutor.initialize();

        //解决使用@Async注解,获取不到上下文信息的问题
        taskExecutor.setTaskDecorator(runnable -> {
            RequestAttributes requestAttributes = RequestContextHolder.currentRequestAttributes();
            return ()->{
                try {
                    // 我们set 进去 ,其实是一个ThreadLocal维护的.
                    RequestContextHolder.setRequestAttributes(requestAttributes);
                    runnable.run();
                } finally {
                    // 最后记得释放内存
                    RequestContextHolder.resetRequestAttributes();
                }
            };

        });

        return taskExecutor;
    }



	class MyAsyncExceptionHandler implements AsyncUncaughtExceptionHandler {

		@Override
		public void handleUncaughtException(Throwable throwable, Method method, Object... objects) {
			logger.error("MethodName={},Throwable={}",method.getName(),throwable.toString());
		}
	}
}

总结

解决使用@Async注解,获取不到上下文信息的问题,只需要增加这一段代码即可

java 复制代码
 taskExecutor.setTaskDecorator(runnable -> {
            RequestAttributes requestAttributes = RequestContextHolder.currentRequestAttributes();
            return ()->{
                try {
                    // 我们set 进去 ,其实是一个ThreadLocal维护的.
                    RequestContextHolder.setRequestAttributes(requestAttributes);
                    runnable.run();
                } finally {
                    // 最后记得释放内存
                    RequestContextHolder.resetRequestAttributes();
                }
            };

        });

额外补充一点

如果使用 @Async注解,发现没有生效,那有可能 你没有加 @EnableAsync 注解。

@EnableAsync注解表示 开启异步任务,可以写在springboot的启动类上,也可以写在 配置类上

相关推荐
时间静止不是简史3 小时前
生产事故排查:Redis 宕机导致补偿队列消费线程死亡,通话录音未上送业务平台
java·redis
2301_32241428046 小时前
基于SpringBoot+Vue的休闲零食超市管理系统的设计与实现59004-计算机毕设 原创(免费领源码+部署教程+开发环境)
java·vue.js·spring boot·redis·mysql·决策树·推荐算法
金銀銅鐵8 小时前
[Java] 用 GUI 展示 EnumSet 增减元素时的内部变化(上)
java·后端·ai编程
福建佰胜张工9 小时前
SMC 薄型气爪 MHF2-16D1 深度解析:平行开闭、低剖面结构在自动化装配中的选型与落地
java·服务器·安全·自动化
念何架构之路9 小时前
go-grpc设计思想
开发语言·后端·golang
catoop9 小时前
API Key 设计与安全生成方案:结构拆解、数据库设计与 Java 实现
java·数据库·安全
system_error_x9 小时前
SpringBoot 集成 Thymeleaf 简洁入门
java·spring boot·后端
驭渊的小故事10 小时前
Spring AI Alibaba 实战:从maven依赖治理到声明式工具调用
java·人工智能·spring
Elastic 中国社区官方博客10 小时前
我们如何通过 70 多个拉取请求,将 OpenTelemetry Java agent 迁移到 invokedynamic
java·大数据·开发语言·elasticsearch·搜索引擎·全文检索·es
tyn18810 小时前
记录本地安装nacos服务的方法及异常
java·开发语言