本文主要分享 SkyWalking Agent 插件体系。主要涉及三个流程 :
可能看起来有点抽象,不太容易理解。淡定,我们每个小章节进行解析。
本文涉及到的类主要在 org.skywalking.apm.agent.core.plugin 包里,如下图所示 :
每个流程会涉及到较多的类,我们会贯穿着解析代码实现。
在 《SkyWalking 源码分析 —— Agent 初始化》 一文中,Agent 初始化时,调用 PluginBootstrap#loadPlugins() 方法,加载所有的插件。整体流程如下图 :
PluginBootstrap#loadPlugins() 方法,代码如下 :
org.skywalking.apm.agent.core.plugin.loader.AgentClassLoader ,继承 java.lang.ClassLoader ,Agent 类加载器。
为什么实现自定义的 ClassLoader ?应用透明接入 SkyWalking ,不会显示导入 SkyWalking 的插件依赖。通过实现自定义的 ClassLoader ,从插件 Jar 中查找相关类。例如说,从 apm-dubbo-plugin-3.2.6-2017.jar 查找 org.skywalking.apm.plugin.dubbo.DubboInstrumentation 。
AgentClassLoader 构造方法,代码如下 :
public class AgentClassLoader extends ClassLoader { /** * The default class loader for the agent. */ private static AgentClassLoader DEFAULT_LOADER; /** * classpath */ private List classpath; /** * Jar 数组 */ private List allJars; /** * Jar 读取时的锁 */ private ReentrantLock jarScanLock = new ReentrantLock(); public AgentClassLoader(ClassLoader parent) throws AgentPackageNotFoundException { super(parent); File agentDictionary = AgentPackagePath.getPath(); classpath = new LinkedList(); classpath.add(new File(agentDictionary, "plugins")); classpath.add(new File(agentDictionary, "activations")); }}
#initDefaultLoader() 静态方法,初始化默认的 AgentClassLoader ,代码如下 :
public static AgentClassLoader initDefaultLoader() throws AgentPackageNotFoundException { DEFAULT_LOADER = new AgentClassLoader(PluginBootstrap.class.getClassLoader()); return getDefault();}
如下方法已经添加相关中文注释,胖友请自行阅读理解 :
在 ClassLoader 加载资源( 例如,类 ),会调用 #findResource(name) / #findResources(name) 方法。
2.2 PluginResourcesResolver
org.skywalking.apm.agent.core.plugin.PluginResourcesResolver ,插件资源解析器,读取所有插件的定义文件。插件定义文件必须以skywalking-plugin.def 命名,例如 :
#getResources() 方法,获得插件定义路径数组,代码如下 :
2.3 PluginCfg
org.skywalking.apm.agent.core.plugin.PluginCfg ,插件定义配置,读取 skywalking-plugin.def 文件,生成插件定义(org.skywalking.apm.agent.core.plugin.PluginDefinie )数组。
#load(InputStream) 方法,读取 skywalking-plugin.def 文件,添加到 pluginClassList 。如下是apm-springmvc-annotation-4.x-plugin-3.2.6-2017.jar 插件的定义文件 :
spring-mvc-annotation-4.x=org.skywalking.apm.plugin.spring.mvc.v4.define.ControllerInstrumentationspring-mvc-annotation-4.x=org.skywalking.apm.plugin.spring.mvc.v4.define.RestControllerInstrumentationspring-mvc-annotation-4.x=org.skywalking.apm.plugin.spring.mvc.v4.define.HandlerMethodInstrumentationspring-mvc-annotation-4.x=org.skywalking.apm.plugin.spring.mvc.v4.define.InvocableHandlerInstrumentation
2.4 AbstractClassEnhancePluginDefine
org.skywalking.apm.agent.core.plugin.AbstractClassEnhancePluginDefine ,类增强插件定义抽象基类。不同插件通过实现 AbstractClassEnhancePluginDefine 抽象类,定义不同框架的切面,记录调用链路。以 Spring 插件为例子,如下是相关类图 :
PluginDefine 对象的 defineClass 属性,即对应不同插件对AbstractClassEnhancePluginDefine 的实现类。所以在PluginBootstrap#loadPlugins() 方法的【第 74 行】,我们看到通过该属性,创建创建类增强插件定义对象。
2.5 小结
胖友,回过头,在看一下流程图,理解理解。
在 《SkyWalking 源码分析 —— Agent 初始化》 一文,我们提到,SkyWalking Agent 基于 JavaAgent 机制,实现应用透明接入 SkyWalking 。下面笔者默认胖友已经对 JavaAgent 机制已经有一定的了解。如果胖友暂时不了解,建议先阅读如下文章 :
友情提示 :建议自己手撸一个简单的 JavaAgent ,更容易理解 SkyWalking Agent 。
笔者练手的 JavaAgent 项目地址 :https://github.com/YunaiV/learning/tree/master/javaagent01
通过 JavaAgent 机制,我们可以在 #premain(String, Instrumentation) 方法里,调用Instrumentation#addTransformer(ClassFileTransformer) 方法,向 Instrumentation 注册 java.lang.instrument.ClassFileTransformer 对象,可以修改 Java 类的二进制,从而动态修改 Java 类的代码实现。
如果胖友使用过 AOP 实现切面记录日志,那么就很容易理解,SkyWalking 通过这样的方式,使用不同框架定义方法切面,从而在在切面记录调用链路。
直接修改 Java 类的二进制,是非常繁杂的。因此,SkyWalking 引入了 byte-buddy 。
byte-buddy 是一个代码生成和操作库,用于在 Java 应用程序
运行时创建和修改 Java 类,而徐无需编译器的帮助。
除了参与 Java 类库一起提供代码生成工具外,byte-buddy 允许创建任意类,并不限于实现用于创建运行时代理的接口。
此外,byte-buddy 提供了一个方便的 API ,用于 Java Agent 或在构建过程中更改类。
下面笔者默认胖友已经对 byte-buddy 有一定的了解。如果胖友暂不了解,建议先阅读如下文章 :
友情提示 :建议自己简单使用下 byte-buddy ,更容易理解 SkyWalking Agent 。
笔者练手的 byte-buddy 项目地址 :https://github.com/YunaiV/learning/tree/master/bytebuddy
下面,让我们打开 SkyWalkingAgent#premain(String, Instrumentation) 方法,从【第 79 行】代码开始看 :
这个方法信息量比较大,笔者对 byte-buddy 不是很熟悉,花费了较多时间梳理与理解。建议,如果胖友此处不是理解的很清晰,可以阅读完全文,在回过头再捋一捋这块的代码实现。
3.1 InstrumentDebuggingClass
org.skywalking.apm.agent.InstrumentDebuggingClass ,Instrument 调试类,用于将被 JavaAgent 修改的所有类存储到${JAVA_AGENT_PACKAGE}/debugger 目录下。需要配置 agent.is_open_debugging_class = true ,效果如下图 :
代码比较简单,胖友点击 InstrumentDebuggingClass 理解。
3.2 ClassMatch
在分享本节相关内容之前,我们先来看下 bytebuddy 的 net.bytebuddy.matcher 模块。该模块提供了各种灵活的匹配方法。那么 SkyWalking 为什么实现自己的 org.skywalking.apm.agent.core.plugin.match 模块?笔者认为,仅定位于类级别的匹配,更常用而又精简的 API 。
org.skywalking.apm.agent.core.plugin.match.ClassMatch ,类匹配接口。目前子类如下 :
每个类已经添加详细的代码注释,胖友喜欢哪个点哪个哟。
3.3 PluginFinder
org.skywalking.apm.agent.core.plugin.PluginFinder ,插件发现者。其提供 #find(...) 方法,获得类增强插件定义(org.skywalking.apm.agent.core.plugin.AbstractClassEnhancePluginDefine )对象。
PluginFinder 构造方法,代码如下 :
#find(...) 方法,获得类增强插件定义( org.skywalking.apm.agent.core.plugin.AbstractClassEnhancePluginDefine )对象,代码如下 :
#buildMatch() 方法,获得全部插件的类匹配,多个插件的类匹配条件以 or 分隔,代码如下 :
在上文中,我们已经提到,SkyWalking 通过 JavaAgent 机制,对需要拦截的类的方法,使用 byte-buddy 动态修改 Java 类的二进制,从而进行方法切面拦截,记录调用链路。
看具体的代码实现之前,想一下拦截会涉及到哪些元素 :
下面,我们来看看本小节会涉及到的类。如图所示:
看起来类比想象的多?梳理之,结果如图 :
In this class, it provide a bridge between byte-buddy and sky-walking plugin.
4.1 ClassEnhancePluginDefine
整体类图如下:
整体流程如下 :
OK ,下面我们开始看看代码是如何实现的。
4.1.1 AbstractClassEnhancePluginDefine
org.skywalking.apm.agent.core.plugin.AbstractClassEnhancePluginDefine ,SkyWalking 类增强插件定义抽象基类。它注重在定义( Define )的抽象与实现。
#enhanceClass() 抽象方法,定义了类匹配( ClassMatch ) 。
#witnessClasses() 方法,见证类列表。当且仅当应用存在见证类列表,插件才生效。什么意思?让我们看看这种情况:一个类库存在两个发布的版本( 如 1.0 和 2.0 ),其中包括相同的目标类,但不同的方法或不同的方法参数列表。所以我们需要根据库的不同版本使用插件的不同版本。然而版本显然不是一个选项,这时需要使用见证类列表,判断出当前引用类库的发布版本。
#define(...) 方法,设置 net.bytebuddy.dynamic.DynamicType.Builder 对象。通过该对象,定义如何拦截需要修改的目标 Java 类(方法的transformClassName 参数)。代码如下 :
4.1.2 ClassEnhancePluginDefine
org.skywalking.apm.agent.core.plugin.interceptor.enhance.ClassEnhancePluginDefine ,SkyWalking 类增强插件定义抽象类。它注重在增强( Enhance )的抽象与实现。包括如下 :
拦截切面,在 「4.2 InterceptPoint」 有相关解析。
#getStaticMethodsInterceptPoints() 抽象方法,获得 StaticMethodsInterceptPoint 数组。
#getConstructorsInterceptPoints() 抽象方法,获得 ConstructorInterceptPoint 数组。
#getInstanceMethodsInterceptPoints() 抽象方法,获得 InstanceMethodsInterceptPoint 数组。
#enhance(...) 方法,增强静态方法、构造方法、实例方法。
4.1.2.1 增强静态方法
调用 #enhanceClass(...) 方法,增强静态方法,代码如下 :
4.1.2.2 增强构造方法和实例方法
调用 #enhanceInstance() 方法,增强构造方法和实例方法,代码如下 :
4.1.3 ClassStaticMethodsEnhancePluginDefine
org.skywalking.apm.agent.core.plugin.interceptor.enhance.ClassStaticMethodsEnhancePluginDefine ,类增强静态方法的插件定义抽象类,和本文 「4.1.2.1 增强静态方法」 对应。
实现 #getConstructorsInterceptPoints() / #getInstanceMethodsInterceptPoints() 抽象方法,返回空,表示不增强构造方法和实例方法。即只增强静态方法。
4.1.4 ClassInstanceMethodsEnhancePluginDefine
org.skywalking.apm.agent.core.plugin.interceptor.enhance.ClassInstanceMethodsEnhancePluginDefine ,类增强构造方法和实例方法的插件定义抽象类,和本文 「4.1.2.2 增强构造方法和实例方法」 对应。
实现 #getStaticMethodsInterceptPoints() 抽象方法,返回空,表示不增强静态方法。即只增强构造方法和实例方法。
4.2 InterceptPoint
InterceptPoint方法类型方法匹配拦截器#isOverrideArgs()StaticMethodsInterceptPoint静态方法#getMethodsMatcher()#getMethodsInterceptor()有ConstructorInterceptPoint构造方法#getConstructorMatcher()#getConstructorInterceptor()无InstanceMethodsInterceptPoint实例方法#getMethodsMatcher()#getMethodsInterceptor()有
XXXInterceptPoint 接口,对应一个 net.bytebuddy.matcher.ElementMatcher 和一个拦截器。
代码比较简单,老友自己查看。
4.3 Interceptor
在开始分享 Inter 之前,我们先来看看 Interceptor 相关接口。如下图所见:
在 「4. 2 InterceptPoint」 里,我们看到 #getXXXInterceptor() 方法返回的拦截器类名,需要通过org.skywalking.apm.agent.core.plugin.loader.InterceptorInstanceLoader 加载与创建拦截器实例。
4.4 Inter
我们先来看 Inter 的定义 :
In this class, it provide a bridge between byte-buddy and sky-walking plugin.
根据方法类型,将 Inter 整理如下 :
方法类型构造方法ConstructorInter实例方法InstMethodsInterInstMethodsInterWithOverrideArgs静态方法StaticMethodsInterStaticMethodsInterWithOverrideArgs
4.4.1 构造方法 Inter
org.skywalking.apm.agent.core.plugin.interceptor.enhance.ConstructorInter ,构造方法 Inter 。
ConstructorInter 构造方法,调用 InterceptorInstanceLoader#load(String, classLoader) 方法,加载构造方法拦截器。
#intercept(Object) 方法,在构造方法执行完成后进行拦截,调用 InstanceConstructorInterceptor#onConstruct(...) 方法。
为什么没有 ConstructorInterWithOverrideArgs?InstanceConstructorInterceptor#onConstruct(...) 方法,是在构造方法执行完成后进行调用拦截,OverrideArgs 用于在调用方法之前,改变传入方法的参数。所以,在此处暂时没这块需要,因而没有 ConstructorInterWithOverrideArgs 。
4.4.2 实例方法 Inter
org.skywalking.apm.agent.core.plugin.interceptor.enhance.InstMethodsInter ,实例方法 Inter 。
ConstructorInter 构造方法,调用 InterceptorInstanceLoader#load(String, classLoader) 方法,加载实例方法拦截器。
#intercept(...) 方法,Before-After 方式拦截实例方法,代码如下 :
org.skywalking.apm.agent.core.plugin.interceptor.enhance.InstMethodsInterWithOverrideArgs ,覆写参数的实例方法 Inter 。
不太理解覆写参数?有这样一个场景,InstanceMethodsAroundInterceptor#beforeMethod(...) 方法里,我们修改了方法参数,并且希望原有实例方法执行时,使用的是修改了的方法参数,此时,就需要使用 InstMethodsInterWithOverrideArgs 。
InstMethodsInterWithOverrideArgs#intercept(...) 方法,总体逻辑和 InstMethodsInter 是一致的,下面我们来看看差异点 :
先来瞅瞅 @Morph 注解的定义 :
This annotation instructs Byte Buddy to inject a proxy class that calls a method’s super method with explicit arguments.
For this, the {@link Morph.Binder} needs to be installed for an interface type that takes an argument of the array type {@link java.lang.Object} and returns a non-array type of {@link java.lang.Object}.
This is an alternative to using the {@link net.bytebuddy.implementation.bind.annotation.SuperCall} or {@link net.bytebuddy.implementation.bind.annotation.DefaultCall} annotations which call a super method using the same arguments as the intercepted method was invoked with.
简单的来说 :
// ClassEnhancePluginDefine.java// `#enhanceInstance(...)` 方法newClassBuilder = newClassBuilder.method(not(isStatic()).and(instanceMethodsInterceptPoint.getMethodsMatcher())) // 匹配 .intercept( // 拦截 MethodDelegation.withDefaultConfiguration() .withBinders( Morph.Binder.install(OverrideCallable.class) // 覆写参数 ) .to(new InstMethodsInterWithOverrideArgs(interceptor, classLoader)) );
4.4.3 静态方法 Inter
org.skywalking.apm.agent.core.plugin.interceptor.enhance.StaticMethodsInter 和org.skywalking.apm.agent.core.plugin.interceptor.enhance.StaticMethodsInterWithOverrideArgs 和实例方法 Inter基本一致,胖友可以自己捋一捋,笔者就不瞎比比了。
4.5 小结
总的来说,涉及到的组件,如下图 :
老友再梳理梳理。
关注+转发后私信回复资料,即可获取所有源码解析及架构学习方向和资料