2. 启动流程
2. 启动流程
📌 快速导航:
2.1入口函数
入口函数:org.apache.skywalking.apm.agent.SkyWalkingAgent#premain
2.2读取配置
SnifferConfigInitializer.initializeCoreConfig(agentArgs);
读取配置的时候,流程是这样的:
- 先读取配置文件
- 再获取环境变量
- 最后读取命令行
这样相同的参数,如果都进行了配置的话,后面的就会覆盖前面的值。
因此,配置的优先级是:
重要
命令行 > 环境变量 > 配置文件
2.3加载插件声明并实例化插件对象
new PluginBootstrap().loadPlugins();
用于加载所有插件的jar的路径,即包含skywalking 自己的定义文件skywalking-plugin.def
的jar包的路径,以便于类加载器后续去加载他们。
def 插件声明文件名称是 skywalking-plugin.def
, 放在插件的 resource
目录下。
def文件格式:
activemq-5.x=org.apache.skywalking.apm.plugin.activemq.define.ActiveMQProducerInstrumentation
activemq-5.x=org.apache.skywalking.apm.plugin.activemq.define.ActiveMQConsumerInstrumentation
org.apache.skywalking.apm.agent.core.plugin.PluginBootstrap#loadPlugins
提示
概括:
- 使用类加载器读取插件jar包
- 读取jar中def文件,将def中的定义实例化成:
PluginDefine
- 使用类加载器加载def文件中声明的插件类
- 将插件类交给
PluginFinder
管理
2.3.1 定义默认的类加载器
这里使用了自己创建的类加载器 AgentClassLoader
, 父加载器是 applicationClassLoader.
public static void initDefaultLoader() throws AgentPackageNotFoundException {
if (DEFAULT_LOADER == null) {
synchronized (AgentClassLoader.class) {
if (DEFAULT_LOADER == null) {
// 初始化了默认的类加载器,父类是 调用类:PluginBootstrap
DEFAULT_LOADER = new AgentClassLoader(PluginBootstrap.class.getClassLoader());
}
}
}
}
构造器
从 自定义类加载器可以看到,他获取到了
agent
的jar路径,然后将"plugins", "activations"
这两个目录也加入了classpath
中,classpath 就是要查找插件的基础目录。
public AgentClassLoader(ClassLoader parent) throws AgentPackageNotFoundException {
super(parent);
// agent jar包所在的路径
File agentDictionary = AgentPackagePath.getPath();
classpath = new LinkedList<>();
// public static List<String> MOUNT = Arrays.asList("plugins", "activations");
// 将MOUNT下面两个路径加入到classpath中,会从这两个文件中加载插件。
Config.Plugin.MOUNT.forEach(mountFolder -> classpath.add(new File(agentDictionary, mountFolder)));
}
2.3.2 读取插件目录下的所有包含声明文件def的jar路径
/*
* 加载所有插件
*
* @return plugin definition list.
*/
public List<AbstractClassEnhancePluginDefine> loadPlugins() throws AgentPackageNotFoundException {
// 初始化自定义类加载器
AgentClassLoader.initDefaultLoader();
PluginResourcesResolver resolver = new PluginResourcesResolver();
// 1. 获取所有含有 skywalking-plugin.def 的jar
List<URL> resources = resolver.getResources();
// 2. PluginCfg加载所有的插件并生成 pluginDefine 方便使用。
for (URL pluginUrl : resources) {
PluginCfg.INSTANCE.load(pluginUrl.openStream());
}
List<PluginDefine> pluginClassList = PluginCfg.INSTANCE.getPluginClassList();
// 3. 实例化插件对象
List<AbstractClassEnhancePluginDefine> plugins = new ArrayList<>();
for (PluginDefine pluginDefine : pluginClassList) {
try {
AbstractClassEnhancePluginDefine plugin = Class.forName(pluginDefine.getDefineClass(), true, AgentClassLoader.getDefault())
.newInstance();
plugin.setPluginName(pluginDefine.getName());
plugins.add(plugin);
}
// 4. 加载动态插件,什么叫动态插件?
plugins.addAll(DynamicPluginLoader.INSTANCE.load(AgentClassLoader.getDefault()));
// 返回所有的插件对象
return plugins;
}
代码解释:
2.3.2.1 查询含有插件jar路径集合
PluginResourcesResolver用于查找所有的声明文件skywalking-plugin.def
, 再这里。sw 自定义的类加载器就派上用场了,正常的类加载器只加载Java程序classpath下的类文件,而插件一般都是放到 插件目录,默认的jvm类加载器不会加载,自定义的加载器根据配置的插件目录,就会去这些目录查找所有的jar包,将其加载。
PluginResourcesResolver加载插件逻辑
- 使用AgentClassLoader获取所有插件目录中以
.jar
为后缀的文件, - 检查jar中是否函数声明文件
skywalking-plugin.def
,如果有,返回当前jar的url,方便读取
重点看一下 AgentClassLoader#findResources
, 看下类加载器是如何加载jar包的。
首先调用的是:doGetJars
,找到所有agent插件目录下的所有jar包
private LinkedList<Jar> doGetJars() {
LinkedList<Jar> jars = new LinkedList<>();
// 从插件目录加载所有的jar文件
// classpath 里就是两个插件的目录集合
for (File path : classpath) {
String[] jarFileNames = path.list((dir, name) -> name.endsWith(".jar"));
for (String fileName : jarFileNames) {
File file = new File(path, fileName);
Jar jar = new Jar(new JarFile(file), file);
jars.add(jar);
}
}
return jars;
}
找到所有的jar之后,需要对jar进行过滤,只需要哪些jar中含有 skywalking-plugin.def
文件的jar
@Override
protected Enumeration<URL> findResources(String name) throws IOException {
List<URL> allResources = new LinkedList<>();
// 1. 找到插件目录下的所有jar包
List<Jar> allJars = getAllJars();
for (Jar jar : allJars) {
// 2.过滤jar包
JarEntry entry = jar.jarFile.getJarEntry(name);
if (entry != null) {
allResources.add(new URL("jar:file:" + jar.sourceFile.getAbsolutePath() + "!/" + name));
}
}
}
2.3.2.2 PluginCfg加载和校验声明文件
PluginCfg 会对声明文件进行读取和内容校验,并且会将def文件转换成pluginDefine
对象,方便Java中使用。
PluginDefine结构
public class PluginDefine {
/**
* Plugin name.
*/
private String name;
/**
* The class name of plugin defined.
*/
private String defineClass;
}
- 1.读取
void load(InputStream input) throws IOException {
// 1. 读取def文件
BufferedReader reader = new BufferedReader(new InputStreamReader(input));
String pluginDefine;
while ((pluginDefine = reader.readLine()) != null) {
// 跳过注释和空文件,注释都是# 开头
if (pluginDefine.trim().length() == 0 || pluginDefine.startsWith("#")) {
continue;
}
// 创建插件定义对象,只是维护了插件的名字和插件类路径
PluginDefine plugin = PluginDefine.build(pluginDefine);
pluginClassList.add(plugin);
}
}
- 2.校验和转换
public static PluginDefine build(String define) throws IllegalPluginDefineException {
// def 声明文件不能为空
if (StringUtil.isEmpty(define)) {
throw new IllegalPluginDefineException(define);
}
// 必须符合规范 如:active=org.apache.activeInteceptor
String[] pluginDefine = define.split("=");
if (pluginDefine.length != 2) {
throw new IllegalPluginDefineException(define);
}
String pluginName = pluginDefine[0];
String defineClass = pluginDefine[1];
// 创建插件def对象,注意,这只是声明对象。不是实际的插件
return new PluginDefine(pluginName, defineClass);
}
总结一下:
- 首先读取def文件的内容
- 跳过注释
- 校验def文件是否合法
- 创建def对象 PluginDefine
2.3.2.3 创建插件对象
这里会使用自定义的类加载器AgentClassLoader
加载插件类,并将其实例化。
List<AbstractClassEnhancePluginDefine> plugins = new ArrayList<AbstractClassEnhancePluginDefine>();
for (PluginDefine pluginDefine : pluginClassList) {
// 创建插件对象
AbstractClassEnhancePluginDefine plugin = (AbstractClassEnhancePluginDefine) Class.forName(pluginDefine.getDefineClass(), true, AgentClassLoader
.getDefault()).newInstance();
plugin.setPluginName(pluginDefine.getName());
// 管理插件
plugins.add(plugin);
}
2.3.2.4 加载动态插件
动态插件实现原理:
- 使用Java SPI 机制加载动态插件加载器 InstrumentationLoader
- 委托InstrumentationLoader
去自定义加载插件
- 将插件加入sw的插件集合
public List<AbstractClassEnhancePluginDefine> load(AgentClassLoader classLoader) {
List<AbstractClassEnhancePluginDefine> all = new ArrayList<AbstractClassEnhancePluginDefine>();
for (InstrumentationLoader instrumentationLoader : ServiceLoader.load(InstrumentationLoader.class, classLoader)) {
List<AbstractClassEnhancePluginDefine> plugins = instrumentationLoader.load(classLoader);
if (plugins != null && !plugins.isEmpty()) {
all.addAll(plugins);
}
}
return all;
}
2.3创建插PluginFinder
这个东西是对上一步插件list的包装,就是为了更方便的查找插件,不至于每次都去遍历插件list
他将插件分为三种:
- 通过名称匹配:插件会放到
nameMatchDefine
- 通过名称匹配
NameMatch
- 通过名称匹配
- 标识符匹配:放到
signatureMatchDefine
- 通过类型匹配
- 引导类插件:放到
bootstrapClassMatchDefine
查找的时候,就会变得方便。
public PluginFinder(List<AbstractClassEnhancePluginDefine> plugins) {
for (AbstractClassEnhancePluginDefine plugin : plugins) {
ClassMatch match = plugin.enhanceClass();
//名称匹配
if (match instanceof NameMatch) {
NameMatch nameMatch = (NameMatch) match;
var pluginDefines = nameMatchDefine.get(nameMatch.getClassName());
if (pluginDefines == null) {
pluginDefines = new LinkedList<>();
nameMatchDefine.put(nameMatch.getClassName(), pluginDefines);
}
pluginDefines.add(plugin);
} else {
// 签名标识匹配
signatureMatchDefine.add(plugin);
}
// 引导类
if (plugin.isBootstrapInstrumentation()) {
bootstrapClassMatchDefine.add(plugin);
}
}
}
2.5增强Java类
入口函数:
installClassTransformer(instrumentation, pluginFinder);
这个函数是整个agent最核心的函数,他将前面所有的组件和组件加载的资源进行整合。
通过插件对目标类进行增强,
先看代码:
static void installClassTransformer(Instrumentation instrumentation, PluginFinder pluginFinder) {
LOGGER.info("Skywalking agent begin to install transformer ...");
// 忽略这些包,即不对这些包进行增强
// 优先使用忽略规则:在应用 type 匹配器之前,应优先通过 AgentBuilder.ignore(ElementMatcher) 排除明显不需要检测的类(如第三方库、JDK 自身类库),这可以显著提升代理效率
AgentBuilder agentBuilder = newAgentBuilder().ignore(
nameStartsWith("net.bytebuddy.")
.or(nameStartsWith("org.slf4j."))
.or(nameStartsWith("org.groovy."))
.or(nameContains("javassist"))
.or(nameContains(".asm."))
.or(nameContains(".reflectasm."))
.or(nameStartsWith("sun.reflect"))
.or(allSkyWalkingAgentExcludeToolkit())
.or(ElementMatchers.isSynthetic()));
//
agentBuilder.type(pluginFinder.buildMatch()) // 决定是否匹配类型
.transform(new Transformer(pluginFinder))
.with(AgentBuilder.RedefinitionStrategy.RETRANSFORMATION)
.with(new RedefinitionListener())
.with(new Listener())
.installOn(instrumentation);
PluginFinder.pluginInitCompleted();
LOGGER.info("Skywalking agent transformer has installed.");
}
2.5.1 typeMatcher
类型匹配:使用 typeMatcher 对正在加载的类型进行筛选。
简单说就是决定当前类是否匹配成功,进行transform。
AgentBuilder.type(ElementMatcher) 方法是 ByteBuddy 库中用于配置字节码增强代理(Agent)的核心 API,它通过匹配目标类型来决定是否对其应用指定的转换器(Transformer)
该方法的核心作用是定义一个类型匹配规则,当目标类被类加载器加载时,ByteBuddy 会检查该类是否满足 typeMatcher 指定的条件。如果匹配成功,就会对该类应用后续通过 .transform() 方法指定的一个或多个字节码转换器(Transformer)
2.5.2 Transformer
“转换器”(Transformer)。这个转换器会负责对匹配到的类进行实际的字节码增强操作。你可以将其理解为一个“加工车间”,符合条件的类都会被送入这个车间进行改造。
- 在 transform() 方法内部,会再次咨询 PluginFinder:给定一个正在被加载的类,PluginFinder 能够找出所有需要作用于这个类的插件(即那些在初始化时注册的、匹配规则与此类相符的插件)。
- 然后,遍历这些找到的插件,调用每个插件的 define() 方法来具体定义如何增强这个类(例如,在方法入口和出口添加埋点代码以记录执行时间、捕获异常等)。
- 最后,返回被修改后的字节码。
2.5.2.1 增强实现
提示
这里分成几步:
- 针对当前加载的类寻找匹配到的插件定义
- 使用插件来增强当前类
当然这里增强的实现特别复杂且经典,在这里写还的新建一个新的文档呢?
在这里 :如何增强类?
2.5.3 其他的参数
.with(AgentBuilder.RedefinitionStrategy.RETRANSFORMATION)
- 这行代码设置了 Byte Buddy 的 重定义策略(RedefinitionStrategy) 为 RETRANSFORMATION。
- 在 Java Agent 中,重定义策略控制如何修改已加载的类。
- RETRANSFORMATION 策略允许使用 Java Agent 的 retransformClasses 功能来重新转换已加载的类。这是实现运行时类增强的常见方式,特别是对于那些在 Agent 启动之后才加载的类,或者需要重新增强的类。
- 另一种常见的策略是 REDEFINITION,但它有更多限制(例如不能更改类的结构),而 RETRANSFORMATION 通常更灵活。
2.5.4 install
将特定的字节码转换逻辑注册到 JVM 的 Instrumentation 机制中,使得在类加载时或类重定义时能够拦截并修改字节码。
2.6启动内部服务组件 BootService
ServiceManager.INSTANCE.boot();
都是接口BootService
实现类
这个接口,可以理解为 skywalking的内部服务组件。
相当于是每个实现了 BootService
接口的实现类,都可以看作是一个单独在组件,提供不同的功能。