docs/developer-guide.md
TTL传递框架/中间件集成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。Java AgentJava Agent方式对应用代码无侵入User Guide - 2.3 使用Java Agent来修饰JDK线程池实现类 说到了,相对修饰Runnable或是线程池的方式,Java Agent方式是对应用代码无侵入的。下面做一些展开说明。
按框架图,把前面示例代码操作可以分成下面几部分:
TTL。Runnable到线程池。要有修饰操作Runnable(无论是直接修饰Runnable还是修饰线程池)。TTL,做业务检查。SDK中完成,无需应用参与。只有第2部分的操作和应用代码相关。
如果不通过Java Agent修饰线程池,则修饰操作需要应用代码来完成。
使用Java Agent方式,应用无需修改代码,即做到 相对应用代码 透明地完成跨线程池的上下文传递。
更多关于应用场景的了解说明参见文档需求场景。
Java Agent中嵌入TTL 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
Bootstrap ClassPath上添加通用库Jar的问题及其解决方法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
详见独立文档 发布操作列表。
JDK core classesJava AgentJavassistMaven Shade插件