Spring Cloud——SkyWalking源码解析:从javaagent到premain
目录
SkyWalking 源码复盘 ①:-javaagent 到 premain
这一轮不从几十万行源码硬啃,而是沿着你实际启动 order-service 的那条链往下追。
你在 IDEA 中配置的是:
-javaagent:H:\tools\...\agent\skywalking-agent.jar
-Dskywalking.agent.service_name=order-service
-Dskywalking.collector.backend_service=127.0.0.1:11800
这三个参数分别解决:
-javaagent 启动 SkyWalking Agent
service_name 当前 JVM 在 UI 中叫什么
collector.backend_service Trace 数据发给哪个 OAP
一、-javaagent 和普通依赖有什么区别
普通 Maven 依赖:
<dependency>
...
</dependency>
只是把类放入当前应用的 ClassPath,通常要由业务代码主动调用。
而:
-javaagent:skywalking-agent.jar
是告诉 JVM:
在应用程序
main()执行前,先启动这个 Java Agent。
Java Agent 的标准入口是:
public static void premain(
String agentArgs,
Instrumentation instrumentation
)
Byte Buddy 官方也把 premain 描述为 Java Agent 的入口,并通过 Instrumentation 接入 JVM 的类转换机制。(GitHub)
启动顺序可以理解为:
启动 JVM
↓
识别 -javaagent
↓
加载 skywalking-agent.jar
↓
调用 SkyWalkingAgent.premain()
↓
安装字节码增强器
↓
启动 Spring Boot main()
所以 SkyWalking 能在 Tomcat、Spring MVC、JDBC 等类加载时提前介入。
二、SkyWalking Agent 的入口类
你使用的是 Java Agent 9.4.0。其入口类是:
org.apache.skywalking.apm.agent.SkyWalkingAgent
核心入口:
public static void premain(
String agentArgs,
Instrumentation instrumentation
) throws PluginException
源码注释直接说明:这是基于 Java Agent 机制的主入口,并使用 Byte Buddy 增强插件定义的类。(GitHub)
你不用记住完整包名,但要记住:
-javaagent
↓
SkyWalkingAgent
↓
premain()
三、premain() 主要干了哪几件事
从 9.4.0 源码看,主流程可以压缩为四步:
1. 加载配置
2. 加载插件
3. 安装 Class Transformer
4. 启动 Agent 内部服务
对应源码骨架:
public static void premain(
String agentArgs,
Instrumentation instrumentation
) {
// 1. 加载配置
SnifferConfigInitializer.initializeCoreConfig(agentArgs);
// 2. 加载插件
PluginFinder pluginFinder =
new PluginFinder(
new PluginBootstrap().loadPlugins()
);
// 3. 安装字节码转换器
installClassTransformer(
instrumentation,
pluginFinder
);
// 4. 启动 Agent 内部服务
ServiceManager.INSTANCE.boot();
}
这几行就是整个 Agent 启动主干。(GitHub)
四、第一步:加载配置
源码中首先执行:
SnifferConfigInitializer.initializeCoreConfig(agentArgs);
它会整合不同来源的配置,例如:
agent/config/agent.config
JVM -D 参数
环境变量
-javaagent 后携带的参数
你配置的:
-Dskywalking.agent.service_name=order-service
最终会进入 Agent 的配置对象。
因此,UI 中显示:
order-service
不是 Spring Boot 的:
spring:
application:
name: order-service
直接决定的,而是 SkyWalking Agent 自己读取到的服务名。
在你的项目中两者恰好设置成一样,方便统一管理。
五、第二步:加载插件
核心源码:
new PluginBootstrap().loadPlugins()
随后封装成:
PluginFinder
这一步的目的不是立即修改所有类,而是先建立一份规则清单:
哪些类需要增强
增强哪些方法
方法进入前做什么
方法结束后做什么
发生异常时做什么
例如可以抽象为:
Tomcat 插件
→ 匹配 Servlet/Tomcat 请求处理类
→ 创建 EntrySpan
JDBC 插件
→ 匹配 PreparedStatement
→ 创建数据库 ExitSpan
Feign 插件
→ 匹配 Feign HTTP 调用
→ 创建远程 ExitSpan
→ 注入 Trace 上下文
Agent 的插件是可拆分的;官方也将部分 JDK 类插件放在可选的 bootstrap-plugins 目录中,需要时再移动到插件目录。(Apache SkyWalking)
六、第三步:安装字节码转换器
真正关键的是:
installClassTransformer(
instrumentation,
pluginFinder
);
内部使用 Byte Buddy 构造:
AgentBuilder
然后:
agentBuilder
.type(pluginFinder.buildMatch())
.transform(new Transformer(pluginFinder))
.installOn(instrumentation);
这一段可以拆成三句话:
type(...) 哪些类需要拦截
transform(...) 如何修改这些类
installOn(...) 把转换器安装进 JVM
SkyWalking 9.4.0 源码中还设置了 RETRANSFORMATION,并在类转换时通过 PluginFinder 找到匹配的插件定义。(GitHub)
七、什么是 Instrumentation
你可以把 Instrumentation 理解为:
JVM 给 Java Agent 的一个“类改造接口”。
Agent 可以通过它在类加载时拿到原始字节码:
.class 原始字节码
↓
ClassFileTransformer
↓
插入 SkyWalking 拦截逻辑
↓
增强后的字节码
↓
JVM 加载并执行
业务源码:
public OrderInfo queryOrder(Integer orderId) {
return orderMapper.selectById(orderId);
}
你看到的源码没变,但 JVM 实际运行的类可能已经被增强成类似:
public OrderInfo queryOrder(Integer orderId) {
Span span = createSpan("queryOrder");
try {
OrderInfo result =
originalQueryOrder(orderId);
span.markSuccess();
return result;
} catch (Throwable e) {
span.log(e);
throw e;
} finally {
stopSpan(span);
}
}
这段是帮助理解的伪代码,不是 SkyWalking 原始源码。
八、第四步:启动 Agent 内部服务
字节码增强安装完成后,源码执行:
ServiceManager.INSTANCE.boot();
它负责启动 Agent 自身需要的后台组件,例如:
服务实例注册
心跳上报
Trace 数据缓冲
gRPC 网络发送
JVM 指标采集
配置拉取
同时,SkyWalking 还注册了 JVM 关闭钩子:
Runtime.getRuntime()
.addShutdownHook(...)
这样应用退出时,可以关闭 Agent 内部服务并释放资源。(GitHub)
因此 Agent 不只是“改了一下字节码”,它本身还运行着一套后台服务。
九、结合你的 order-service 看完整启动过程
你点击 IDEA 的启动按钮后,实际发生:
JVM 启动
↓
发现 -javaagent
↓
执行 SkyWalkingAgent.premain()
↓
读取 order-service 和 127.0.0.1:11800
↓
扫描 agent/plugins 中的插件
↓
创建 Byte Buddy AgentBuilder
↓
安装 Class Transformer
↓
启动 Agent 网络与上报服务
↓
执行 OrderApplication.main()
↓
Spring Boot 开始创建 Tomcat、HikariCP、Feign 等对象
↓
相关类加载时被 Agent 匹配并增强
↓
应用启动完成
这就解释了为什么必须:
在服务启动之前配置 -javaagent
而不能等服务完全启动后,才简单引入一个 Maven 包。
十、为什么不是所有类都增强
源码中会明确忽略部分包,例如:
net.bytebuddy
org.slf4j
部分 ASM、反射及 Agent 自身类
合成类
这是为了避免:
Agent 增强自己
递归增强
类加载死循环
不必要的性能开销
兼容性问题
9.4.0 的 installClassTransformer() 中可以直接看到这些忽略规则。(GitHub)
所以 SkyWalking 不是:
把 JVM 中所有类都修改一遍
而是:
插件提供匹配规则
→ 只增强命中的类和方法
十一、这一节最核心的认知
你现在应该把 Agent 启动理解成:
-javaagent
不是“引入一个监控 jar”
而是:
在 main() 之前启动一套字节码增强系统
完整主线:
-javaagent
↓
premain()
↓
读取配置
↓
加载插件规则
↓
安装 Transformer
↓
Byte Buddy 增强目标类
↓
启动上报服务
↓
业务 main() 正常运行
十二、面试回答模板
面试官问:
SkyWalking 为什么不修改业务源码也能完成链路追踪?
可以这样回答:
SkyWalking Java Agent 基于 Java Instrumentation。应用通过
-javaagent启动时,JVM 会在业务main方法之前调用 Agent 的premain。Agent 加载各类插件规则,并利用 Byte Buddy 安装 Class Transformer,在 Tomcat、Feign、JDBC 等目标类加载或重新转换时注入拦截逻辑。拦截器在方法进入、退出和异常阶段创建、结束或标记 Span,再由 Agent 后台服务异步上报给 OAP。因此业务源码基本不需要手工埋点。
十三、本节自测
先自己回答这四个问题:
-
-javaagent和普通 Maven 依赖的本质区别是什么? -
premain()为什么比 Spring Boot 的main()更早执行? -
PluginFinder和Instrumentation分别负责什么? -
Agent 安装 Transformer 后,业务类什么时候真正被增强?
标准答案压缩为:
PluginFinder:决定增强谁、怎么增强
Instrumentation:把转换器挂进 JVM
类加载/重转换时:真正修改字节码
下一条主线是:
Tomcat 收到 /order/query/1
→ SkyWalking 如何创建第一个 EntrySpan
更多推荐


所有评论(0)