在进行微服务调用的时候,不管是服务之间(A 服务调用 B 服务)还是服务内部调用(服务 A 某个方法进行里有异步)都存在异步调用。但是 Nepxion Discovery
在进行参数传递的时候很多情况是使用的是基于 ThreadLocal
。
ThreadLocal
的作用是提供线程内的局部变量,在多线程环境下访问时能保证各个线程内的ThreadLocal
变量各自独立。在异步场景下,由于出现线程切换的问题,例如主线程切换到子线程,会导致线程ThreadLocal
上下文丢失。DiscoveryAgent
通过Java Agent
方式解决这些痛点。它基本涵盖所有 Java 框架的异步场景,解决如下 7 个异步场景下丢失线程 ThreadLocal
上下文的问题:
插件获取方式有两种方式:
discovery-agent-starter-${discovery.version}.jar
为 Agent 引导启动程序,JVM 启动时进行加载;discovery-agent/plugin
目录包含 discovery-agent-starter-plugin-strategy-${discovery.version}.jar
为 Nepxion Discovery
自带的实现方案,业务系统可以自定义plugin,解决业务自己定义的上下文跨线程传递-javaagent:/discovery-agent/discovery-agent-starter-${discovery.agent.version}.jar -Dthread.scan.packages=com.abc;com.xyz
例如:
-javaagent:C:/opt/discovery-agent/discovery-agent-starter-${discovery.agent.version}.jar -Dthread.scan.packages=org.springframework.aop.interceptor;com.netflix.hystrix;com.nepxion.discovery.guide.service.feign
参数说明:
/discovery-agent
:Agent 所在的目录,需要对应到实际的目录上-Dthread.scan.packages
:Runnable
,Callable
对象所在的扫描目录,该目录下的 Runnable
,Callable
对象都会被装饰。该目录最好精细和准确,这样可以减少被装饰的对象数,提高性能,目录如果有多个,用 “;”
分隔。该参数只作用于服务侧,网关侧不需要加。参数定义为:
@Async
场景下的扫描目录对应为org.springframework.aop.interceptor
Hystrix
线程池隔离场景下的扫描目录对应为com.netflix.hystrix
Runnable
,Callable
对象所在类的目录“;”
,可能会在某些操作系统中无法被识别,请用""进行引入,例如,-Dthread.scan.packages="com.abc;com.xyz"
-Dthread.request.decorator.enabled
:异步调用场景下在服务端的 Request
请求的装饰,当主线程先于子线程执行完的时候,Request
会被 Destory
,导致 Header
仍旧拿不到,开启装饰,就可以确保拿到。默认为开启,根据实践经验,大多数场景下,需要开启该开关-Dthread.mdc.enabled
:SLF4J MDC
日志输出到异步子线程。默认关闭,如果需要,则开启该开关ThreadLocal
的跨线程传递plugin
目录为放置需要在线程切换时进行 ThreadLocal
传递的自定义插件。业务自定义插件开发完后,放入到 plugin
目录下即可具体步骤介绍,如下
① SDK侧工作
新建 ThreadLocal 上下文类
MyContext.java
public class MyContext {
private static final ThreadLocal<MyContext> THREAD_LOCAL = new ThreadLocal<MyContext>() {
@Override
protected MyContext initialValue() {
return new MyContext();
}
};
public static MyContext getCurrentContext() {
return THREAD_LOCAL.get();
}
public static void clearCurrentContext() {
THREAD_LOCAL.remove();
}
private Map<String, String> attributes = new HashMap<>();
public Map<String, String> getAttributes() {
return attributes;
}
public void setAttributes(Map<String, String> attributes) {
this.attributes = attributes;
}
}
② Agent侧工作
新建一个模块,引入如下依赖
<dependency>
<groupId>com.nepxion</groupId>
<artifactId>discovery-agent-starter</artifactId>
<version>${discovery.agent.version}</version>
<scope>provided</scope>
</dependency>
新建一个 ThreadLocalHook
类继承 AbstractThreadLocalHook
:
MyContextHook.java
public class MyContextHook extends AbstractThreadLocalHook {
@Override
public Object create() {
// 从主线程的ThreadLocal里获取并返回上下文对象
return MyContext.getCurrentContext().getAttributes();
}
@Override
public void before(Object object) {
// 把create方法里获取到的上下文对象放置到子线程的ThreadLocal里
if (object instanceof Map) {
MyContext.getCurrentContext().setAttributes((Map<String, String>) object);
}
}
@Override
public void after() {
// 线程结束,销毁上下文对象
MyContext.clearCurrentContext();
}
}
新建一个 Plugin
类继承 AbstractPlugin
:
MyContextPlugin.java
public class MyContextPlugin extends AbstractPlugin {
private Boolean threadMyPluginEnabled = Boolean.valueOf(System.getProperty("thread.myplugin.enabled", "false"));
@Override
protected String getMatcherClassName() {
// 返回存储ThreadLocal对象的类名,由于插件是可以插拔的,所以必须是字符串形式,不允许是显式引入类
return "com.nepxion.discovery.example.sdk.MyContext";
}
@Override
protected String getHookClassName() {
// 返回ThreadLocalHook类名
return MyContextHook.class.getName();
}
@Override
protected boolean isEnabled() {
// 通过外部-Dthread.myplugin.enabled=true/false的运行参数来控制当前Plugin是否生效。该方法在父类中定义的返回值为true,即缺省为生效
return threadMyPluginEnabled;
}
}
定义SPI扩展,在 src/main/resources/META-INF/services
目录下定义 SPI
文件,名称为固定如下格式:
com.nepxion.discovery.agent.plugin.Plugin
内容为 Plugin
类的全路径:
com.nepxion.discovery.example.agent.MyContextPlugin
执行 Maven
编译,把编译后的包放在 discovery-agent/plugin
目录下:
给服务增加启动参数并启动,如下:
-javaagent:C:/opt/discovery-agent/discovery-agent-starter-${discovery.agent.version}.jar -Dthread.scan.packages=com.nepxion.discovery.example.application -Dthread.myplugin.enabled=true
③ Application侧工作
执行 MyApplication
,它模拟在主线程 ThreadLocal
放入 Map 数据,子线程通过 DiscoveryAgent
获取到该 Map 数据,并打印出来:
MyApplication.java
@SpringBootApplication
@RestController
public class MyApplication {
private static final Logger LOG = LoggerFactory.getLogger(MyApplication.class);
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
invoke();
}
public static void invoke() {
RestTemplate restTemplate = new RestTemplate();
for (int i = 1; i <= 10; i++) {
restTemplate.getForEntity("http://localhost:8080/index/" + i, String.class).getBody();
}
}
@GetMapping("/index/{value}")
public String index(@PathVariable(value = "value") String value) throws InterruptedException {
Map<String, String> attributes = new HashMap<String, String>();
attributes.put(value, "MyContext");
MyContext.getCurrentContext().setAttributes(attributes);
LOG.info("【主】线程ThreadLocal:{}", MyContext.getCurrentContext().getAttributes());
new Thread(new Runnable() {
@Override
public void run() {
LOG.info("【子】线程ThreadLocal:{}", MyContext.getCurrentContext().getAttributes());
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
LOG.info("Sleep 5秒之后,【子】线程ThreadLocal:{} ", MyContext.getCurrentContext().getAttributes());
}
}).start();
return "";
}
}
输出结果,如下:
2020-11-09 00:08:14.330 INFO 16588 --- [nio-8080-exec-1] c.n.d.example.application.MyApplication : 【主】线程ThreadLocal:{1=MyContext}
2020-11-09 00:08:14.381 INFO 16588 --- [ Thread-4] c.n.d.example.application.MyApplication : 【子】线程ThreadLocal:{1=MyContext}
2020-11-09 00:08:14.402 INFO 16588 --- [nio-8080-exec-2] c.n.d.example.application.MyApplication : 【主】线程ThreadLocal:{2=MyContext}
2020-11-09 00:08:14.403 INFO 16588 --- [ Thread-5] c.n.d.example.application.MyApplication : 【子】线程ThreadLocal:{2=MyContext}
2020-11-09 00:08:14.405 INFO 16588 --- [nio-8080-exec-3] c.n.d.example.application.MyApplication : 【主】线程ThreadLocal:{3=MyContext}
2020-11-09 00:08:14.406 INFO 16588 --- [ Thread-6] c.n.d.example.application.MyApplication : 【子】线程ThreadLocal:{3=MyContext}
2020-11-09 00:08:14.414 INFO 16588 --- [nio-8080-exec-4] c.n.d.example.application.MyApplication : 【主】线程ThreadLocal:{4=MyContext}
2020-11-09 00:08:14.414 INFO 16588 --- [ Thread-7] c.n.d.example.application.MyApplication : 【子】线程ThreadLocal:{4=MyContext}
2020-11-09 00:08:14.417 INFO 16588 --- [nio-8080-exec-5] c.n.d.example.application.MyApplication : 【主】线程ThreadLocal:{5=MyContext}
2020-11-09 00:08:14.418 INFO 16588 --- [ Thread-8] c.n.d.example.application.MyApplication : 【子】线程ThreadLocal:{5=MyContext}
2020-11-09 00:08:14.421 INFO 16588 --- [nio-8080-exec-6] c.n.d.example.application.MyApplication : 【主】线程ThreadLocal:{6=MyContext}
2020-11-09 00:08:14.422 INFO 16588 --- [ Thread-9] c.n.d.example.application.MyApplication : 【子】线程ThreadLocal:{6=MyContext}
2020-11-09 00:08:14.424 INFO 16588 --- [nio-8080-exec-7] c.n.d.example.application.MyApplication : 【主】线程ThreadLocal:{7=MyContext}
2020-11-09 00:08:14.425 INFO 16588 --- [ Thread-10] c.n.d.example.application.MyApplication : 【子】线程ThreadLocal:{7=MyContext}
2020-11-09 00:08:14.427 INFO 16588 --- [nio-8080-exec-8] c.n.d.example.application.MyApplication : 【主】线程ThreadLocal:{8=MyContext}
2020-11-09 00:08:14.428 INFO 16588 --- [ Thread-11] c.n.d.example.application.MyApplication : 【子】线程ThreadLocal:{8=MyContext}
2020-11-09 00:08:14.430 INFO 16588 --- [nio-8080-exec-9] c.n.d.example.application.MyApplication : 【主】线程ThreadLocal:{9=MyContext}
2020-11-09 00:08:14.431 INFO 16588 --- [ Thread-12] c.n.d.example.application.MyApplication : 【子】线程ThreadLocal:{9=MyContext}
2020-11-09 00:08:14.433 INFO 16588 --- [io-8080-exec-10] c.n.d.example.application.MyApplication : 【主】线程ThreadLocal:{10=MyContext}
2020-11-09 00:08:14.434 INFO 16588 --- [ Thread-13] c.n.d.example.application.MyApplication : 【子】线程ThreadLocal:{10=MyContext}
2020-11-09 00:08:19.382 INFO 16588 --- [ Thread-4] c.n.d.example.application.MyApplication : Sleep 5秒之后,【子】线程ThreadLocal:{1=MyContext}
2020-11-09 00:08:19.404 INFO 16588 --- [ Thread-5] c.n.d.example.application.MyApplication : Sleep 5秒之后,【子】线程ThreadLocal:{2=MyContext}
2020-11-09 00:08:19.406 INFO 16588 --- [ Thread-6] c.n.d.example.application.MyApplication : Sleep 5秒之后,【子】线程ThreadLocal:{3=MyContext}
2020-11-09 00:08:19.416 INFO 16588 --- [ Thread-7] c.n.d.example.application.MyApplication : Sleep 5秒之后,【子】线程ThreadLocal:{4=MyContext}
2020-11-09 00:08:19.418 INFO 16588 --- [ Thread-8] c.n.d.example.application.MyApplication : Sleep 5秒之后,【子】线程ThreadLocal:{5=MyContext}
2020-11-09 00:08:19.422 INFO 16588 --- [ Thread-9] c.n.d.example.application.MyApplication : Sleep 5秒之后,【子】线程ThreadLocal:{6=MyContext}
2020-11-09 00:08:19.425 INFO 16588 --- [ Thread-10] c.n.d.example.application.MyApplication : Sleep 5秒之后,【子】线程ThreadLocal:{7=MyContext}
2020-11-09 00:08:19.428 INFO 16588 --- [ Thread-11] c.n.d.example.application.MyApplication : Sleep 5秒之后,【子】线程ThreadLocal:{8=MyContext}
2020-11-09 00:08:19.432 INFO 16588 --- [ Thread-12] c.n.d.example.application.MyApplication : Sleep 5秒之后,【子】线程ThreadLocal:{9=MyContext}
2020-11-09 00:08:19.434 INFO 16588 --- [ Thread-13] c.n.d.example.application.MyApplication : Sleep 5秒之后,【子】线程ThreadLocal:{10=MyContext}
如果不加异步 Agent
,则输出结果,如下,可以发现在子线程中 ThreadLocal
上下文全部都丢失:
2020-11-09 00:01:40.133 INFO 16692 --- [nio-8080-exec-1] c.n.d.example.application.MyApplication : 【主】线程ThreadLocal:{1=MyContext}
2020-11-09 00:01:40.135 INFO 16692 --- [ Thread-8] c.n.d.example.application.MyApplication : 【子】线程ThreadLocal:{}
2020-11-09 00:01:40.158 INFO 16692 --- [nio-8080-exec-2] c.n.d.example.application.MyApplication : 【主】线程ThreadLocal:{2=MyContext}
2020-11-09 00:01:40.159 INFO 16692 --- [ Thread-9] c.n.d.example.application.MyApplication : 【子】线程ThreadLocal:{}
2020-11-09 00:01:40.162 INFO 16692 --- [nio-8080-exec-3] c.n.d.example.application.MyApplication : 【主】线程ThreadLocal:{3=MyContext}
2020-11-09 00:01:40.163 INFO 16692 --- [ Thread-10] c.n.d.example.application.MyApplication : 【子】线程ThreadLocal:{}
2020-11-09 00:01:40.170 INFO 16692 --- [nio-8080-exec-5] c.n.d.example.application.MyApplication : 【主】线程ThreadLocal:{4=MyContext}
2020-11-09 00:01:40.170 INFO 16692 --- [ Thread-11] c.n.d.example.application.MyApplication : 【子】线程ThreadLocal:{}
2020-11-09 00:01:40.173 INFO 16692 --- [nio-8080-exec-4] c.n.d.example.application.MyApplication : 【主】线程ThreadLocal:{5=MyContext}
2020-11-09 00:01:40.174 INFO 16692 --- [ Thread-12] c.n.d.example.application.MyApplication : 【子】线程ThreadLocal:{}
2020-11-09 00:01:40.176 INFO 16692 --- [nio-8080-exec-6] c.n.d.example.application.MyApplication : 【主】线程ThreadLocal:{6=MyContext}
2020-11-09 00:01:40.177 INFO 16692 --- [ Thread-13] c.n.d.example.application.MyApplication : 【子】线程ThreadLocal:{}
2020-11-09 00:01:40.179 INFO 16692 --- [nio-8080-exec-8] c.n.d.example.application.MyApplication : 【主】线程ThreadLocal:{7=MyContext}
2020-11-09 00:01:40.180 INFO 16692 --- [ Thread-14] c.n.d.example.application.MyApplication : 【子】线程ThreadLocal:{}
2020-11-09 00:01:40.182 INFO 16692 --- [nio-8080-exec-7] c.n.d.example.application.MyApplication : 【主】线程ThreadLocal:{8=MyContext}
2020-11-09 00:01:40.182 INFO 16692 --- [ Thread-15] c.n.d.example.application.MyApplication : 【子】线程ThreadLocal:{}
2020-11-09 00:01:40.185 INFO 16692 --- [nio-8080-exec-9] c.n.d.example.application.MyApplication : 【主】线程ThreadLocal:{9=MyContext}
2020-11-09 00:01:40.186 INFO 16692 --- [ Thread-16] c.n.d.example.application.MyApplication : 【子】线程ThreadLocal:{}
2020-11-09 00:01:40.188 INFO 16692 --- [io-8080-exec-10] c.n.d.example.application.MyApplication : 【主】线程ThreadLocal:{10=MyContext}
2020-11-09 00:01:40.189 INFO 16692 --- [ Thread-17] c.n.d.example.application.MyApplication : 【子】线程ThreadLocal:{}
2020-11-09 00:01:45.136 INFO 16692 --- [ Thread-8] c.n.d.example.application.MyApplication : Sleep 5秒之后,【子】线程ThreadLocal:{}
2020-11-09 00:01:45.160 INFO 16692 --- [ Thread-9] c.n.d.example.application.MyApplication : Sleep 5秒之后,【子】线程ThreadLocal:{}
2020-11-09 00:01:45.163 INFO 16692 --- [ Thread-10] c.n.d.example.application.MyApplication : Sleep 5秒之后,【子】线程ThreadLocal:{}
2020-11-09 00:01:45.171 INFO 16692 --- [ Thread-11] c.n.d.example.application.MyApplication : Sleep 5秒之后,【子】线程ThreadLocal:{}
2020-11-09 00:01:45.174 INFO 16692 --- [ Thread-12] c.n.d.example.application.MyApplication : Sleep 5秒之后,【子】线程ThreadLocal:{}
2020-11-09 00:01:45.177 INFO 16692 --- [ Thread-13] c.n.d.example.application.MyApplication : Sleep 5秒之后,【子】线程ThreadLocal:{}
2020-11-09 00:01:45.181 INFO 16692 --- [ Thread-14] c.n.d.example.application.MyApplication : Sleep 5秒之后,【子】线程ThreadLocal:{}
2020-11-09 00:01:45.183 INFO 16692 --- [ Thread-15] c.n.d.example.application.MyApplication : Sleep 5秒之后,【子】线程ThreadLocal:{}
2020-11-09 00:01:45.187 INFO 16692 --- [ Thread-16] c.n.d.example.application.MyApplication : Sleep 5秒之后,【子】线程ThreadLocal:{}
2020-11-09 00:01:45.190 INFO 16692 --- [ Thread-17] c.n.d.example.application.MyApplication : Sleep 5秒之后,【子】线程ThreadLocal:{}
下面我们来分析一下 DiscoveryAgent
是如何实现的,通过上面的示例,可以得出如果我们需要自定义的插件扩展有以下几个比较重要的关键点:
ThreadLocalHook
类继承 AbstractThreadLocalHook
,完成自己的跨线程变量共享。 AbstractThreadLocalHook
这个抽象类实现了 ThreadLocalHook
这个接口。通过名称我们可以大胆猜测 ThreadLocalHook
是属于 DiscoveryAgent
的钩子程序,我们在分析源码的时候需要注意ThreadLocalHook
这个接口Plugin
类继承 AbstractPlugin
: AbstractPlugin
这个抽象了实现 Plugin
这个接口。从它的名字我们就可以猜测这个类是插件增强类。install
方法把添加当前插件注册到 TransformTemplate
。Agent
,并通过 -javaagent:/Users/carl/.m2/repository/com/nepxion/discovery-agent-starter/1.0.1/discovery-agent-starter-1.0.1.jar -Dthread.scan.packages=com.nepxion.discovery.example.application -Dthread.myplugin.enabled=true
加载 discovery-agent-starter
并扫描需要增强的包。有了以上几个前置条件我们再来分析一下 DiscoveryAgent 的源码。
DiscoveryAgent
其实是基于 Java Agent 特性,关于 Java Agent 不知道的同学可以看我之前的博客 – 基于 Java Agent 实现零倾入监控。这样就可以找到
DiscoveryAgent
的处理入口类是:DiscoveryAgent#premain
。
DiscoveryAgent.java
public class DiscoveryAgent {
public static void premain(String agentArgs, Instrumentation instrumentation) {
LOG.info(String.format("%s agent on load, version %s", DiscoveryAgent.class.getSimpleName(), DiscoveryAgentConstant.DISCOVERY_AGENT_VERSION));
TransformTemplate transformTemplate = new TransformTemplate();
PluginFinder.load(transformTemplate);
instrumentation.addTransformer(new DispatcherClassFileTransformer(transformTemplate));
System.setProperty(DiscoveryAgentConstant.SPRING_APPLICATION_DISCOVERY_AGENT_VERSION, DiscoveryAgentConstant.DISCOVERY_AGENT_VERSION);
}
}
这个类里面其实最主要做了以下几件事。
TransformTemplate
对象,这个对象主要是装载 Plugin 增强DiscoveryAgent
提供的默认 Plugin 以及通过 SPI 外置的 PluginDispatcherClassFileTransformer
到 Instrumentation
,看哪些类需要进行 Agent 增强。key
值为 spring.application.discovery.agent.version
, value 为 1.0.0
到系统变量里面第 4 步特别简单,不做分析,下面就来详细分析一下其它各个步骤。
TransformTemplate
对象创建 TransformTemplate
对象,这个类里面的里面就 3 个 Map 属性。
private final Map<ClassMatcher, TransformCallback> register = new HashMap<>();
private final Map<String, ClassMatcher> classMatcherFinder = new HashMap<>();
private final Map<MatcherOperator, ClassMatcher> interfaceMatcherFinder = new HashMap<>();
在介绍这 3 个属性之前,首先先来分析一下这些属性泛型里面涉及到的对象。
InterfaceMatcher
,根据传入的 ClassInfo
中的 class 名称
是否包含指定接口来判断是否匹配,包含就匹配,不包含就不匹配;ClassNameMatcher
,根据传入的ClassInfo
中的 class 名称
是否与指定的类全名相等来判断是否匹配,相等就匹配,不包含就不匹配;MatcherOperator
,它实现 ClassMatcher
的 match 始终为 false,但是它提供了另外一个匹配方法,首先是否包匹配(class 名称是否以指定包开始),然后根据InterfaceMatcher
进行匹配。在明白上面的 2 个接口的概念之后,下面我们再来分析一下 TransformTemplate
里面的 3 个 Map 对象相信大家就应该更容易理解一点了。
register
: 其它类在调用 TransformTemplate#transform
方法时会把 ClassMatch(增强的类匹配规则) 以及 TransformCallback
映射关系添加到这个 Map 当中classMatcherFinder
: 其它类在调用 TransformTemplate#transform
方法时,如果 ClassMatch
属于 ClassNameMatcher
就会把 class 名称与 ClassMatcher
的关系添加到 classMatcherFinder
当中interfaceMatcherFinder
:其它类在调用 TransformTemplate#transform
方法时,如果 ClassMatch
属于 MatcherOperator
就会把 MatcherOperator
与 ClassMatcher
的关系添加到 interfaceMatcherFinder
当中也就是如果一个类要找它是否需要增强,需要先把从 classMatcherFinder
或者 interfaceMatcherFinder
看是否能够找到 ClassMatch,如果找不到就不需要进行增强,如果找到才从register
映射关系里面找是如何进行增强的。
PluginFinder.java
public class PluginFinder {
public static void load(TransformTemplate transformTemplate) {
new SpringAsyncPlugin().install(transformTemplate);
new ThreadPlugin().install(transformTemplate);
URL[] pluginUrls = getPlugin().toArray(new URL[] {});
ClassLoader classLoader = URLClassLoaderFactory.createClassLoader("discovery.agent", pluginUrls, PluginFinder.class.getClassLoader());
List<Plugin> loadPlugins = PluginLoader.load(classLoader, Plugin.class);
for (Plugin plugin : loadPlugins) {
if (plugin.isEnabled()) {
plugin.install(transformTemplate);
LOG.info(String.format("%s install successfully", plugin.getClass().getSimpleName()));
} else {
LOG.info(String.format("%s disable to install", plugin.getClass().getSimpleName()));
}
}
}
}
SpringAsyncPlugin
:调用 SpringAsyncPlugin#install
对 Spring @Async
异步处理注解进行增强ThreadPlugin
:调用 ThreadPlugin#install
对 java 中的 Callable
与 Runnable
异步操作进行增强。首先需要在环境变量中配置变量 thread.scan.packages
,然后使用 RunnableTransformCallback
对 Runnable
进行增强,使用 CallableTransformCallback
对 Callable
接口进行增强。Plugin#install
把字节增强添加到 TransformTemplate 中。上面 3 种不同的 Plugin 增强方式是不一样的。但是它们最终的思想都是一样的。就拿SpringAsyncPlugin#install
对 Spring @Async
异步处理注解进行增强为线,它会在 AsyncExecutionAspectSupport
类的 doSubmit
方法通过字符码增强创建的 Callable 类型的对象使用 WrapCallable
进行包装。
WrapCallable.java
public class WrapCallable<T> implements AsyncContextAccessor, Callable<T> {
private Callable<T> callable;
private AsyncContext asyncContext;
public WrapCallable(Callable<T> callable) {
this.callable = callable;
Object[] objects = ThreadLocalCopier.create();
setAsyncContext(new AsyncContext(objects));
}
@Override
public T call() throws Exception {
Object[] objects = asyncContext.getObjects();
ThreadLocalCopier.before(objects);
try {
return callable.call();
} finally {
ThreadLocalCopier.after();
}
}
@Override
public void setAsyncContext(AsyncContext asyncContext) {
this.asyncContext = asyncContext;
}
@Override
public AsyncContext getAsyncContext() {
return asyncContext;
}
}
WrapCallable
创建对象的时候构造器里面会通过 ThreadLocalCopier.create()
创建 ThreadLocalCopier
对象持有的 ThreadLocalHook
列表的 ThreadLocalHook#create
方法。然后再异步方法调用前会调用 ThreadLocalHook#before
, 异步方法调用后会调用 ThreadLocalHook#after
方法。
对上面的代码再进一步白话就是在异步方法调用之前通过 ThreadLocalHook#create
创建需要进行异步传递的参数。然后在异常方法之前通过ThreadLocalHook#before
把这个值添加到 ThreadLocal 里面,最后通过 ThreadLocalHook#aflter
把这个 ThreadLocal 进行异步清除。
在这里需要特别注意的是:包装类必须实现 AsyncContextAccessor 接口,这样在构建器进行增强的时候就可以把 ThreadLocalHook#create
创建的对象添加进去。在进行异步方法执行之前就可以通过AsyncContextAccessor#getObjects()
。在 WrapCallable
这个对象面向不明显,可以查看对 Runnable 以及 Callable 的构建器增强 ThreadConstructorInterceptor
以及 ThreadCallInterceptor
。
这里其实最重要的就是分发器DispatcherClassFileTransformer
,它实现了 ClassFileTransformer
主要就是对 Class 进行增强。DispatcherClassFileTransformer
持有对象 TransformTemplate。它是对需要字节码对象增强的注册表。上面我们已经分析了而 DispatcherClassFileTransformer
逻辑其实挺简单的。核心逻辑如下: