寫在開篇前:
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
提取SkyWalkingAgent.premain主要代碼
public static void premain(String agentArgs, Instrumentation instrumentation) {
//解析“/config/agent.config”文件说贝,加載agent配置到Config.Agent.class
SnifferConfigInitializer.initialize(agentArgs);
//根據配置內容加載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本地服務庐橙、線程
ServiceManager.INSTANCE.boot();
//關閉服務
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();
//解析配置文件內容到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();
//根據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需要聲明哪些內容
插件工程結構:
skywalking-plugin.def文件內容:
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圖如下:
enhanceClass方法:待增強類的匹配規(guī)則
getInstanceMethodsInterceptPoints方法:待增強的實例方法和攔截處理器
getConstructorsInterceptPoints方法:待增強的構造方法和攔截處理器
getStaticMethodsInterceptPoints方法:待增強的靜態(tài)方法和攔截處理器
同樣對于靜態(tài)方法攔截器和實例方法攔截器也定義了InstanceMethodsAroundInterceptor和StaticMethodsAroundInterceptor兩個接口琳轿,都定義了beforeMethod、afterMethod耿芹、handleMethodException三個待實現(xiàn)方法崭篡。
區(qū)別在于:
靜態(tài)攔截器中模版方法的第一個參數(shù)為Class clazz,便于我們在攔截邏輯中調用父類的靜待方法吧秕,相當于super指針琉闪;
實例方法攔截器中模版方法的第一個參數(shù)為EnhancedInstance objInst,便于我們在攔截邏輯中調用父類的實例想法砸彬,相當于this指針颠毙。
對于為什么這里提到只能調用父類的靜態(tài)方法和實例方法斯入,基于我的理解如下:
首先,從字節(jié)碼文件結構本身最基本的認知來說蛀蜜,肯定是能在待增強方法的字節(jié)碼序列中插入invokestatic刻两、invokevirtual等指令調用指定的方法描述符(CONSTANT_NameAndType_info)的。
但從skywalking的agent實現(xiàn)上來說滴某,我們在增強邏輯代碼中是拿不到待增強類的類信息的磅摹,因為類增強的觸發(fā)點為類信息加載后,而代碼邏輯中要拿到一個類的類信息必須在加載霎奢、鏈接(驗證偏瓤、準備、解析)椰憋、初始化后厅克。筆者在工程實踐中的測試結果為,將上述攔截方法的第一個參數(shù)clazz或者objInst強轉為對應的類橙依,從而在攔截邏輯中使用類信息的話证舟,都會報如下類重定義異常。
java.lang.LinkageError: loader (instance of sun/misc/Launcher$AppClassLoader): attempted duplicate class definition for name
但在IDEA開發(fā)工具中直接運行代碼則不會報重定義異常窗骑,分析為IDEA中直接運行的程序會在java程序啟動命令中自動加上一些-javaagent的熱部署插件女责。