天天看點

SkyWalking 源碼分析 —— Agent 插件體系1. 概述2. 插件的加載3. 插件的比對4. 插件的攔截

1. 概述

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

  • 插件的加載
  • 插件的比對
  • 插件的攔截

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

本文涉及到的類主要在 

org.skywalking.apm.agent.core.plugin

 包裡,如下圖所示 :

SkyWalking 源碼分析 —— Agent 插件體系1. 概述2. 插件的加載3. 插件的比對4. 插件的攔截

每個流程會涉及到較多的類,我們會貫穿着解析代碼實作。

2. 插件的加載

在 《SkyWalking 源碼分析 —— Agent 初始化》 一文中,Agent 初始化時,調用 

PluginBootstrap#loadPlugins()

 方法,加載所有的插件。整體流程如下圖 :

SkyWalking 源碼分析 —— Agent 插件體系1. 概述2. 插件的加載3. 插件的比對4. 插件的攔截

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<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()

     方法,可以擷取到它。
  • 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

 命名,例如 :

SkyWalking 源碼分析 —— Agent 插件體系1. 概述2. 插件的加載3. 插件的比對4. 插件的攔截

#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 插件為例子,如下是相關類圖 :

SkyWalking 源碼分析 —— Agent 插件體系1. 概述2. 插件的加載3. 插件的比對4. 插件的攔截

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

     對象,并設定相關屬性。
    • AgentBuilder ,提供便利的 API ,建立 Java Agent 。
    • 第 79 行 :調用 

      AgentBuilder#type(ElementMatcher)

       方法,實作 

      net.bytebuddy.matcher.ElementMatcher

       接口,設定需要攔截的類。

      PluginFinder#buildMatch()

       方法,在本文 「3.3 PluginFinder」 詳細解析。
    • 第 79 至 104 行 :調用 

      AgentBuilder#transform(Transformer)

       方法,設定 Java 類的修改邏輯。
      • 第 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. 插件的攔截」 也會詳細解析。
  • 第 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

 ,效果如下圖 :

SkyWalking 源碼分析 —— Agent 插件體系1. 概述2. 插件的加載3. 插件的比對4. 插件的攔截

代碼比較簡單,胖友點選 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

 ,類比對接口。目前子類如下 :

SkyWalking 源碼分析 —— Agent 插件體系1. 概述2. 插件的加載3. 插件的比對4. 插件的攔截
  • NameMatch :基于完整的類名進行比對,例如:

    "com.alibaba.dubbo.monitor.support.MonitorFilter"

     。
  • IndirectMatch :間接比對接口。相比 NameMatch 來說,确實比較 “委婉” 🙂 。
    • ClassAnnotationMatch :基于類注解進行比對,可設定同時比對多個。例如:

      "@RequestMapping"

    • HierarchyMatch :基于父類 / 接口進行比對,可設定同時比對多個。
    • MethodAnnotationMatch :基于方法注解進行比對,可設定同時比對多個。目前項目裡主要用于比對方法上的 

      [email protected]

       注解。

每個類已經添加詳細的代碼注釋,胖友喜歡哪個點哪個喲。

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 :一個類有哪些攔截切面及對應的攔截器

下面,我們來看看本小節會涉及到的類。如圖所示:

SkyWalking 源碼分析 —— Agent 插件體系1. 概述2. 插件的加載3. 插件的比對4. 插件的攔截

看起來類比想象的多?梳理之,結果如圖 :

SkyWalking 源碼分析 —— Agent 插件體系1. 概述2. 插件的加載3. 插件的比對4. 插件的攔截
  • 根據方法類型的不同,使用不同 ClassEnhancePluginDefine 的實作類。其中,構造方法和靜态方法使用相同的實作類。
  • 相比上面提到攔截會涉及到的元素,多了一個 Inter ?如下是官方的說明 :
    In this class, it provide a bridge between 

    byte-buddy

     and 

    sky-walking

     plugin.

4.1 ClassEnhancePluginDefine

整體類圖如下:

SkyWalking 源碼分析 —— Agent 插件體系1. 概述2. 插件的加載3. 插件的比對4. 插件的攔截
  • AbstractClassEnhancePluginDefine :SkyWalking 類增強插件定義抽象基類。
  • ClassEnhancePluginDefine :SkyWalking 類增強插件定義抽象類。
  • 從 UML 圖中的方法,我們可以看出,AbstractClassEnhancePluginDefine 注重在定義( Define ),ClassEnhancePluginDefine 注重在增強( Enhance )。

整體流程如下 :

SkyWalking 源碼分析 —— Agent 插件體系1. 概述2. 插件的加載3. 插件的比對4. 插件的攔截

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

SkyWalking 源碼分析 —— Agent 插件體系1. 概述2. 插件的加載3. 插件的比對4. 插件的攔截
  • 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.

簡單的來說 :

  • @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 小結

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

SkyWalking 源碼分析 —— Agent 插件體系1. 概述2. 插件的加載3. 插件的比對4. 插件的攔截

繼續閱讀