框架/中间件集成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
依赖,也规避了依赖冲突的问题。
编译构建的环境要求: JDK 8~11
;用Maven
常规的方式执行编译构建即可:
# 在工程中已经包含了符合版本要求的Maven
,直接运行 工程根目录下的mvnw
;并不需要先手动自己安装好Maven
。
# 运行测试Case
./mvnw test
# 编译打包
./mvnw package
# 运行测试Case、编译打包、安装TTL库到Maven本地
./mvnw install
#####################################################
# 如果使用你自己安装的`Maven`,版本要求:maven 3.3.9+
mvn install
TTL
的代码实现使用了JDK 8
的标准库类,但编译成Java 6
版本的类文件。即
- 编译
Java
文件的Java
语言版本 是Java 6
。 - 而编译依赖的
Java API
/标准库(由JVM
提供) 需要是Java 8
/JVM 8
;高于Java
语言版本。
现代的IDE
(如IntelliJ IDEA
)一般会缺省做 语言版本 与 API
版本 的检查:
- 如何使用了高于语言版本的标准库类,
IDE
会报错。 - 以避免在语言版本
JVM
运行时,可能会出API
/标准类找不到的风险。
可以在IDE
设置中,关闭这个『语言版本 与 API
版本』检查。
在设置中关闭【Inspections
- Usages of API which isn't available at the configured language level
】:
当然通过【Find Actions...
cmd + shift + A】,可以更方便快速完成设置:
其它IDE
(如Eclipse
、NetBeans
)可以找一下设置方法,以关闭这个『语言版本 与 API
版本』检查。
如果没有找到IDE
的设置方法,也可以用下面的方法来 workaround
: 😂
打开 工程根目录下的pom4ide.xml
文件(修改了Java
文件的语言版本),而不是pom.xml
。
- 官方文档
- Java SE 6 新特性: Instrumentation 新功能
- Creation, dynamic loading and instrumentation with javaagents
- JavaAgent加载机制分析
Maven
的Shade插件