写在开篇前:
github地址:https://github.com/apache/skywalking.git
介绍的skywalking版本:6.5.0
agent配置参数:
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,生效每一个插件的匹配规则、拦截逻辑
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"));
}
本小节梳理agent插件加载流程
1.PluginBootstrap.loadPlugins 插件配置文件解析
public List loadPlugins() {
//查找class路径下所有的skywalking-plugin.def文件
PluginResourcesResolver resolver = new PluginResourcesResolver();
List resources = resolver.getResources();
//解析配置文件内容到List
//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 pluginClassList = PluginCfg.INSTANCE.getPluginClassList();
//根据List实例化插件配置类
List plugins = new ArrayList();
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
声明两个要素:待增强类的匹配规则、待增强的方法和拦截处理器
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 getMethodsMatcher() {
return named("invoke");
}
@Override
public String getMethodsInterceptor() {
return INTERCEPT_CLASS;
}
@Override
public boolean isOverrideArgs() {
return false;
}
}
};
}
}
skywalking agent针对静态方法和实例方法增强分别定义了两个不同的抽象模版类,都继承自ClassEnhancePluginDefine.class
ER图如下:
enhanceClass方法:待增强类的匹配规则
getInstanceMethodsInterceptPoints方法:待增强的实例方法和拦截处理器
getConstructorsInterceptPoints方法:待增强的构造方法和拦截处理器
getStaticMethodsInterceptPoints方法:待增强的静态方法和拦截处理器
同样对于静态方法拦截器和实例方法拦截器也定义了InstanceMethodsAroundInterceptor和StaticMethodsAroundInterceptor两个接口,都定义了beforeMethod、afterMethod、handleMethodException三个待实现方法。
区别在于:
静态拦截器中模版方法的第一个参数为Class clazz,便于我们在拦截逻辑中调用父类的静待方法,相当于super指针;
实例方法拦截器中模版方法的第一个参数为EnhancedInstance objInst,便于我们在拦截逻辑中调用父类的实例想法,相当于this指针。
对于为什么这里提到只能调用父类的静态方法和实例方法,基于我的理解如下:
首先,从字节码文件结构本身最基本的认知来说,肯定是能在待增强方法的字节码序列中插入invokestatic、invokevirtual等指令调用指定的方法描述符(CONSTANT_NameAndType_info)的。
但从skywalking的agent实现上来说,我们在增强逻辑代码中是拿不到待增强类的类信息的,因为类增强的触发点为类信息加载后,而代码逻辑中要拿到一个类的类信息必须在加载、链接(验证、准备、解析)、初始化后。笔者在工程实践中的测试结果为,将上述拦截方法的第一个参数clazz或者objInst强转为对应的类,从而在拦截逻辑中使用类信息的话,都会报如下类重定义异常。
java.lang.LinkageError: loader (instance of sun/misc/Launcher$AppClassLoader): attempted duplicate class definition for name
但在IDEA开发工具中直接运行代码则不会报重定义异常,分析为IDEA中直接运行的程序会在java程序启动命令中自动加上一些-javaagent的热部署插件。