ByteBuddy不僅僅是為了生成Java-Agent,它提供的API甚至可以改變重寫一個Java類,本文我們使用其API實現和第二節一樣的功能,給目標類中的函數統計其調用耗時。
二、實現
1、修改pom.xml
本節和上節的不同點,主要有兩個。一個是引入ByteBuddy的依賴,另一個是需要將ByteBuddy的包通過shade打入到Agent中。下面只截取關鍵代碼:
<dependency>
<groupId>net.bytebuddy</groupId>
<artifactId>byte-buddy</artifactId>
<version>1.5.7</version>
</dependency>
<dependency>
<groupId>net.bytebuddy</groupId>
<artifactId>byte-buddy-agent</artifactId>
<version>1.5.7</version>
</dependency>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</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>
2、實現一個Agent
與之前相同的是,這里仍然是在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.<MethodDescription>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);
}
}
3、實現一個用來委托的Interceptor
在上一步實現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並增強了其調用方法。