整理自架构经理(汤哥)的分享
字节增强条件匹配
在 skywalking 中实现很多基于 byte-buddy 的关于链式匹配查询的实现, 代码如下所示:
public abstract class AbstractJunction<V> implements ElementMatcher.Junction<V>
其对应的类的类图关系如下所示:
除此之外, 为了便于更好的执行在拦截时期的的匹配, skywalking 又定义了一套自实现的 Match 的匹配器, 类的简要类图关系如下所示:
其中 NameMatch 名称 Match 的 NameMatch 好象相对类的继承相关较独立,但是其它方式的 Match 继承结构相对有点复杂, 都继承于IndirectMatch , 上面类的简意类图对应的类的完整类图详情如下所示:
插件方式增强的扩展点
所有的基于 skywalking 的整套增强逻辑框架下,如查有新的要增强的中间件或组件, 必须遵守其制定的插件扩展规范, 其具体的规范如下:
一、 在对应的插件的 jar 包的 classpath 下必须存在定义文件 skywalking-plugin.def ,文件中必须包括 对 JTI 中的 Instrumentation 的自定义类的全限定名, 以 Dubbo 增强为例, 其内容为
dubbo=org.apache.skywalking.apm.plugin.dubbo.DubboInstrumentation
二、自定义的 Instrumentation 必须继承至 ClassInstanceMethodsEnhancePluginDefine 抽象类 , 在类中,须指定这个设施的增强的两个核心要素内容:
A). 须要增强的目标类 , Enhance Class
B). 插件中自己要实现的具体的拦截实现 xxxxxInterceptor
同样以 Dubbo 为例, 如果要自定义自己的插件实的话,则就要指定如下内容:
三、最后就是要真实的定义一个 实现接口 InstanceMethodsAroundInterceptor 的一个拦截实现
对于每一个插件来说, 不一定只能有一个 Interceptor , 还可以有多个, 因为有时不一定只是要对一个类或一个方法进行拦截, 可能是多个类, 也有可能是一个类中的多个方法, 或多个类的多个方法,因此,在最初的 Instrumentation 入口定义之外,就提供了可以多个方法的增强扩展, 具体的如下图针对 Dubbo 的增强所示:
插件方式增强主体逻辑
理解实现插件增强机理的类与类之间的关系,犹为重要,它也是 sky-walking 的对于中间件以插件方式进行增强操作的核心, 下面我们就来仔细的分析下,是如何进行自定义插件, 并如何进行对中间件的增强的。在整个的增强的机制中, ClassEnhancePluginDefine 是很重要, 很核心的一个类, 在其 enhance 方法中, 分别指定了对
- 静态方法增强
- 实现方法增强
- 定义了 ConstructorInterceptPoint、InstanceMethodsInterceptPoint、StaticMethodsInterceptPoint
这三个 构造器,实例方法, 静态方法拦截点的 抽象 方法, 让插件子类去实现拦截点
下面是在 Dubbo 为例 DubboInstrumentation 插件主体实现依赖的类图关系:
上面的是以 ClassEnhancePluginDefine 为核心的主体的核心类图结构, 现在主要讲一下此类的内部的主要构成与执行序列, 完整的类图如下所示:
从上面可知, ClassEnhancePluginDefine 继承至 AbstractClassEnhancePluginDefine , 做为所有的插件都要继承实现的基础, 抽象类AbstractClassEnhancePluginDefine 具体定义了什么行为, 这些行为又有哪些意义?什么地方在调用这个方法?
AbstractClassEnhancePluginDefine 中有四个方法
- enhance 抽象方法, 抽象类中无实现, 子类去实现,具体的增强的逻辑
- enhanceClass 抽象方法, 抽象类中无实现,子类去实现,具体的要增强的类的 Match
- witnessClasses 也是可以被重载的一个方法,子类可重载
- define 实例方法,它主要做了下面的几次事 ( 入口方法)
1 找到增强插件类的所有显式指定的 WitnessClasses - 用户自己重载显式指定
2 调用 enhance 方法, 执行真正的插件的增强逻辑,返回新的 DynamicType.Builder
3 设置上下文, 标记初始化定义步骤已完成
4 最后再返回新的 第 2 中的增强后的 newClassBuilder
那么在这个类中,最为重要的一个方法 define 又是谁调用的呢?
上面的就是这个类方法被使用的 Usages 情况, 主要是被 Sky-Walking Agent 的 Agent 主入口调用, 这个方法也是使用 byte-buddy 的字节码增强的 agent 中的一个使用范式, 也就是说在 agent 执行真正的 transformer , 所有的插件中因为都是继承至AbstractClassEnhancePluginDefine , 自然它们的 define 的定义的初始化方法, 也就会被全部调用初始化, 代码如下所示:
对于 snow-walking 来说, 使用 Agent 的固定范式( 来自于 byte-buddy 的固定实现方式)如下所示:
至此,已经分析完了, 所有 sky-walking 的自定义插件都必须要继承并重载的抽象类 AbstractClassEnhancePluginDefine , 它定义了所有的Agent 插件都必须要实现的行为, 以及它自身需要定义与初始化的行为逻辑。
接下为, 真正的主角上场了, 它就是 ClassEnhancePluginDefine , 其类的依赖关系如下:
下面看一下 类的本身的方法, 总共有 六 个方法:
enhance 方法, 重载了父类抽象类 AbstractClassEnhancePluginDefine 的方法, 做为所有插件的基类, 它定义了 enhance 方法必须所具备的操作。此方法的内部主要做了两件事:
- enhanceClass , 增强一个类,以拦截其静态方法
- enhanceInstance ,增强一个类,以拦截其构造器 与 实例方法
除了重载的方法外, 其它的三个方法, 分别提供了对应的拦截点:
在实现自定义插件逻辑的时候, 要重载这三个方法中的实现, 以 Dubbo 插件拦截扩展为例, 在 DubboInstrumentation 中 就重载了 父类的getInstanceMethodsInterceptPoints方法, 以此来告诉父类中的共性 enhance 增强处理逻辑, 本插件的实例方法拦截点的一些元数据信, 区配的方法是什么,要增强的类是哪一个,是否要重载其方法参数,这些信息。
上面这里, 在具体的 ClassEnhancePluginDefine 实现插件内部, 对于拦截器 intercepter 都是指定的是 字符串 String 类型, 那么父类中ClassEnhancePluginDefine 是如何转化,并在什么时机调用这个 intercepter 的实现类, 以执行拦截的真实的逻辑的呢?
那上面的这里从用户自定义的插件中显式的指定了 intercepter Fullname了,到了ClassEnhancePluginDefine后,进行enhance时,会最终都落在 byte-buddy 的 builder. method (....).intercpet (MethodDelegation.xxxx)
这样的固定增强的编程范式当中去。在 sky-walking 的实现中, 它又将 intercepter 的名称, 根据不同的增强类型,传入了不同类型的具体的委托执行实例当中去执行了。在 sky-walking 中, 根据增强的分类类型,委托执行实例分为以下几种:
它们分别是, 静态方法固定拦截委托实现器,实例方法固定拦截委托实现器,构造器固定拦截委托实现器, 它们只有一个主要的核心执行方法, 就是 intercept , 这个方法在执行时是与 byte-budy 的 MethodDelegation 配套使用的, 此方法必须要显式的按规定指定相应的byte-buddy 的 Annotation 方能正常的执行增强工作。
因为这三个固定类型的拦截器的处理方式都是差不多, 这里就以 静态方法拦截器举例分析其内部的执行原理。先看一下这个拦截器的类
的依赖情况:
从上面的此拦截器的依赖情况可以, 它主要依赖两个:
- StaticMethodsAroundInterceptor , 这个接口的实现者是自定义插件自行根据需求实现的
- InterceptorInstanceLoader ,这个是用户自定义插件的拦截器的类加载器
之前我们说, 执行 byte-buddy 固定的 intercept 逻辑范式时, 通过 MethodDelegation 委托给了 sky-walking 的预设定的几个类型的拦截器,在构建这些固定拦截器时, 传入的都是用户自定义的拦截器的 ClassFullName , 所以在真实的固定类型的拦截器内部,就得有一个机制去加载用户自定义的拦截器,只有这样, 这些拦截器才能被调用执行。
这些拦截器有三个固定的方法:
- beforeMethod , 被拦截方法之前执行
- afterMethod ,被拦截方法之后执行
- handleMethodException ,处理捕获执行期间的异常
OK , 分析到这里, 基本上, 用户自定义增强插件,如何被增强,如何被拦截执行的过程应该算是比较清楚了。
增强与拦截机制总结
下图是整理后的关于 sky-walking 的 APM 中用户自定义插件的完整的调用链路, 它对于插件如何生效并进行增强与拦截的调用过程做了描绘。