skywalking-agent初始化流程(一)-插件加載和插件定義

寫在開篇前:
github地址:https://github.com/apache/skywalking.git
介紹的skywalking版本:6.5.0

agent配置參數(shù):
java -javaagent://Users/xxx/agent/skywalking-agent.jar -jar helloword.jar

查看skywalking-agent.jar::META-INF/MANIFEST.MF
找到agent入口類:org.apache.skywalking.apm.agent.SkyWalkingAgent


image.png

提取SkyWalkingAgent.premain主要代碼

public static void premain(String agentArgs, Instrumentation instrumentation) {
        //解析“/config/agent.config”文件,加載agent配置到Config.Agent.class
        SnifferConfigInitializer.initialize(agentArgs);
        
        //根據(jù)配置內(nèi)容加載agent插件
        final PluginFinder pluginFinder;
        pluginFinder = new PluginFinder(new PluginBootstrap().loadPlugins());
        
        //使用上一步加載到的插件定義transformer,生效每一個插件的匹配規(guī)則、攔截邏輯
        agentBuilder
            .type(pluginFinder.buildMatch())
            .transform(new Transformer(pluginFinder))
            .with(AgentBuilder.RedefinitionStrategy.RETRANSFORMATION)
            .with(new Listener())
            .installOn(instrumentation);
        
        //啟動一系列agent本地服務(wù)、線程
        ServiceManager.INSTANCE.boot();
        
        //關(guān)閉服務(wù)
        Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
            @Override public void run() {
                ServiceManager.INSTANCE.shutdown();
            }
        }, "skywalking service shutdown thread"));
}

本小節(jié)梳理agent插件加載流程

1.PluginBootstrap.loadPlugins 插件配置文件解析

public List<AbstractClassEnhancePluginDefine> loadPlugins() {
        //查找class路徑下所有的skywalking-plugin.def文件
        PluginResourcesResolver resolver = new PluginResourcesResolver();
        List<URL> resources = resolver.getResources();
        
        //解析配置文件內(nèi)容到List<PluginDefine>
        //PluginDefine.class包含了插件名稱name和插件定義類名稱defineClass
        for (URL pluginUrl : resources) {
            try {
                PluginCfg.INSTANCE.load(pluginUrl.openStream());
            } catch (Throwable t) {
                logger.error(t, "plugin file [{}] init failure.", pluginUrl);
            }
        }
        List<PluginDefine> pluginClassList = PluginCfg.INSTANCE.getPluginClassList();

        //根據(jù)List<PluginDefine>實例化插件配置類
        List<AbstractClassEnhancePluginDefine> plugins = new ArrayList<AbstractClassEnhancePluginDefine>();
        for (PluginDefine pluginDefine : pluginClassList) {
                AbstractClassEnhancePluginDefine plugin =(AbstractClassEnhancePluginDefine)Class.forName(pluginDefine.getDefineClass(),
                        true,
                        AgentClassLoader.getDefault())
                        .newInstance();
                plugins.add(plugin);
        }
        //另一種配置方式加載插件
        plugins.addAll(DynamicPluginLoader.INSTANCE.load(AgentClassLoader.getDefault()));

        return plugins;
    }

2.以dubbo插件為例來看一下插件定義類AbstractClassEnhancePluginDefine需要聲明哪些內(nèi)容
插件工程結(jié)構(gòu):

image.png

skywalking-plugin.def文件內(nèi)容:
image.png

dubbo插件定義:DubboInstrumentation.class
聲明兩個要素:待增強類的匹配規(guī)則、待增強的方法和攔截處理器

public class DubboInstrumentation extends ClassInstanceMethodsEnhancePluginDefine {
    private static final String ENHANCE_CLASS = "org.apache.dubbo.monitor.support.MonitorFilter";

    private static final String INTERCEPT_CLASS = "org.apache.skywalking.apm.plugin.asf.dubbo.DubboInterceptor";

    @Override
    protected ClassMatch enhanceClass() {
        return NameMatch.byName(ENHANCE_CLASS);
    }

    @Override
    public ConstructorInterceptPoint[] getConstructorsInterceptPoints() {
        return null;
    }

    @Override
    public InstanceMethodsInterceptPoint[] getInstanceMethodsInterceptPoints() {
        return new InstanceMethodsInterceptPoint[] {
            new InstanceMethodsInterceptPoint() {
                @Override
                public ElementMatcher<MethodDescription> getMethodsMatcher() {
                    return named("invoke");
                }

                @Override
                public String getMethodsInterceptor() {
                    return INTERCEPT_CLASS;
                }

                @Override
                public boolean isOverrideArgs() {
                    return false;
                }
            }
        };
    }
}

skywalking agent針對靜態(tài)方法和實例方法增強分別定義了兩個不同的抽象模版類,都繼承自ClassEnhancePluginDefine.class
ER圖如下:


image.png

enhanceClass方法:待增強類的匹配規(guī)則
getInstanceMethodsInterceptPoints方法:待增強的實例方法和攔截處理器
getConstructorsInterceptPoints方法:待增強的構(gòu)造方法和攔截處理器
getStaticMethodsInterceptPoints方法:待增強的靜態(tài)方法和攔截處理器

同樣對于靜態(tài)方法攔截器和實例方法攔截器也定義了InstanceMethodsAroundInterceptor和StaticMethodsAroundInterceptor兩個接口,都定義了beforeMethod、afterMethod、handleMethodException三個待實現(xiàn)方法。
區(qū)別在于:
靜態(tài)攔截器中模版方法的第一個參數(shù)為Class clazz,便于我們在攔截邏輯中調(diào)用父類的靜待方法,相當于super指針;
實例方法攔截器中模版方法的第一個參數(shù)為EnhancedInstance objInst,便于我們在攔截邏輯中調(diào)用父類的實例想法,相當于this指針。

對于為什么這里提到只能調(diào)用父類的靜態(tài)方法和實例方法,基于我的理解如下:

首先,從字節(jié)碼文件結(jié)構(gòu)本身最基本的認知來說,肯定是能在待增強方法的字節(jié)碼序列中插入invokestatic、invokevirtual等指令調(diào)用指定的方法描述符(CONSTANT_NameAndType_info)的。
但從skywalking的agent實現(xiàn)上來說,我們在增強邏輯代碼中是拿不到待增強類的類信息的,因為類增強的觸發(fā)點為類信息加載后,而代碼邏輯中要拿到一個類的類信息必須在加載、鏈接(驗證、準備、解析)、初始化后。筆者在工程實踐中的測試結(jié)果為,將上述攔截方法的第一個參數(shù)clazz或者objInst強轉(zhuǎn)為對應(yīng)的類,從而在攔截邏輯中使用類信息的話,都會報如下類重定義異常。

java.lang.LinkageError: loader (instance of  sun/misc/Launcher$AppClassLoader): attempted  duplicate class definition for name

但在IDEA開發(fā)工具中直接運行代碼則不會報重定義異常,分析為IDEA中直接運行的程序會在java程序啟動命令中自動加上一些-javaagent的熱部署插件。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
禁止轉(zhuǎn)載,如需轉(zhuǎn)載請通過簡信或評論聯(lián)系作者。

友情鏈接更多精彩內(nèi)容