聲明:原創(chuàng)文章,轉載請注明出處。http://www.itdecent.cn/p/fe1448bf7d31
Java字節(jié)碼系列
Java字節(jié)碼1-Agent簡單上手
Java字節(jié)碼2-instrument初體驗
Java字節(jié)碼3-使用ByteBuddy實現(xiàn)一個Java-Agent
Java字節(jié)碼4-使用Java-Agent實現(xiàn)一個JVM監(jiān)控工具
本系列代碼可見:https://github.com/hawkingfoo/demo-agent
一、概述
在前面兩節(jié)中,我們實現(xiàn)了Agent,但是其無論在使用方式和功能上面都有一定的局限性。本文我們借助字節(jié)碼工具ByteBuddy,寫出高級的Agent。
ByteBuddy不僅僅是為了生成Java-Agent,它提供的API甚至可以改變重寫一個Java類,本文我們使用其API實現(xiàn)和第二節(jié)一樣的功能,給目標類中的函數(shù)統(tǒng)計其調(diào)用耗時。
二、實現(xiàn)
1、修改pom.xml
本節(jié)和上節(jié)的不同點,主要有兩個。一個是引入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、實現(xiàn)一個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、實現(xiàn)一個用來委托的Interceptor
在上一步實現(xiàn)Transformer的過程中,委托了一個TimeInterceptor.class。下面是其實現(xiàn)方式,整個的try語句是原有的代碼執(zhí)行,我們在之前打了時間戳,并在其結束后,計算并打印了其調(diào)用耗時。
public class TimeInterceptor {
@RuntimeType
public static Object intercept(@Origin Method method,
@SuperCall Callable<?> callable) throws Exception {
long start = System.currentTimeMillis();
try {
// 原有函數(shù)執(zhí)行
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并增強了其調(diào)用方法。