记一次多个Java Agent同时使用的类增强冲突问题及分析( 三 )


void addTransformer(ClassFileTransformer transformer, boolean canRetransform)通过此方法 , 我们可以为我们想要操作的类添加一个ClassFileTransFormer,顾名思义其为类文件转换器,其官方描述如下:

All future class definitions will be seen by the transformer, except definitions of classes upon which any registered transformer is dependent. The transformer is called when classes are loaded, when they are redefined. and if canRetransform is true, when they are retransformed.
简单来讲,在对一个类注册了该转换器后,未来该类的每一次redefine以及retransform,都会被该转换器检查到 , 并且执行该转换器的操作 。
由上述描述可以知道 , 我们想要做的字节码增强操作就是通过向JVM中添加转换器并且通过转换器将JVM中的类转换为我们想要的结果(Transform a class by transfomer.)流程如下:
记一次多个Java Agent同时使用的类增强冲突问题及分析

文章插图
首先通过premain方法运行JavaAgent,此时在premain参数中我们可以获取到Instrumentation,第二步通过Instrumentation接口将实现的ClassFileTransfomer注册到JVM上 , 当JVM去加载类的时候,ClassFileTransfomer会获得类的字节数组,并对其进行transform后再返回给JVM,此后该类在Java程序中的表现就是转换之后的结果 。
retransform上述为类加载时Instrumentation在其中所做的工作,但是如果类以及被加载完成后,想要再次对其做转换(适用于多个JavaAgent场景及通过agentmain方式运行JavaAgent),就需要使用到Instrumentation接口为我们提供的如下方法:
void retransformClasses(Class<?>... classes) throws UnmodifiableClassException其官方描述如下:
This function facilitates the instrumentation of already loaded classes. When classes are initially loaded or when they are redefined, the initial class file bytes can be transformed with the ClassFileTransformer. This function reruns the transformation process (whether or not a transformation has previously occurred)
这个方法将用于对已经加载的类进行插桩,并且是从最初类加载的字节码开始重新应用转换器,并且每一个被注册到JVM的转换器都将会被执行 。
通过这个方法 , 我们就可以对已经被加载的类进行transform,执行该方法后的流程如下,其实就是重新触发ClassFileTransformer中的transform方法:
记一次多个Java Agent同时使用的类增强冲突问题及分析

文章插图
值得注意的是,reTransformClasses 功能很强大,但是其也有一系列的限制,在官方文档描述中,其限制如下:
The retransformation must not add, remove or rename fields or methods, change the signatures of methods, or change inheritance.
重转换过程中,我们不能新增、删除或者重命名字段和方法,不能更改方法的签名,不能更改类的继承 。
字节码分析上述reTransformClasses方法的限制是否是问题产生的根因呢?
在反编译经过SkyWalking增强后的字节码文件后,原因水落石出 。类经过Skywalking增强之后的继承关系上多了implements EnhancedInstance 。这显然改变了类的继承关系,而这一点恰好是官网接口文档中明确描述的限制行为 。正是因为这个接口的实现导致了本文开头描述的多个JavaAgent的类冲突增强失败的问题 。
该问题在SkyWalking的社区中也有一个相关issue , 社区解释为了减少链路追踪过程中的反射调用确实打破了reTransformClasses()的限制,类增强后新增实现了一个接口 。
final class Dispatcher$LegacyAsyncDispatcher extends Dispatcher implements EnhancedInstance { private final ConcurrentLinkedQueue<com.google.common.eventbus.Dispatcher.LegacyAsyncDispatcher.EventWithSubscriber> queue; private volatile Object _$EnhancedClassField_ws; private Dispatcher$LegacyAsyncDispatcher() { this.queue = Queues.newConcurrentLinkedQueue(); } void dispatch(Object var1, Iterator<Subscriber> var2) {delegate$51c0bj0.intercept(this, new Object[]{var1, var2}, cachedValue$P524FzM0$7gcbrk1, new JKwtdbN5(this)); } public void setSkyWalkingDynamicField(Object var1) { this._$EnhancedClassField_ws = var1; } public Object getSkyWalkingDynamicField() { return this._$EnhancedClassField_ws; } static {ClassLoader.getSystemClassLoader().loadClass("net.bytebuddy.dynamic.Nexus").getMethod("initialize", Class.class, Integer.TYPE).invoke((Object)null, Dispatcher$LegacyAsyncDispatcher.class, -1207479570);cachedValue$P524FzM0$7gcbrk1 = Dispatcher$LegacyAsyncDispatcher.class.getDeclaredMethod("dispatch", Object.class, Iterator.class); }}总结避免多个JavaAgent增强冲突的建议现在JavaAgent技术越来越受到各大厂商和开源社区的青睐 , 涌现出不少优秀的JavaAgent框架 。开发者或厂商在使用JavaAgent的时候难免会遇到同时挂载多个JavaAgent的场景,如果JavaAgent开发方能够对其他同类框架做到良好的兼容性,将会给使用者带来更少的麻烦 , 毕竟使用者未必能透彻的了解字节增强的底层原理 。

推荐阅读