聲明:原創(chuàng)文章吵瞻,轉(zhuǎn)載請(qǐng)注明出處线脚。http://www.reibang.com/p/fe1448bf7d31
Java字節(jié)碼系列
Java字節(jié)碼1-Agent簡(jiǎn)單上手
Java字節(jié)碼2-instrument初體驗(yàn)
Java字節(jié)碼3-使用ByteBuddy實(shí)現(xiàn)一個(gè)Java-Agent
Java字節(jié)碼4-使用Java-Agent實(shí)現(xiàn)一個(gè)JVM監(jiān)控工具
本系列代碼可見(jiàn):https://github.com/hawkingfoo/demo-agent
一陋气、概述
在前面兩節(jié)中橘忱,我們實(shí)現(xiàn)了Agent啸澡,但是其無(wú)論在使用方式和功能上面都有一定的局限性飞崖。本文我們借助字節(jié)碼工具ByteBuddy丹弱,寫出高級(jí)的Agent德撬。
ByteBuddy不僅僅是為了生成Java-Agent,它提供的API甚至可以改變重寫一個(gè)Java類躲胳,本文我們使用其API實(shí)現(xiàn)和第二節(jié)一樣的功能蜓洪,給目標(biāo)類中的函數(shù)統(tǒng)計(jì)其調(diào)用耗時(shí)。
二坯苹、實(shí)現(xiàn)
1隆檀、修改pom.xml
本節(jié)和上節(jié)的不同點(diǎn),主要有兩個(gè)粹湃。一個(gè)是引入ByteBuddy的依賴恐仑,另一個(gè)是需要將ByteBuddy的包通過(guò)shade打入到Agent中。下面只截取關(guān)鍵代碼:
<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为鳄、實(shí)現(xiàn)一個(gè)Agent
與之前相同的是裳仆,這里仍然是在premain
處進(jìn)行處理。通過(guò)AgentBuilder
方法孤钦,生成一個(gè)Agent歧斟。這里有兩點(diǎn)需要特別說(shuō)明:其一是在AgentBuilder.type
處记某,這里可以指定需要攔截的類;其二是在builder.method
處构捡,這里可以指定需要攔截的方法液南。當(dāng)然其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滑凉、實(shí)現(xiàn)一個(gè)用來(lái)委托的Interceptor
在上一步實(shí)現(xiàn)Transformer
的過(guò)程中,委托了一個(gè)TimeInterceptor.class
喘帚。下面是其實(shí)現(xiàn)方式畅姊,整個(gè)的try
語(yǔ)句是原有的代碼執(zhí)行,我們?cè)谥按蛄藭r(shí)間戳吹由,并在其結(jié)束后若未,計(jì)算并打印了其調(diào)用耗時(shí)。
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");
}
}
}
三倾鲫、運(yùn)行
這里需要注意的是粗合,我們定義的包路徑要和Agent中定義的相同,否則Agent無(wú)法Hook到這個(gè)類及其方法乌昔。
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();
}
}
結(jié)果:
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并增強(qiáng)了其調(diào)用方法。