歡迎光臨
每天分享高質量文章

鏈路追蹤 SkyWalking 原始碼分析 —— Agent 外掛體系

點選上方“芋道原始碼”,選擇“設為星標

做積極的人,而不是積極廢人!

原始碼精品專欄

 

摘要: 原創出處 http://www.iocoder.cn/SkyWalking/agent-plugin-system/ 「芋道原始碼」歡迎轉載,保留摘要,謝謝!

本文主要基於 SkyWalking 3.2.6 正式版

  • 1. 概述
  • 2. 外掛的載入
  • 3. 外掛的匹配
  • 4. 外掛的攔截

1. 概述

本文主要分享 SkyWalking Agent 外掛體系。主要涉及三個流程 :

  • 外掛的載入
  • 外掛的匹配
  • 外掛的攔截

可能看起來有點抽象,不太容易理解。淡定,我們每個小章節進行解析。

本文涉及到的類主要在 org.skywalking.apm.agent.core.plugin 包裡,如下圖所示 :

每個流程會涉及到較多的類,我們會貫穿著解析程式碼實現。

2. 外掛的載入

在 《SkyWalking 原始碼分析 —— Agent 初始化》 一文中,Agent 初始化時,呼叫 PluginBootstrap#loadPlugins() 方法,載入所有的外掛。整體流程如下圖 :

PluginBootstrap#loadPlugins() 方法,程式碼如下 :

  • 第 47 行 :呼叫 AgentClassLoader#initDefaultLoader() 方法,初始化 AgentClassLoader 。在本文 「2.1 AgentClassLoader」 詳細解析。
  • 第 50 至 56 行 :獲得外掛定義路徑陣列。在本文 「2.2 PluginResourcesResolver」 詳細解析。
  • 第 59 至 66 行 :獲得外掛定義( `org.skywalking.apm.agent.core.plugin.PluginDefine` )陣列。在本文 「2.3 PluginCfg」 詳細解析。
  • 第 69 至 82 行 :建立類增強外掛定義( `org.skywalking.apm.agent.core.plugin.AbstractClassEnhancePluginDefine` )物件陣列。不同外掛透過實現 AbstractClassEnhancePluginDefine 抽象類,定義不同框架的切麵記錄呼叫鏈路。在本文 「2.4 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 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"));
    }
}
  • DEFAULT_LOADER 靜態屬性,預設單例。透過 `#getDefault()` 方法,可以獲取到它。
  • classpath 屬性,Java 類所在的目錄。在構造方法中,我們可以看到 ${AGENT_PACKAGE_PATH}/plugins / ${AGENT_PACKAGE_PATH}/activations 新增到 classpath 。在 `#getAllJars()` 方法中,載入該目錄下的 Jar 中的 Class 檔案。
  • allJars 屬性,Jar 陣列。
  • jarScanLock 屬性,Jar 讀取時的

#initDefaultLoader() 靜態方法,初始化預設的 AgentClassLoader ,程式碼如下 :

public static AgentClassLoader initDefaultLoader() throws AgentPackageNotFoundException {
    DEFAULT_LOADER = new AgentClassLoader(PluginBootstrap.class.getClassLoader());
    return getDefault();
}
  • 使用 org.skywalking.apm.agent.core.plugin.PluginBootstrap 的類載入器作為 AgentClassLoader 的父類載入器

如下方法已經新增相關中文註釋,胖友請自行閱讀理解 :

  • `#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 允許建立任意類,並不限於實現用於建立執行時代理的介面。

此外,byte-buddy 提供了一個方便的 API ,用於 Java Agent 或在構建過程中更改類。

下麵筆者預設胖友已經對 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 。

筆者練手的 byte-buddy 專案地址 :https://github.com/YunaiV/learning/tree/master/bytebuddy


下麵,讓我們開啟 SkyWalkingAgent#premain(String, Instrumentation) 方法,從【第 79 行】程式碼開始看 :

  • 第 79 至 104 行 :建立 `net.bytebuddy.agent.builder.AgentBuilder` 物件,並設定相關屬性。
    • 第 84 行 :呼叫 `PluginFinder#find(TypeDescription, ClassLoader)` 方法,獲得匹配的 AbstractClassEnhancePluginDefine 陣列。因為在【第 79 行】的程式碼,設定了所有外掛需要攔截的類,所以此處需要匹配該類對應的 AbstractClassEnhancePluginDefine 陣列。`PluginFinder#find(TypeDescription, ClassLoader)` 方法,在本文 「3.3 PluginFinder」 詳細解析。
    • 第 85 行 :判斷匹配的 AbstractClassEnhancePluginDefine 陣列大於零。從目前的程式碼看下來,此處屬於防禦性程式設計,在【第 79 行】的程式碼保證一定能匹配到 AbstractClassEnhancePluginDefine 。
    • 第 86 至 100 行 :迴圈匹配到 AbstractClassEnhancePluginDefine 陣列,呼叫 `AbstractClassEnhancePluginDefine#define(…)` 方法,設定 `net.bytebuddy.dynamic.DynamicType.Builder` 物件。透過該物件,定義如何攔截需要修改的 Java 類。在 `AbstractClassEnhancePluginDefine#define(…)` 方法的內部,會呼叫 `net.bytebuddy.dynamic.DynamicType.ImplementationDefinition#intercept(Implementation)`方法,本文 「4. 外掛的攔截」 也會詳細解析。
    • 第 91 行 :為什麼會出現傳回為的情況呢?同一個框架在不同的版本,使用的方式相同,但是實現的程式碼卻不盡相同。舉個例子,SpringMVC 3 和 SpringMVC 4 ,有 `@RequestMapping` 註解定義 URL ,所以【第 84 行】會匹配到 `AbstractSpring3Instrumentation` / `AbstractSpring4Instrumentation` 兩個。當應用使用的是 Spring MVC 4 時,呼叫 `AbstractSpring3Instrumentation#define(…)` 方法會傳回空,而呼叫 `AbstractSpring4Instrumentation#define(…)` 方法會有傳回值。這是如何實現的呢?本文 「4. 外掛的攔截」 也會詳細解析。
    • AgentBuilder ,提供便利的 API ,建立 Java Agent 。
    • 第 79 行 :呼叫 `AgentBuilder#type(ElementMatcher)` 方法,實現 `net.bytebuddy.matcher.ElementMatcher` 介面,設定需要攔截的類。`PluginFinder#buildMatch()` 方法,在本文 「3.3 PluginFinder」 詳細解析。
    • 第 79 至 104 行 :呼叫 `AgentBuilder#transform(Transformer)` 方法,設定 Java 類的修改邏輯。
  • 第 105 至 134 行 :呼叫 `AgentBuilder#with(Listener)` 方法,新增監聽器。
    • `#onTransformation(…)` 方法,當 Java 類的修改成功,進行呼叫。
    • `#onError(…)` 方法,當 Java 類的修改失敗,進行呼叫。InstrumentDebuggingClass 在本文 「3.1 InstrumentDebuggingClass」 詳細解析。
  • 第 135 行 :呼叫 `AgentBuilder#installOn(Instrumentation)` 方法,根據上面 AgentBuilder 設定的屬性,建立 `net.bytebuddy.agent.builder.ResettableClassFileTransformer` 物件,配置到 Instrumentation 物件上。在 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 :基於方法註解進行匹配,可設定同時匹配多個。目前專案裡主要用於匹配方法上的 `org.skywalking.apm.toolkit.trace.@Trace` 註解。

每個類已經新增詳細的程式碼註釋,胖友喜歡哪個點哪個喲。

3.3 PluginFinder

org.skywalking.apm.agent.core.plugin.PluginFinder ,外掛發現者。其提供 #find(...) 方法,獲得類增強外掛定義org.skywalking.apm.agent.core.plugin.AbstractClassEnhancePluginDefine )物件。

PluginFinder 構造方法,程式碼如下 :

  • 第 57 至 77 行 :迴圈 AbstractClassEnhancePluginDefine 物件陣列,新增到 nameMatchDefine / signatureMatchDefine 屬性,方便 #find(…) 方法查詢 AbstractClassEnhancePluginDefine 物件。
    • 第 65 至 72 行 :處理 NameMatch 為匹配的 AbstractClassEnhancePluginDefine 物件,新增到 `nameMatchDefine` 屬性。
    • 第 74 至 76 行 :處理 NameMatch 為匹配的 AbstractClassEnhancePluginDefine 物件,新增到 `signatureMatchDefine` 屬性。

#find(...) 方法,獲得類增強外掛定義org.skywalking.apm.agent.core.plugin.AbstractClassEnhancePluginDefine )物件,程式碼如下 :

  • 第 92 至 96 行 :以 nameMatchDefine 屬性來匹配 AbstractClassEnhancePluginDefine 物件。
  • 第 98 至 104 行 :以 signatureMatchDefine 屬性來匹配 AbstractClassEnhancePluginDefine 物件。在這個過程中,會呼叫 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 byte-buddy and sky-walking plugin.

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 ,有 @RequestMapping 註解定義 URL 。
    • 透過判斷存在 `org.springframework.web.servlet.view.xslt.AbstractXsltView` 類,應用使用 SpringMVC 3 ,使用 `apm-springmvc-annotation-3.x-plugin.jar` 。
    • 透過判斷存在 `org.springframework.web.servlet.tags.ArgumentTag` 類,應用使用 SpringMVC 4 ,使用 `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 行 :呼叫 #getStaticMethodsInterceptPoints() 方法,獲得 StaticMethodsInterceptPoint 陣列。若為,不進行增強。
  • 第 212 至 238 行 :遍歷 StaticMethodsInterceptPoint 陣列,逐個增強StaticMethodsInterceptPoint 對應的靜態方法。
    • 第 214 至 218 行 :獲得攔截器的類名。攔截器的實體,在 Inter 類裡獲取。
    • 第 221 至 229 行 :當 `StaticMethodsInterceptPoint#isOverrideArgs()` 方法傳回 `true` 時,使用 StaticMethodsInterWithOverrideArgs 處理攔截邏輯。在 「4.4.3 靜態方法 Inter」 詳細解析。
    • 第 230 至 236 行 :當 `StaticMethodsInterceptPoint#isOverrideArgs()` 方法傳回 `false` 時,使用 StaticMethodsInter 處理攔截邏輯,在 「4.4.3 靜態方法 Inter」 詳細解析。

4.1.2.2 增強構造方法和實體方法

呼叫 #enhanceInstance() 方法,增強構造方法和實體方法,程式碼如下 :

  • 第 92 至 110 行 :呼叫 #getConstructorsInterceptPoints() / #getInstanceMethodsInterceptPoints() 方法,獲得 ConstructorInterceptPoint / InstanceMethodsInterceptPoint 陣列。若,不進行增強。
  • 第 112 至 128 行 :使用 byte-buddy ,為標的 Java 類“自動”實現 `org.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance` 介面。這樣,標的 Java 類就有一個私有變數,攔截器在執行過程中,可以儲存狀態到該私有變數。這裡如果暫時不理解沒關係,後面分享每個外掛的實現時,會有實際的例子,更易懂。
  • ———- 構造方法 ———-
  • 第 130 至 143 行 :遍歷 ConstructorInterceptPoint 陣列,逐個增強 ConstructorInterceptPoint 對應的構造方法。使用 ConstructorInter 處理攔截邏輯,在 「4.4.1 構造方法 Inter」 詳細解析。
  • ———- 實體方法 ———-
  • 第 145 至 175 行 :遍歷 InstanceMethodsInterceptPoint 陣列,逐個增強 InstanceMethodsInterceptPoint 對應的靜態方法。
    • 第 151 至 154 行 :獲得攔截器的類名。攔截器的實體,在 Inter 類裡獲取。
    • 第 156 至 165 行 :當 `InstanceMethodsInterceptPoint#isOverrideArgs()` 方法傳回 `true` 時,使用 InstMethodsInterWithOverrideArgs 處理攔截邏輯。在 「4.4.2 實體方法 Inter」 詳細解析。
    • 第 166 至 173 行 :當 `InstanceMethodsInterceptPoint#isOverrideArgs()` 方法傳回 `false` 時,使用 InstMethodsInter 處理攔截邏輯,在 「4.4.2 實體方法 Inter」 詳細解析。

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 相關介面。如下圖所見:

  • 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(...) 方法。

為什麼沒有 ConstructorInterWithOverrideArgsInstanceConstructorInterceptor#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`,並且帶有 `net.bytebuddy.implementation.bind.annotation.@Morph` 註解。
  • 第 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.

簡單的來說 :

  • @Morph 註解,註入一個代理物件,該物件會使用傳入的引數,呼叫被代理的方法。例如在 InstMethodsInterWithOverrideArgs 裡,呼叫 OverrideCallable#call(args) 方法,會呼叫原有實體方法。

  • 需要使用 Morph.Binder 設定一個介面,並且該介面的方法定義為 Object methodName(Object[])。在 InstMethodsInterWithOverrideArgs 使用的是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 小結

總的來說,涉及到的元件,如下圖 :

胖友再梳理梳理。

贊(1)

分享創造快樂