SpringBoot 之向异步线程传递上下文

SpringBoot项目中,经常使用@Async来开启一个子线程来完成异步操作。使用异步线程的好处是其执行不影响主线程。比如用户注册成功后,发送一封欢迎邮件,在异步线程中发送邮件,即使出了问题,也不会影响到当前用户的注册体验。

有时在子线程中需要主线程的上下文,可通过如下步骤实现:

1)启用异步功能

在启动类或异步配置类上添加@EnableAsync注解

@EnableAsync
@SpringBootApplication
public class Application {}

2)配置异步

新建一个配置类,实现AsyncConfigurer接口,并重写getAsyncExecutor方法

@Configuration
public class AsyncConfig implements AsyncConfigurer {

    @Override
    public Executor getAsyncExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(10);
        executor.setMaxPoolSize(50);
        executor.setThreadNamePrefix("async-pool-");
        // 这一步是关键,异步任务装饰器
        executor.setTaskDecorator(new MyContextDecorator());
        executor.initialize();
        return executor;
    }
}

3)任务装饰器

新建一个类,实现TaskDecorator接口,并重写decorate方法

public class MyContextDecorator implements TaskDecorator {
    @Override
    public Runnable decorate(@Nonnull Runnable runnable) {
		// 获取主线程中的请求信息
       RequestAttributes attributes = RequestContextHolder.getRequestAttributes();
        return () -> {
            try {
              	// 将主线程的请求信息,设置到子线程中
              	RequestContextHolder.setRequestAttributes(attributes);
             	// 执行子线程
                runnable.run();
            } finally {
            	// 线程结束,清空这些信息,否则可能造成内存泄漏
                RequestContextHolder.resetRequestAttributes();
            }
        };
    }

到此,通过@Async开启的子线程,就可以正常拿到父线程中的Request信息了。

备注:通常我们可以把需要的信息封装到任务中,在需要的地方再取出来。

原理探究

Spring给我们预留了任务装饰器TaskDecorator,通过它可以对线程做一些功能增强。在ThreadPoolTaskExecutor的源码中,initializeExecutor 方法中根据是否有装饰器创建的ThreadPoolExecutor不一样。

ThreadPoolExecutor是Java并发包中的用于创建线程池,ThreadPoolTaskExecutor是Spring框架对ThreadPoolExecutor的封装。

protected ExecutorService initializeExecutor(
			ThreadFactory threadFactory, RejectedExecutionHandler rejectedExecutionHandler) {
		
    BlockingQueue<Runnable> queue = createQueue(this.queueCapacity);
		ThreadPoolExecutor executor;
		if (this.taskDecorator != null) {
              //如果有装饰器,就对任务先装饰,再执行装饰后的任务
			  executor = new ThreadPoolExecutor(this.corePoolSize, this.maxPoolSize, this.keepAliveSeconds, TimeUnit.SECONDS,
					queue, threadFactory, rejectedExecutionHandler) {
				@Override
				public void execute(Runnable command) {
					Runnable decorated = taskDecorator.decorate(command);
					if (decorated != command) {
						decoratedTaskMap.put(decorated, command);
					}
					super.execute(decorated);
				}
			};
		}
		else {
			executor = new ThreadPoolExecutor(
					this.corePoolSize, this.maxPoolSize, this.keepAliveSeconds, TimeUnit.SECONDS,
					queue, threadFactory, rejectedExecutionHandler);
		}
		if (this.allowCoreThreadTimeOut) {
			executor.allowCoreThreadTimeOut(true);
		}
		this.threadPoolExecutor = executor;
		return executor;
	}

注意事项

在 @Async 异步线程使用过程中,需要注意的是以下的用法会使 @Async 失效:

  1. 异步方法使用 static 修饰;
  2. 异步类没有使用 @Component 注解(或其他注解)导致 Spring 无法扫描到异步类;
  3. 异步方法不能与被调用的异步方法在同一个类中;
  4. 类中需要使用 @Autowired 或 @Resource 等注解自动注入,不能手动 new 对象;
  5. 如果使用 Spring Boot 框架必须在启动类(或配置类)上增加 @EnableAsync 注解。

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值