1. 概述
本文主要分享 SkyWalking Agent 插件體系。主要涉及三個流程 :
- 插件的加載
- 插件的比對
- 插件的攔截
可能看起來有點抽象,不太容易了解。淡定,我們每個小章節進行解析。
本文涉及到的類主要在
org.skywalking.apm.agent.core.plugin
包裡,如下圖所示 :
每個流程會涉及到較多的類,我們會貫穿着解析代碼實作。
2. 插件的加載
在 《SkyWalking 源碼分析 —— Agent 初始化》 一文中,Agent 初始化時,調用
PluginBootstrap#loadPlugins()
方法,加載所有的插件。整體流程如下圖 :
PluginBootstrap#loadPlugins()
方法,代碼如下 :
- 第 47 行 :調用
方法,初始化 AgentClassLoader 。在本文 「2.1 AgentClassLoader」 詳細解析。AgentClassLoader#initDefaultLoader()
- 第 50 至 56 行 :獲得插件定義路徑數組。在本文 「2.2 PluginResourcesResolver」 詳細解析。
- 第 59 至 66 行 :獲得插件定義(
)數組。在本文 「2.3 PluginCfg」 詳細解析。org.skywalking.apm.agent.core.plugin.PluginDefine
- 第 69 至 82 行 :建立類增強插件定義(
)對象數組。不同插件通過實作 AbstractClassEnhancePluginDefine 抽象類,定義不同架構的切面,記錄調用鍊路。在本文 「2.4 AbstractClassEnhancePluginDefine」 簡單解析。org.skywalking.apm.agent.core.plugin.AbstractClassEnhancePluginDefine
2.1 AgentClassLoader
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<File> classpath;
/**
* Jar 數組
*/
private List<Jar> allJars;
/**
* Jar 讀取時的鎖
*/
private ReentrantLock jarScanLock = new ReentrantLock();
public AgentClassLoader(ClassLoader parent) throws AgentPackageNotFoundException {
super(parent);
File agentDictionary = AgentPackagePath.getPath();
classpath = new LinkedList<File>();
classpath.add(new File(agentDictionary, "plugins"));
classpath.add(new File(agentDictionary, "activations"));
}
}
-
靜态屬性,預設單例。通過DEFAULT_LOADER
方法,可以擷取到它。#getDefault()
-
屬性,Java 類所在的目錄。在構造方法中,我們可以看到classpath
/${AGENT_PACKAGE_PATH}/plugins
添加到${AGENT_PACKAGE_PATH}/activations
。在classpath
方法中,加載該目錄下的 Jar 中的 Class 檔案。#getAllJars()
-
屬性,Jar 數組。allJars
-
屬性,Jar 讀取時的鎖。jarScanLock
#initDefaultLoader()
靜态方法,初始化預設的 AgentClassLoader ,代碼如下 :
public static AgentClassLoader initDefaultLoader() throws AgentPackageNotFoundException {
DEFAULT_LOADER = new AgentClassLoader(PluginBootstrap.class.getClassLoader());
return getDefault();
}
- 使用
的類加載器作為 AgentClassLoader 的父類加載器。org.skywalking.apm.agent.core.plugin.PluginBootstrap
如下方法已經添加相關中文注釋,胖友請自行閱讀了解 :
-
#findResource(name)
-
#findResources(String name)
-
#getAllJars()
在 ClassLoader 加載資源( 例如,類 ),會調用
#findResource(name)
/
#findResources(name)
方法。
2.2 PluginResourcesResolver
org.skywalking.apm.agent.core.plugin.PluginResourcesResolver
,插件資源解析器,讀取所有插件的定義檔案。插件定義檔案必須以
skywalking-plugin.def
命名,例如 :
#getResources()
方法,獲得插件定義路徑數組,代碼如下 :
- 第 50 行 :使用 AgentClassLoader 獲得所有
的路徑。skywalking-plugin.def
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.ControllerInstrumentation
spring-mvc-annotation-4.x=org.skywalking.apm.plugin.spring.mvc.v4.define.RestControllerInstrumentation
spring-mvc-annotation-4.x=org.skywalking.apm.plugin.spring.mvc.v4.define.HandlerMethodInstrumentation
spring-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 小結
胖友,回過頭,在看一下流程圖,了解了解。
3. 插件的比對
在 《SkyWalking 源碼分析 —— Agent 初始化》 一文,我們提到,SkyWalking Agent 基于 JavaAgent 機制,實作應用透明接入 SkyWalking 。下面筆者預設胖友已經對 JavaAgent 機制已經有一定的了解。如果胖友暫時不了解,建議先閱讀如下文章 :
- 《Instrumentation 新功能》
- 《JVM源碼分析之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
允許建立任意類,并不限于實作用于建立運作時代理的接口。
此外,
提供了一個友善的 API ,用于 Java Agent 或在建構過程中更改類。
byte-buddy
下面筆者預設胖友已經對
byte-buddy
有一定的了解。如果胖友暫不了解,建議先閱讀如下文章 :
- 《Java位元組碼3-使用ByteBuddy實作一個Java-Agent》
- 《Byte Buddy 教程》
- 《Easily Create Java Agents with Byte Buddy》
- 《skywalking源碼分析之javaAgent工具ByteBuddy的應用》 搜尋 “BYTE BUDDY應用” 部分
友情提示 :建議自己簡單使用下
byte-buddy
,更容易了解 SkyWalking Agent 。
筆者練手的
項目位址 :https://github.com/YunaiV/learning/tree/master/bytebuddy
byte-buddy
下面,讓我們打開
SkyWalkingAgent#premain(String, Instrumentation)
方法,從【第 79 行】代碼開始看 :
- 第 79 至 104 行 :建立
對象,并設定相關屬性。net.bytebuddy.agent.builder.AgentBuilder
- AgentBuilder ,提供便利的 API ,建立 Java Agent 。
- 第 79 行 :調用
方法,實作AgentBuilder#type(ElementMatcher)
接口,設定需要攔截的類。net.bytebuddy.matcher.ElementMatcher
方法,在本文 「3.3 PluginFinder」 詳細解析。PluginFinder#buildMatch()
- 第 79 至 104 行 :調用
方法,設定 Java 類的修改邏輯。AgentBuilder#transform(Transformer)
- 第 84 行 :調用
方法,獲得比對的 AbstractClassEnhancePluginDefine 數組。因為在【第 79 行】的代碼,設定了所有插件需要攔截的類,是以此處需要比對該類對應的 AbstractClassEnhancePluginDefine 數組。PluginFinder#find(TypeDescription, ClassLoader)
方法,在本文 「3.3 PluginFinder」 詳細解析。PluginFinder#find(TypeDescription, ClassLoader)
- 第 85 行 :判斷比對的 AbstractClassEnhancePluginDefine 數組大于零。從目前的代碼看下來,此處屬于防禦性程式設計,在【第 79 行】的代碼保證一定能比對到 AbstractClassEnhancePluginDefine 。
- 第 86 至 100 行 :循環比對到 AbstractClassEnhancePluginDefine 數組,調用
方法,設定AbstractClassEnhancePluginDefine#define(...)
對象。通過該對象,定義如何攔截需要修改的 Java 類。在net.bytebuddy.dynamic.DynamicType.Builder
方法的内部,會調用AbstractClassEnhancePluginDefine#define(...)
方法,本文 「4. 插件的攔截」 也會詳細解析。net.bytebuddy.dynamic.DynamicType.ImplementationDefinition#intercept(Implementation)
- 第 91 行 :為什麼會出現傳回為空的情況呢?同一個架構在不同的大版本,使用的方式相同,但是實作的代碼卻不盡相同。舉個例子,SpringMVC 3 和 SpringMVC 4 ,都有
注解定義 URL ,是以【第 84 行】會比對到@RequestMapping
/AbstractSpring3Instrumentation
兩個。當應用使用的是 Spring MVC 4 時,調用AbstractSpring4Instrumentation
方法會傳回空,而調用AbstractSpring3Instrumentation#define(...)
方法會有傳回值。這是如何實作的呢?本文 「4. 插件的攔截」 也會詳細解析。AbstractSpring4Instrumentation#define(...)
- 第 84 行 :調用
- 第 105 至 134 行 :調用
方法,添加監聽器。AgentBuilder#with(Listener)
-
方法,當 Java 類的修改成功,進行調用。#onTransformation(...)
-
方法,當 Java 類的修改失敗,進行調用。InstrumentDebuggingClass 在本文 「3.1 InstrumentDebuggingClass」 詳細解析。#onError(...)
-
- 第 135 行 :調用
方法,根據上面 AgentBuilder 設定的屬性,建立AgentBuilder#installOn(Instrumentation)
對象,配置到 Instrumentation 對象上。在net.bytebuddy.agent.builder.ResettableClassFileTransformer
方法的内部,會調用AgentBuilder#installOn(Instrumentation)
方法。Instrumentation#addTransformer(ClassFileTransformer)
😈 這個方法資訊量比較大,筆者對
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
,類比對接口。目前子類如下 :
- NameMatch :基于完整的類名進行比對,例如:
。"com.alibaba.dubbo.monitor.support.MonitorFilter"
- IndirectMatch :間接比對接口。相比 NameMatch 來說,确實比較 “委婉” 🙂 。
- ClassAnnotationMatch :基于類注解進行比對,可設定同時比對多個。例如:
。"@RequestMapping"
- HierarchyMatch :基于父類 / 接口進行比對,可設定同時比對多個。
- MethodAnnotationMatch :基于方法注解進行比對,可設定同時比對多個。目前項目裡主要用于比對方法上的
注解。[email protected]
- ClassAnnotationMatch :基于類注解進行比對,可設定同時比對多個。例如:
每個類已經添加詳細的代碼注釋,胖友喜歡哪個點哪個喲。
3.3 PluginFinder
org.skywalking.apm.agent.core.plugin.PluginFinder
,插件發現者。其提供
#find(...)
方法,獲得類增強插件定義(
org.skywalking.apm.agent.core.plugin.AbstractClassEnhancePluginDefine
)對象。
PluginFinder 構造方法,代碼如下 :
- 第 57 至 77 行 :循環 AbstractClassEnhancePluginDefine 對象數組,添加到
/nameMatchDefine
屬性,友善signatureMatchDefine
方法查找 AbstractClassEnhancePluginDefine 對象。#find(...)
- 第 65 至 72 行 :處理 NameMatch 為比對的 AbstractClassEnhancePluginDefine 對象,添加到
屬性。nameMatchDefine
- 第 74 至 76 行 :處理非 NameMatch 為比對的 AbstractClassEnhancePluginDefine 對象,添加到
屬性。signatureMatchDefine
- 第 65 至 72 行 :處理 NameMatch 為比對的 AbstractClassEnhancePluginDefine 對象,添加到
#find(...)
方法,獲得類增強插件定義(
org.skywalking.apm.agent.core.plugin.AbstractClassEnhancePluginDefine
)對象,代碼如下 :
- 第 92 至 96 行 :以
屬性來比對 AbstractClassEnhancePluginDefine 對象。nameMatchDefine
- 第 98 至 104 行 :以
屬性來比對 AbstractClassEnhancePluginDefine 對象。在這個過程中,會調用signatureMatchDefine
方法,進行比對。IndirectMatch#isMatch(TypeDescription)
#buildMatch()
方法,獲得全部插件的類比對,多個插件的類比對條件以
or
分隔,代碼如下 :
- 第 117 至 123 行 :以
屬性來比對。nameMatchDefine
- 第 124 至 132 行 :以
屬性來比對。signatureMatchDefine
- 實際上,該方法和
方法邏輯是一緻的。#find(...)
4. 插件的攔截
在上文中,我們已經提到,SkyWalking 通過 JavaAgent 機制,對需要攔截的類的方法,使用
byte-buddy
動态修改 Java 類的二進制,進而進行方法切面攔截,記錄調用鍊路。
看具體的代碼實作之前,想一下攔截會涉及到哪些元素 :
- 攔截切面 InterceptPoint
- 攔截器 Interceptor
- 攔截類的定義 Define :一個類有哪些攔截切面及對應的攔截器
下面,我們來看看本小節會涉及到的類。如圖所示:
看起來類比想象的多?梳理之,結果如圖 :
- 根據方法類型的不同,使用不同 ClassEnhancePluginDefine 的實作類。其中,構造方法和靜态方法使用相同的實作類。
- 相比上面提到攔截會涉及到的元素,多了一個 Inter ?如下是官方的說明 :
In this class, it provide a bridge between
andbyte-buddy
plugin.sky-walking
4.1 ClassEnhancePluginDefine
整體類圖如下:
- AbstractClassEnhancePluginDefine :SkyWalking 類增強插件定義抽象基類。
- ClassEnhancePluginDefine :SkyWalking 類增強插件定義抽象類。
- 從 UML 圖中的方法,我們可以看出,AbstractClassEnhancePluginDefine 注重在定義( Define ),ClassEnhancePluginDefine 注重在增強( Enhance )。
整體流程如下 :
OK ,下面我們開始看看代碼是如何實作的。
4.1.1 AbstractClassEnhancePluginDefine
org.skywalking.apm.agent.core.plugin.AbstractClassEnhancePluginDefine
,SkyWalking 類增強插件定義抽象基類。它注重在定義( Define )的抽象與實作。
#enhanceClass()
抽象方法,定義了類比對( ClassMatch ) 。
#witnessClasses()
方法,見證類清單。當且僅當應用存在見證類清單,插件才生效。什麼意思?讓我們看看這種情況:一個類庫存在兩個釋出的版本( 如
1.0
和
2.0
),其中包括相同的目标類,但不同的方法或不同的方法參數清單。是以我們需要根據庫的不同版本使用插件的不同版本。然而版本顯然不是一個選項,這時需要使用見證類清單,判斷出目前引用類庫的釋出版本。
- 舉個實際的例子,SpringMVC 3 和 SpringMVC 4 ,都有
注解定義 URL 。@RequestMapping
- 通過判斷存在
類,應用使用 SpringMVC 3 ,使用org.springframework.web.servlet.view.xslt.AbstractXsltView
。apm-springmvc-annotation-3.x-plugin.jar
- 通過判斷存在
類,應用使用 SpringMVC 4 ,使用org.springframework.web.servlet.tags.ArgumentTag
。apm-springmvc-annotation-4.x-plugin.jar
- 通過判斷存在
- 另外,該方法傳回空數組。即預設情況,插件生效,無需見證類清單。
#define(...)
方法,設定
net.bytebuddy.dynamic.DynamicType.Builder
對象。通過該對象,定義如何攔截需要修改的目标 Java 類(方法的
transformClassName
參數)。代碼如下 :
- 第 57 至 70 行 :判斷見證類清單是否都存在。若不存在,則插件不生效。
-
,已經添加完整注釋,胖友點選檢視。org.skywalking.apm.agent.core.plugin.WitnessClassFinder
-
- 第 72 至 76 行 :調用
抽象方法,使用攔截器增強目标類。#enhance(...)
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(...)
方法,增強靜态方法,代碼如下 :
- 第 206 至 210 行 :調用
方法,獲得 StaticMethodsInterceptPoint 數組。若為空,不進行增強。#getStaticMethodsInterceptPoints()
- 第 212 至 238 行 :周遊 StaticMethodsInterceptPoint 數組,逐個增強StaticMethodsInterceptPoint 對應的靜态方法。
- 第 214 至 218 行 :獲得攔截器的類名。攔截器的執行個體,在 Inter 類裡擷取。
- 第 221 至 229 行 :當
方法傳回StaticMethodsInterceptPoint#isOverrideArgs()
時,使用 StaticMethodsInterWithOverrideArgs 處理攔截邏輯。在 「4.4.3 靜态方法 Inter」 詳細解析。true
- 第 230 至 236 行 :當
方法傳回StaticMethodsInterceptPoint#isOverrideArgs()
時,使用 StaticMethodsInter 處理攔截邏輯,在 「4.4.3 靜态方法 Inter」 詳細解析。false
4.1.2.2 增強構造方法和執行個體方法
調用
#enhanceInstance()
方法,增強構造方法和執行個體方法,代碼如下 :
- 第 92 至 110 行 :調用
/#getConstructorsInterceptPoints()
方法,獲得 ConstructorInterceptPoint / InstanceMethodsInterceptPoint 數組。若都為空,不進行增強。#getInstanceMethodsInterceptPoints()
- 第 112 至 128 行 :使用
,為目标 Java 類“自動”實作byte-buddy
接口。這樣,目标 Java 類就有一個私有變量,攔截器在執行過程中,可以存儲狀态到該私有變量。這裡如果暫時不了解沒關系,後面分享每個插件的實作時,會有實際的例子,更易懂。org.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance
- ———- 構造方法 ———-
- 第 130 至 143 行 :周遊 ConstructorInterceptPoint 數組,逐個增強 ConstructorInterceptPoint 對應的構造方法。使用 ConstructorInter 處理攔截邏輯,在 「4.4.1 構造方法 Inter」 詳細解析。
- ———- 執行個體方法 ———-
- 第 145 至 175 行 :周遊 InstanceMethodsInterceptPoint 數組,逐個增強 InstanceMethodsInterceptPoint 對應的靜态方法。
- 第 151 至 154 行 :獲得攔截器的類名。攔截器的執行個體,在 Inter 類裡擷取。
- 第 156 至 165 行 :當
方法傳回InstanceMethodsInterceptPoint#isOverrideArgs()
時,使用 InstMethodsInterWithOverrideArgs 處理攔截邏輯。在 「4.4.2 執行個體方法 Inter」 詳細解析。true
- 第 166 至 173 行 :當
方法傳回InstanceMethodsInterceptPoint#isOverrideArgs()
時,使用 InstMethodsInter 處理攔截邏輯,在 「4.4.2 執行個體方法 Inter」 詳細解析。false
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 | 方法類型 | 方法比對 | 攔截器 | |
---|---|---|---|---|
StaticMethodsInterceptPoint | 靜态方法 | | | 有 |
ConstructorInterceptPoint | 構造方法 | | | 無 |
InstanceMethodsInterceptPoint | 執行個體方法 | | | 有 |
XXXInterceptPoint 接口,對應一個
net.bytebuddy.matcher.ElementMatcher
和一個攔截器。
代碼比較簡單,胖友自己檢視。
4.3 Interceptor
在開始分享 Inter 之前,我們先來看看 Interceptor 相關接口。如下圖所見:
- InstanceConstructorInterceptor ,構造方法攔截器接口。
- AroundInterceptor
- StaticMethodsAroundInterceptor ,靜态方法攔截器接口。
- InstanceMethodsAroundInterceptor ,執行個體方法攔截器接口。
- 接口方法基本一緻,下面 Inter 邏輯也基本一緻。
在 「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 | |
執行個體方法 | InstMethodsInter | InstMethodsInterWithOverrideArgs |
靜态方法 | StaticMethodsInter | StaticMethodsInterWithOverrideArgs |
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 方式攔截執行個體方法,代碼如下 :
- 第 79 至 86 行 :調用
方法,執行在執行個體方法之前的邏輯。InstanceMethodsAroundInterceptor#beforeMethod(...)
-
,方法攔截器執行結果。當調用org.skywalking.apm.agent.core.plugin.interceptor.enhance.MethodInterceptResult
方法,設定執行結果,并标記不再繼續執行。MethodInterceptResult#defineReturnValue(Object)
-
- 第 90 至 92 行 :當 MethodInterceptResult 已經有執行結果,不再執行原有方法,直接傳回結果。
- 第 94 至 96 行 :調用
方法,執行原有執行個體方法。Callable#call()
- 第 97 至 105 行 :調用
方法,處理異常。InstanceMethodsAroundInterceptor#handleMethodException(...)
- 第 107 至 113 行 :調用
方法,執行後置邏輯。InstanceMethodsAroundInterceptor#afterMethod(...)
org.skywalking.apm.agent.core.plugin.interceptor.enhance.InstMethodsInterWithOverrideArgs
,覆寫參數的執行個體方法 Inter 。
不太了解覆寫參數?有這樣一個場景,
InstanceMethodsAroundInterceptor#beforeMethod(...)
方法裡,我們修改了方法參數,并且希望原有執行個體方法執行時,使用的是修改了的方法參數,此時,就需要使用 InstMethodsInterWithOverrideArgs 。
InstMethodsInterWithOverrideArgs#intercept(...)
方法,總體邏輯和 InstMethodsInter 是一緻的,下面我們來看看差異點 :
- 第 76 行 :方法參數類型是
,并且帶有org.skywalking.apm.agent.core.plugin.interceptor.enhance.OverrideCallable
注解。[email protected]
- 第 96 行 :調用
方法,使用被前置方法修改過的參數,執行原有執行個體方法。OverrideCallable#call(args)
先來瞅瞅
@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.
簡單的來說 :
-
注解,注入一個代理對象,該對象會使用傳入的參數,調用被代理的方法。例如在 InstMethodsInterWithOverrideArgs 裡,調用@Morph
方法,會調用原有執行個體方法。OverrideCallable#call(args)
- 需要使用
設定一個接口,并且該接口的方法定義為Morph.Binder
。在 InstMethodsInterWithOverrideArgs 使用的是Object methodName(Object[])
接口。另外,調用org.skywalking.apm.agent.core.plugin.interceptor.enhance.OverrideCallable
方法的代碼如下 :Morph.Binder#install(Class<?>)
// 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 小結
總的來說,涉及到的元件,如下圖 :