Java字节码系列
Java字节码1-Agent简单上手
Java字节码2-instrument初体验
Java字节码3-使用ByteBuddy实现一个Java-Agent
Java字节码4-使用Java-Agent实现一个JVM监控工具
本系列代码可见:https://github.com/hawkingfoo/demo-agent
在前面两节中,我们实现了Agent,但是其无论在使用方式和功能上面都有一定的局限性。本文我们借助字节码工具ByteBuddy,写出高级的Agent。
ByteBuddy不仅仅是为了生成Java-Agent,它提供的API甚至可以改变重写一个Java类,本文我们使用其API实现和第二节一样的功能,给目标类中的函数统计其调用耗时。
本节和上节的不同点,主要有两个。一个是引入ByteBuddy的依赖,另一个是需要将ByteBuddy的包通过shade打入到Agent中。下面只截取关键代码:
<dependency>
<groupId>net.bytebuddygroupId>
<artifactId>byte-buddyartifactId>
<version>1.5.7version>
dependency>
<dependency>
<groupId>net.bytebuddygroupId>
<artifactId>byte-buddy-agentartifactId>
<version>1.5.7version>
dependency>
<plugin>
<groupId>org.apache.maven.pluginsgroupId>
<artifactId>maven-shade-pluginartifactId>
<executions>
<execution>
<phase>packagephase>
<goals>
<goal>shadegoal>
goals>
execution>
executions>
<configuration>
<artifactSet>
<includes>
<include>javassist:javassist:jar:include>
<include>net.bytebuddy:byte-buddy:jar:include>
<include>net.bytebuddy:byte-buddy-agent:jar:include>
includes>
artifactSet>
configuration>
plugin>
与之前相同的是,这里仍然是在premain
处进行处理。通过AgentBuilder
方法,生成一个Agent。这里有两点需要特别说明:其一是在AgentBuilder.type
处,这里可以指定需要拦截的类;其二是在builder.method
处,这里可以指定需要拦截的方法。当然其API支持各种isStatic、isPublic
等等一系列方式。
public class MyAgent {
public static void premain(String agentArgs, Instrumentation inst) {
System.out.println("this is an perform monitor agent.");
AgentBuilder.Transformer transformer = new AgentBuilder.Transformer() {
@Override
public DynamicType.Builder> transform(DynamicType.Builder> builder,
TypeDescription typeDescription,
ClassLoader classLoader) {
return builder
.method(ElementMatchers.any()) // 拦截任意方法
.intercept(MethodDelegation.to(TimeInterceptor.class)); // 委托
}
};
AgentBuilder.Listener listener = new AgentBuilder.Listener() {
@Override
public void onTransformation(TypeDescription typeDescription, ClassLoader classLoader, JavaModule module, DynamicType dynamicType) {}
@Override
public void onIgnored(TypeDescription typeDescription, ClassLoader classLoader, JavaModule module) { }
@Override
public void onError(String typeName, ClassLoader classLoader, JavaModule module, Throwable throwable) { }
@Override
public void onComplete(String typeName, ClassLoader classLoader, JavaModule module) { }
};
new AgentBuilder
.Default()
.type(ElementMatchers.nameStartsWith("com.example.demo")) // 指定需要拦截的类
.transform(transformer)
.with(listener)
.installOn(inst);
}
}
在上一步实现Transformer
的过程中,委托了一个TimeInterceptor.class
。下面是其实现方式,整个的try
语句是原有的代码执行,我们在之前打了时间戳,并在其结束后,计算并打印了其调用耗时。
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 + ": took " + (System.currentTimeMillis() - start) + "ms");
}
}
}
这里需要注意的是,我们定义的包路径要和Agent中定义的相同,否则Agent无法Hook到这个类及其方法。
package com.example.demo;
public class AgentTest {
private void fun1() throws Exception {
System.out.println("this is fun 1.");
Thread.sleep(500);
}
private void fun2() throws Exception {
System.out.println("this is fun 2.");
Thread.sleep(500);
}
public static void main(String[] args) throws Exception {
AgentTest test = new AgentTest();
test.fun1();
test.fun2();
}
}
结果:
this is an perform monitor agent.
this is fun 1.
private void com.example.demo.AgentTest.fun1() throws java.lang.Exception: took 501ms
this is fun 2.
private void com.example.demo.AgentTest.fun2() throws java.lang.Exception: took 500ms
public static void com.example.demo.AgentTest.main(java.lang.String[]) throws java.lang.Exception: took 1001ms
可以看到,我们的Agent成功Hook并增强了其调用方法。