上一篇文章说到,之前使用了@Async注解,子线程无法获取到上下文信息,导致流量无法打到灰度,然后改成 线程池的方式,每次调用异步调用的时候都手动透传 上下文(硬编码)解决了问题。

后面查阅了资料,找到了方案不用每次硬编码,来上下文透传数据了。

方案一:

继承线程池,重写相应的方法,透传上下文。

方案二:(推荐)

线程池ThreadPoolTaskExecutor,有一个TaskDecorator装饰器,实现这个接口,透传上下文。

方案一:继承线程池,重写相应的方法,透传上下文。

1、ThreadPoolTaskExecutor spring封装的线程池

ThreadPoolTaskExecutor 线程池代码如下:

    @Bean(ExecutorConstant.simpleExecutor_3)
public Executor asyncExecutor3() {
MyThreadPoolTaskExecutor taskExecutor = new MyThreadPoolTaskExecutor();
taskExecutor.setCorePoolSize(corePoolSize);
taskExecutor.setMaxPoolSize(maxPoolSize);
taskExecutor.setQueueCapacity(queueCapacity);
taskExecutor.setThreadNamePrefix(threadNamePrefix_3);
taskExecutor.initialize();
return taskExecutor;
} //------- 继承父类 重写对应的方法 start
class MyCallable<T> implements Callable<T> {
private Callable<T> task;
private RequestAttributes context; public MyCallable(Callable<T> task, RequestAttributes context) {
this.task = task;
this.context = context;
} @Override
public T call() throws Exception {
if (context != null) {
RequestContextHolder.setRequestAttributes(context);
} try {
return task.call();
} finally {
RequestContextHolder.resetRequestAttributes();
}
}
}
class MyThreadPoolTaskExecutor extends ThreadPoolTaskExecutor{ @Override
public <T> Future<T> submit(Callable<T> task) {
return super.submit(new MyCallable(task, RequestContextHolder.currentRequestAttributes()));
} @Override
public <T> ListenableFuture<T> submitListenable(Callable<T> task) {
return super.submitListenable(new MyCallable(task, RequestContextHolder.currentRequestAttributes()));
}
}
//------- 继承父类 重写对应的方法 end

1、MyCallable是继承Callable,创建MyCallable对象的时候已经把Attributes对象赋值给属性context了(创建MyCallable对象的时候因为实在当前主线程创建的,所以是能获取到请求的Attributes),在执行call方法前,先执行了RequestContextHolder.setRequestAttributes(context); 【把这个MyCallable对象的属性context 设置到setRequestAttributes中】 所以在执行具体业务时,当前线程(子线程)就能取得主线程的Attributes

2、MyThreadPoolTaskExecutor类是继承了ThreadPoolTaskExecutor 重写了submit和submitListenable方法

为什么是重写submit和submitListenable这两个方法了?

@Async AOP源码的方法位置是在:AsyncExecutionInterceptor.invoke

doSubmit方法能看出来

无返回值调用的是线程池方法:submit()

有返回值,根据不同的返回类型也知道:

  1. 返回值类型是:Future.class 调用的是方法:submit()
  2. 返回值类型是:ListenableFuture.class 调用的方法是:submitListenable(task)
  3. 返回值类型是:CompletableFuture.class调用的是CompletableFuture.supplyAsync这个在异步注解中暂时用不上的,就不考虑重写了。
public Object invoke(final MethodInvocation invocation) throws Throwable {
Class<?> targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null);
Method specificMethod = ClassUtils.getMostSpecificMethod(invocation.getMethod(), targetClass);
final Method userDeclaredMethod = BridgeMethodResolver.findBridgedMethod(specificMethod); AsyncTaskExecutor executor = determineAsyncExecutor(userDeclaredMethod);
if (executor == null) {
throw new IllegalStateException(
"No executor specified and no default executor set on AsyncExecutionInterceptor either");
} Callable<Object> task = () -> {
try {
Object result = invocation.proceed();
if (result instanceof Future) {
return ((Future<?>) result).get();
}
}
catch (ExecutionException ex) {
handleError(ex.getCause(), userDeclaredMethod, invocation.getArguments());
}
catch (Throwable ex) {
handleError(ex, userDeclaredMethod, invocation.getArguments());
}
return null;
}; return doSubmit(task, executor, invocation.getMethod().getReturnType());
} @Nullable
protected Object doSubmit(Callable<Object> task, AsyncTaskExecutor executor, Class<?> returnType) {
if (CompletableFuture.class.isAssignableFrom(returnType)) {
return CompletableFuture.supplyAsync(() -> {
try {
return task.call();
}
catch (Throwable ex) {
throw new CompletionException(ex);
}
}, executor);
}
else if (ListenableFuture.class.isAssignableFrom(returnType)) {
return ((AsyncListenableTaskExecutor) executor).submitListenable(task);
}
else if (Future.class.isAssignableFrom(returnType)) {
return executor.submit(task);
}
else {
executor.submit(task);
return null;
}
}

2、ThreadPoolExecutor 原生线程池

ThreadPoolExecutor线程池代码如下:

//------- ThreadPoolExecutor 继承父类 重写对应的方法 start
class MyRunnable implements Runnable {
private Runnable runnable;
private RequestAttributes context; public MyRunnable(Runnable runnable, RequestAttributes context) {
this.runnable = runnable;
this.context = context;
} @Override
public void run() {
if (context != null) {
RequestContextHolder.setRequestAttributes(context);
}
try {
runnable.run();
} finally {
RequestContextHolder.resetRequestAttributes();
}
}
} class MyThreadPoolExecutor extends ThreadPoolExecutor{
@Override
public void execute(Runnable command) {
if(!(command instanceof MyRunnable)){
command = new MyRunnable(command,RequestContextHolder.currentRequestAttributes())
}
super.execute(command);
} public MyThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) {
super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
} public MyThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory) {
super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory);
} public MyThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, RejectedExecutionHandler handler) {
super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, handler);
} public MyThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) {
super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, handler);
}
}
//------- ThreadPoolExecutor 继承父类 重写对应的方法 end

像ThreadPoolExecutor主要重写execute方法,在启动新线程的时候先把Attributes取到放到MyRunnable对象的一个属性中,MyRunnable在具体执行run方法的时候,把属性Attributes赋值到子线程中,当run方法执行完了在把Attributes清空掉。

为什么只要重写了execute方法就可以了?

ThreadPoolExecutor大家都知道主要是由submit和execute方法来执行的。

看ThreadPoolExecutor类的submit具体执行方法是由父类AbstractExecutorService#submit来实现。

具体代码在下面贴出来了,可以看到submit实际上最后调用的还是execute方法,所以我们重写execute方法就好了。

submit方法路径及源码:

java.util.concurrent.AbstractExecutorService#submit(java.lang.Runnable)

    public Future<?> submit(Runnable task) {
if (task == null) throw new NullPointerException();
RunnableFuture<Void> ftask = newTaskFor(task, null);
execute(ftask);
return ftask;
} /**
* @throws RejectedExecutionException {@inheritDoc}
* @throws NullPointerException {@inheritDoc}
*/
public <T> Future<T> submit(Runnable task, T result) {
if (task == null) throw new NullPointerException();
RunnableFuture<T> ftask = newTaskFor(task, result);
execute(ftask);
return ftask;
} /**
* @throws RejectedExecutionException {@inheritDoc}
* @throws NullPointerException {@inheritDoc}
*/
public <T> Future<T> submit(Callable<T> task) {
if (task == null) throw new NullPointerException();
RunnableFuture<T> ftask = newTaskFor(task);
execute(ftask);
return ftask;
}

方案二:(推荐)

ThreadPoolTaskExecutor线程池

实现TaskDecorator接口,把实现类设置到taskExecutor.setTaskDecorator(new MyTaskDecorator());

    //------- 实现TaskDecorator 接口 start

    @Bean(ExecutorConstant.simpleExecutor_4)
public Executor asyncExecutor4() {
MyThreadPoolTaskExecutor taskExecutor = new MyThreadPoolTaskExecutor();
taskExecutor.setCorePoolSize(corePoolSize);
taskExecutor.setMaxPoolSize(maxPoolSize);
taskExecutor.setQueueCapacity(queueCapacity);
taskExecutor.setThreadNamePrefix(threadNamePrefix_4);
taskExecutor.setTaskDecorator(new MyTaskDecorator());
taskExecutor.initialize();
return taskExecutor;
} class MyTaskDecorator implements TaskDecorator{ @Override
public Runnable decorate(Runnable runnable) {
try {
RequestAttributes attributes = RequestContextHolder.getRequestAttributes();
return () -> {
try {
RequestContextHolder.setRequestAttributes(attributes);
runnable.run();
} finally {
RequestContextHolder.resetRequestAttributes();
}
};
} catch (IllegalStateException e) {
return runnable;
}
}
}
//------- 实现TaskDecorator 接口 end

为什么设置了setTaskDecorator就能实现透传数据了?

主要还是看taskExecutor.initialize()方法,主要是重写了ThreadPoolExecutor的execute方法,用装饰器模式 增强了Runnable接口,源代码如下:

	@Nullable
private ThreadPoolExecutor threadPoolExecutor; //初始化方法
public void initialize() {
if (logger.isDebugEnabled()) {
logger.debug("Initializing ExecutorService" + (this.beanName != null ? " '" + this.beanName + "'" : ""));
}
if (!this.threadNamePrefixSet && this.beanName != null) {
setThreadNamePrefix(this.beanName + "-");
}
this.executor = initializeExecutor(this.threadFactory, this.rejectedExecutionHandler);
} @Override
protected ExecutorService initializeExecutor(
ThreadFactory threadFactory, RejectedExecutionHandler rejectedExecutionHandler) { BlockingQueue<Runnable> queue = createQueue(this.queueCapacity); ThreadPoolExecutor executor; //判断是否设置了,taskDecorator装饰器
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接口
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);
}
//把初始化好的ThreadPoolExecutor线程池赋值给 当前类属性threadPoolExecutor
this.threadPoolExecutor = executor;
return executor;
}

总结

无论是方案1还是方案2,原理都是先在当前线程获取到Attributes,然后把Attributes赋值到Runnable的一个属性中,在起一个子线程后,具体执行run方法的时候,把Attributes设置给当子线程,当run方法执行完了,在清空Attributes。

方案2实现比较优雅,所以推荐使用它。

工作没多久的时候觉得spring的使用很麻烦,但是工作久了慢慢发现spring一些小细节、设计模式运用的非常巧妙,很容易解决遇到的问题,只能说spring 。

我已经把上述代码例子放到gitee了,大家感兴趣可以clone 传送门~

spring boot @Async异步注解上下文透传的更多相关文章

  1. Spring Boot Async异步执行

    异步调用就是不用等待结果的返回就执行后面的逻辑,同步调用则需要等带结果再执行后面的逻辑. 通常我们使用异步操作都会去创建一个线程执行一段逻辑,然后把这个线程丢到线程池中去执行,代码如下: Execut ...

  2. Spring Boot @Async 异步任务执行

    1.任务执行和调度 Spring用TaskExecutor和TaskScheduler接口提供了异步执行和调度任务的抽象. Spring的TaskExecutor和java.util.concurre ...

  3. spring boot使用@Async异步注解

    1.java的大部分接口的方法都是串行执行的,但是有些业务场景是不需要同步返回结果的,可以把结果直接返回,具体业务异步执行,也有些业务接口是需要并行获取数据,最后把数据聚合在统一返回给前端. 通常我们 ...

  4. (32)Spring Boot使用@SpringBootApplication注解,从零开始学Spring Boot

    [来也匆匆,去也匆匆,在此留下您的脚印吧,转发点赞评论] 如果看了我之前的文章,这个节你就可以忽略了,这个是针对一些刚入门的选手存在的困惑进行写的一篇文章. 很多Spring Boot开发者总是使用 ...

  5. Spring boot 配置异步处理执行器

    示例如下: 1. 新建Maven 项目 async-executor 2.pom.xml <project xmlns="http://maven.apache.org/POM/4.0 ...

  6. Spring Boot 二十个注解

    Spring Boot 二十个注解 占据无力拥有的东西是一种悲哀. Cold on the outside passionate on the insede. 背景:Spring Boot 注解的强大 ...

  7. Spring Boot中@Scheduled注解的使用方法

    Spring Boot中@Scheduled注解的使用方法 一.定时任务注解为@Scheduled,使用方式举例如下 //定义一个按时间执行的定时任务,在每天16:00执行一次. @Scheduled ...

  8. @Async异步注解与SpringBoot结合使用

    当你在service层需要启动异步线程去执行某些分支任务,又不希望显式使用Thread等线程相关类,只想专注于实现业务逻辑代码开发,可以使用@Async异步注解. 1. 使用@Async 异步注解 C ...

  9. spring boot下MultipartHttpServletRequest如何提高上传文件大小的默认值

    前言: 上传下载功能算是一个非常常见的功能,如果使用MultipartHttpServletRequest来做上传功能. 不配置上传大小的话,默认是2M.在有些场景,这个肯定不能满足条件. 上传代码: ...

随机推荐

  1. SpringBoot系列——事件发布与监听

    前言 日常开发中,我们经常会碰到这样的业务场景:用户注册,注册成功后需要发送邮箱.短信提示用户,通常我们都是这样写: /** * 用户注册 */ @GetMapping("/userRegi ...

  2. 对Linux系统内核版本稳定性测试介绍

    对Linux系统内核版本稳定性测试介绍 在对 Linux 内核版本稳定性的测试中,需要明确地声明并证明为什么版本是稳定的或者是不稳定的. 然而还没有被证明和证实当前现有的系统范围内的压力测试可以测试 ...

  3. linux操作系统优化系列-RAID不同阵列模式的选择

    背景 笔者所在的某通信运营商某大数据项目由于应用面临瓶颈需要扩充服务器设备,当初上这个项目的时候,服务器上线前的工作(配置raid,安装操作系统,Infiniband网络调试,系统漏洞安全加固)都是我 ...

  4. CentOS 7 调整home大小

    把/home内容备份,然后将/home文件系统所在的逻辑卷删除,扩大/root文件系统,新建/home: tar cvf /tmp/home.tar /home #备份/home umount /ho ...

  5. IT菜鸟之计算机硬件

    现在的人们几乎无时无刻都会碰到计算机!不管是桌面计算机.笔记本电脑.平板计算机.智能型手机等等,这些东西都算计算机.虽然接触的怎么多,但是,我们一般很少会专门了解计算机内部的构成,下面就是自己在听课结 ...

  6. 063.Python前端Django分页器

    Django的分页器 1 前期准备 创建一个数据库,用于存放数据 mysql> create database pager default charset=utf8; mysql> use ...

  7. 基于Centos7.4搭建prometheus+grafana+altertManger监控Spring Boot微服务(docker版)

    目的:给我们项目的微服务应用都加上监控告警.在这之前你需要将 Spring Boot Actuator引入 本章主要介绍 如何集成监控告警系统Prometheus 和图形化界面Grafana 如何自定 ...

  8. STM32标准外设库中USE_STDPERIPH_DRIVER, STM32F10X_MD的含义

    在项目中使用stm32标准外设库(STM32F10x Standard Peripherals Library)的时候,我们会在项目的选项中预定义两个宏定义:USE_STDPERIPH_DRIVER, ...

  9. 将.netcore5.0(.net5)部署在Ubuntu的docker容器中

    环境:     宿主机:winows 10 家庭版     虚拟机管理软件:Hyper-V     虚拟机系统:Ubuntu 20.10     Docker版本:Docker CE 20.10.2 ...

  10. python操作mongodb根据_id查询数据的实现方法

    python操作mongodb根据_id查询数据的实现方法   python操作mongodb根据_id查询数据的实现方法,实例分析了Python根据pymongo不同版本操作ObjectId的技巧, ...