框架/中间件集成TTL
传递,通过TransmittableThreadLocal.Transmitter
抓取当前线程的所有TTL
值并在其他线程进行回放;在回放线程执行完业务操作后,恢复为回放线程原来的TTL
值。
TransmittableThreadLocal.Transmitter
提供了所有TTL
值的抓取、回放和恢复方法(即CRR
操作):
capture
方法:抓取线程(线程A)的所有TTL
值。replay
方法:在另一个线程(线程B)中,回放在capture
方法中抓取的TTL
值,并返回 回放前TTL
值的备份restore
方法:恢复线程B执行replay
方法之前的TTL
值(即备份)
示例代码:
// ===========================================================================
// 线程 A
// ===========================================================================
TransmittableThreadLocal<String> parent = new TransmittableThreadLocal<String>();
parent.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 = parent.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> parent = new TransmittableThreadLocal<String>();
parent.set("value-set-in-parent");
// (1) 抓取当前线程的所有TTL值
final Object captured = TransmittableThreadLocal.Transmitter.capture();
// ===========================================================================
// 线程 B(异步线程)
// ===========================================================================
String result = runSupplierWithCaptured(captured, () -> {
// 你的业务逻辑,这里你可以获取到外面设置的TTL值
String value = parent.get();
System.out.println("Hello: " + value);
...
return "World: " + value;
}); // (2) + (3)
更多TTL
传递的说明详见TransmittableThreadLocal.Transmitter
的JavaDoc
。
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.0.0.jar:/path/to/your/agent/jar/files
通过Java
命令参数-Xbootclasspath
把库的Jar
加Bootstrap
ClassPath
上。Bootstrap
ClassPath
上的Jar
中类会优先于应用ClassPath
的Jar
被加载,并且不能被覆盖。
TTL
在Bootstrap
ClassPath
上添加了Javassist
的依赖,如果应用中如果使用了Javassist
,实际上会优先使用Bootstrap
ClassPath
上的Javassist
,即应用不能选择Javassist
的版本,应用需要的Javassist
和TTL
的Javassist
有兼容性的风险。
可以通过repackage
(重新命名包名)来解决这个问题。
Maven
提供了Shade
插件,可以完成repackage
操作,并把Javassist
的类加到TTL
的Jar
中。
这样就不需要依赖外部的Javassist
依赖,也规避了依赖冲突的问题。
- Java Agent规范
- Java SE 6 新特性: Instrumentation 新功能
- Creation, dynamic loading and instrumentation with javaagents
- JavaAgent加载机制分析
Maven
的Shade插件