发车篇提到,要用 Skywalking 监控一个应用,需要在其 VM 参数中添加 “-javaagent:skywalking-agent.jar”(省略skywalking-agent.jar的完整路径),这其实用了Java探针技术,算是个比较老的技术了,本节就简单介绍一下Java Agent。
Java Agent是从 JDK1.5 开始引入的,用一句概括其功能的话就是“在main()函数之前的一个拦截器”,也就是在执行main函数前,先执行Agent中的代码。
来吧,直接上Demo吧,一边看代码一边说( Maven 项目,不解释了 ,不是Maven项目的话,可以自己百度一下Java Agent怎么玩,也挺简单的):
先看TestAgent的代码:
public class TestAgent {
public static void premain(String agentArgs,
Instrumentation inst) {
System.out.println("this is a java agent.");
System.out.println("参数:" + agentArgs + "/n");
}
}
Java Agent的入口是premain()方法,听名字就知道是在main()方法前面执行的,有两个重载,如下所示:
public static void premain(String agentArgs,
Instrumentation inst); 【1】
public static void premain(String agentArgs); 【2】
如果两个同时存在的时候,【2】将会被忽略,只执行【1】
pom.xml里面我们需要用到一个插件:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>2.6</version>
<configuration>
<archive>
<manifestEntries>
<premain-class>com.xxx.TestAgent</premain-class>
</manifestEntries>
</archive>
</configuration>
</plugin>
这个插件是在打包的时候,写一下MANIFEST.MF,重点是写 premain-class :
Manifest-Version: 1.0
premain-class: com.xxx.TestAgent # 这里这里
Archiver-Version: Plexus Archiver
Built-By: xxx
Created-By: Apache Maven 3.6.0
Build-Jdk: 1.8.0_191
最后来看Main这个类:
public class Main {
public static void main(String[] args) {
System.out.println("TestAgent Main!");
}
}
接下来maven package一下,得到test-agent.jar这个包,然后设置VM options:
-javaagent:/Users/xxx/ks/TestAgent/target/test-agent.jar=TestAgentArgs
这里等号之后,就是传入premain()方法的参数哈
执行main()方法,会得到如下输出:
this is a java agent.
参数:TestAgentArgs
TestAgent Main!
我知道,Hello World肯定满足不了你们,下面用 Java Agent + Byte Buddy 实现一个统计方法执行时间的功能。
Byte Buddy 的 API 在后面会单独来一篇介绍一下,这里不深入追究,简单说明每个 API 的作用即可。
整个项目的结构不变,首先多加两个Byte Buddy的依赖:
<dependency>
<groupId>net.bytebuddy</groupId>
<artifactId>byte-buddy</artifactId>
<version>1.9.2</version>
</dependency>
<dependency>
<groupId>net.bytebuddy</groupId>
<artifactId>byte-buddy-agent</artifactId>
<version>1.9.2</version>
</dependency>
然后来看 TestAgent的代码 :
public class TestAgent {
public static void premain(String agentArgs, Instrumentation inst) {
AgentBuilder.Transformer transformer = new AgentBuilder.Transformer() {
public DynamicType.Builder<?> transform(DynamicType.Builder<?> builder,
TypeDescription typeDescription,
ClassLoader classLoader,
JavaModule module) {
return builder
// 拦截任意方法
.method(ElementMatchers.<MethodDescription>any())
// 拦截到的方法委托给TimeInterceptor
.intercept(MethodDelegation.to(TimeInterceptor.class));
}
};
new AgentBuilder // Byte Buddy专门有个AgentBuilder来处理Java Agent的场景
.Default()
// 根据包名前缀拦截类
.type(ElementMatchers.nameStartsWith("com.xxx"))
// 拦截到的类由transformer处理
.transform(transformer)
.installOn(inst);
}
}
当 Agent 拦截到符合条件的类时,会交给我们的 AgentBuilder.Transformer 实现处理,当 Transformer 拦截到符合条件的方法时,会交给我们的 TimeInterceptor 处理。 TimeInterceptor 的具体实现如下:
public class TimeInterceptor {
@RuntimeType
public static Object intercept(@Origin Method method,
@SuperCall Callable<?> callable)
throws Exception {
long start = System.currentTimeMillis();
try {
return callable.call(); // 执行原函数
} finally {
System.out.println(method.getName() + ":"
+ (System.currentTimeMillis() - start)
+ "ms");
}
}
}
TimeInterceptor 就类似于 AOP 的环绕切面。这里通过 @SuperCall 注解注入的 Callable 实例可以调到被拦截的目标方法(即使目标方法带参数,这里也不用传哈);这里通过 @Origin 注入的 Method 就是目标方法的元信息,没啥可说的。
在Skywalking中用到的 Byte Buddy 知识在下一篇文章中会进行说明的,容我整理整理 。
Main.java 中sleep 10s, VM options与前面的示例相同,不再赘述。执行 main() 方法,得到输出如下:
TestAgent Main!
main:10001ms
好了,Java Agent的基础知识和练手功能,就这样吧,Hello World 总是简单的,
╮(╯_╰)╭ ~ see you~