相对修饰Runnable
或是线程池的方式,Java Agent
方式为什么是应用代码无侵入的?
按框架图,把前面示例代码操作可以分成下面几部分:
- 读取信息设置到
TTL
。
这部分在容器中完成,无需应用参与。 - 提交
Runnable
到线程池。要有修饰操作Runnable
(无论是直接修饰Runnable
还是修饰线程池)。
这部分操作一定是在用户应用中触发。 - 读取
TTL
,做业务检查。
在SDK
中完成,无需应用参与。
只有第2部分的操作和应用代码相关。
如果不通过Java Agent
修饰线程池,则修饰操作需要应用代码来完成。
使用Java Agent
方式,应用无需修改代码,即做到 相对应用代码 透明地完成跨线程池的上下文传递。
把这些失效情况都解决了是最好的,但复杂化了实现。下面是一些权衡:
- 不推荐使用
Timer
类,推荐用ScheduledThreadPoolExecutor
。ScheduledThreadPoolExecutor
实现更强壮,并且功能更丰富。 如支持配置线程池的大小(Timer
只有一个线程);Timer
在Runnable
中抛出异常会中止定时执行。 - 覆盖了
execute
、submit
、schedule
的问题的权衡是: 业务上没有修改这些方法的需求。并且线程池类提供了beforeExecute
方法用于插入扩展的逻辑。
这样可以减少Java命令上Agent的配置。
在自己的ClassFileTransformer
中调用TtlTransformer
,示例代码如下:
public class TransformerAdaptor implements ClassFileTransformer {
final TtlTransformer ttlTransformer = new TtlTransformer();
@Override
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined,
ProtectionDomain protectionDomain, byte[] classfileBuffer)
throws IllegalClassFormatException {
final byte[] transform = ttlTransformer.transform(
loader, className, classBeingRedefined, protectionDomain, classfileBuffer);
if (transform != null) {
return transform;
}
// Your transform code ...
return null;
}
}
注意还是要在bootclasspath
上,加上TTL
依赖的2个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
和MTC
的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插件