框架/中间件集成TTL
传递,通过TransmittableThreadLocal.Transmitter
抓取当前线程的所有TTL
值并在其他线程进行回放;在回放线程执行完业务操作后,恢复为回放线程原来的TTL
值。
TransmittableThreadLocal.Transmitter
提供了所有TTL
值的抓取、回放和恢复方法(即CRR
操作):
capture
方法:抓取线程(线程A)的所有TTL
值。replay
方法:在另一个线程(线程B)中,回放在capture
方法中抓取的TTL
值,并返回 回放前TTL
值的备份restore
方法:恢复线程B执行replay
方法之前的TTL
值(即备份)
示例代码:
// ===========================================================================
// 线程 A
// ===========================================================================
TransmittableThreadLocal<String> context = new TransmittableThreadLocal<>();
context.set("value-set-in-parent");
// (1) 抓取当前线程的所有TTL值
final Object captured = TransmittableThreadLocal.Transmitter.capture();
// ===========================================================================
// 线程 B(异步线程)
// ===========================================================================
// (2) 在线程 B中回放在capture方法中抓取的TTL值,并返回 回放前TTL值的备份
final Object backup = TransmittableThreadLocal.Transmitter.replay(captured);
try {
// 你的业务逻辑,这里你可以获取到外面设置的TTL值
String value = context.get();
System.out.println("Hello: " + value);
...
String result = "World: " + value;
} finally {
// (3) 恢复线程 B执行replay方法之前的TTL值(即备份)
TransmittableThreadLocal.Transmitter.restore(backup);
}
更多TTL
传递的代码实现示例,参见 TtlRunnable.java
、TtlCallable.java
。
当然可以使用TransmittableThreadLocal.Transmitter
的工具方法runSupplierWithCaptured
和runCallableWithCaptured
和可爱的Java 8 Lambda
语法
来简化replay
和restore
操作,示例代码:
// ===========================================================================
// 线程 A
// ===========================================================================
TransmittableThreadLocal<String> context = new TransmittableThreadLocal<>();
context.set("value-set-in-parent");
// (1) 抓取当前线程的所有TTL值
final Object captured = TransmittableThreadLocal.Transmitter.capture();
// ===========================================================================
// 线程 B(异步线程)
// ===========================================================================
String result = runSupplierWithCaptured(captured, () -> {
// 你的业务逻辑,这里你可以获取到外面设置的TTL值
String value = context.get();
System.out.println("Hello: " + value);
...
return "World: " + value;
}); // (2) + (3)
- 更多
TTL
传递的说明,详见TransmittableThreadLocal.Transmitter
的JavaDoc
。 - 更多
TTL
传递的代码实现,参见TtlRunnable.java
、TtlCallable.java
。
User Guide - 2.3 使用Java Agent
来修饰JDK
线程池实现类 说到了,相对修饰Runnable
或是线程池的方式,Java Agent
方式是对应用代码无侵入的。下面做一些展开说明。
按框架图,把前面示例代码操作可以分成下面几部分:
- 读取信息设置到
TTL
。
这部分在容器中完成,无需应用参与。 - 提交
Runnable
到线程池。要有修饰操作Runnable
(无论是直接修饰Runnable
还是修饰线程池)。
这部分操作一定是在用户应用中触发。 - 读取
TTL
,做业务检查。
在SDK
中完成,无需应用参与。
只有第2部分的操作和应用代码相关。
如果不通过Java Agent
修饰线程池,则修饰操作需要应用代码来完成。
使用Java Agent
方式,应用无需修改代码,即做到 相对应用代码 透明地完成跨线程池的上下文传递。
更多关于应用场景的了解说明参见文档需求场景。
这样可以减少Java
启动命令行上的Agent
的配置。
在自己的Agent
中加上TTL Agent
的逻辑,示例代码如下(YourXxxAgent.java
):
import com.alibaba.ttl.threadpool.agent.TtlAgent;
import com.alibaba.ttl.threadpool.agent.TtlTransformer;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.Instrumentation;
import java.util.logging.Logger;
public final class YourXxxAgent {
private static final Logger logger = Logger.getLogger(YourXxxAgent.class.getName());
public static void premain(String agentArgs, Instrumentation inst) {
TtlAgent.premain(agentArgs, inst); // add TTL Transformer
// add your Transformer
...
}
}
关于Java Agent
和ClassFileTransformer
的如何实现可以参考:TtlAgent.java
、TtlTransformer.java
。
注意,在bootclasspath
上,还是要加上TTL Jar
:
-Xbootclasspath/a:/path/to/transmittable-thread-local-2.x.y.jar:/path/to/your/agent/jar/files
TTL Agent
的使用方式,需要将TTL Jar
加到Bootstrap ClassPath
上(通过Java
命令行参数-Xbootclasspath
);这样TTL
的类与JDK
的标准库的类(如java.lang.String
)的ClassLoader
是一样的,都在Bootstrap ClassPath
上。
Bootstrap ClassPath
上的类会优先于应用ClassPath
的Jar
被加载,并且加载ClassLoader
不能被改。
# 当然技术上严格地说,通过Bootstrap ClassPath
上的类(如标准库的类)是可以改ClassLoader
的,但这样做一般只会带来各种麻烦的问题。关于ClassLoader
及其使用注意的介绍说明 可以参见ClassLoader委托关系的完备配置。
TTL Agent
自己内部实现使用了Javassist
,即在Bootstrap ClassPath
上也需要添加Javassist
。如果应用中也使用了Javassist
,由于运行时会优先使用TTL Agent
配置Bootstrap ClassPath
上的Javassist
,应用逻辑运行时实际不能选择/指定应用自己的Javassist
的版本,带来了 应用需要的Javassist
与TTL Agent
用的Javassist
之间的兼容性风险。
可以通过 repackage
依赖(即 重命名/改写 依赖类的包名)来解决这个问题。Maven
提供了Shade
插件,可以完成下面的操作:
repackage
Javassist
的类文件- 添加
repackage
过的Javassist
到TTL Jar
中
这样操作后,TTL Agent
不需要依赖外部的Javassist
依赖,效果上这样的shade
过的TTL Jar
是自包含的、在使用上是编译/运行时0依赖的,自然也规避了依赖冲突的问题。
编译构建的环境要求: JDK 8+
;用Maven
常规的方式执行编译构建即可:
# 在工程中已经包含了符合版本要求的Maven
,直接运行 工程根目录下的mvnw
;并不需要先手动自己安装好Maven
。
# 运行测试Case
./mvnw test
# 编译打包
./mvnw package
# 运行测试Case、编译打包、安装TTL库到Maven本地
./mvnw install
#####################################################
# 如果使用你自己安装的 maven,版本要求:maven 3.3.9+
mvn install
详见独立文档 发布操作列表。