案例簡述
通過上一章節的介紹《嗨!JavaAgent》,我們已經知道通過配置-javaagent:檔案.jar後,在java程式啟動時候會執行premain方法。接下來我們使用javassist位元組碼增強的方式,來監控方法程式的執行耗時。
Javassist是一個開源的分析、編輯和建立Java位元組碼的類庫。是由東京工業大學的數學和計算機科學系的 Shigeru Chiba (千葉 滋)所建立的。它已加入了開放源代碼JBoss應用伺服器項目,通過使用Javassist對位元組碼操作為JBoss實作動态"AOP"架構。
關于java位元組碼的處理,目前有很多工具,如bcel,asm。不過這些都需要直接跟虛拟機指令打交道。如果你不想了解虛拟機指令,可以采用javassist。javassist是jboss的一個子項目,其主要的優點,在于簡單,而且快速。直接使用java編碼的形式,而不需要了解虛拟機指令,就能動态改變類的結構,或者動态生成類。
環境準備
1、IntelliJ IDEA Community Edition
2、jdk1.8.0_45 64位
配置資訊(路徑相關修改為自己的)
1、配置位置:Run/Debug Configurations -> VM options
2、配置内容:-javaagent:E:\itstack\GIT\itstack.org\itstack-demo-agent\itstack-demo-agent-02\target\itstack-demo-agent-02-1.0.0-SNAPSHOT.jar=testargs
代碼示例
itstack-demo-agent-02
├── pom.xml
└── src
├── main
│ ├── java
│ │ └── org.itstack.demo.agent
│ │ ├── MyAgent.java
│ │ └── MyMonitorTransformer.java
│ └── resources
│ └── META-INF
│ └── MANIFEST.MF
└── test
└── java
└── org.itstack.demo.test
└── ApiTest.java
pom.xml (引入javassist并打入到Agent包中)
<properties>
<!-- Build args -->
<argline>-Xms512m -Xmx512m</argline>
<skip_maven_deploy>false</skip_maven_deploy>
<updateReleaseInfo>true</updateReleaseInfo>
<project.build.sourceEncoding>utf-8</project.build.sourceEncoding>
<maven.test.skip>true</maven.test.skip>
<!-- 自定義MANIFEST.MF -->
<maven.configuration.manifestFile>src/main/resources/META-INF/MANIFEST.MF</maven.configuration.manifestFile>
</properties>
<dependencies>
<dependency>
<groupId>javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.12.1.GA</version>
<type>jar</type>
</dependency>
</dependencies>
<!-- 将javassist包打包到Agent中 -->
<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>
</includes>
</artifactSet>
</configuration>
</plugin>
MyAgent.java
/**
* 部落格:http://itstack.org
* 論壇:http://bugstack.cn
* 公衆号:bugstack蟲洞棧 {擷取學習源碼}
* create by fuzhengwei on 2019
*/
public class MyAgent {
//JVM 首先嘗試在代理類上調用以下方法
public static void premain(String agentArgs, Instrumentation inst) {
System.out.println("this is my agent:" + agentArgs);
MyMonitorTransformer monitor = new MyMonitorTransformer();
inst.addTransformer(monitor);
}
//如果代理類沒有實作上面的方法,那麼 JVM 将嘗試調用該方法
public static void premain(String agentArgs) {
}
}
MyMonitorTransformer.java
/**
* 部落格:http://itstack.org
* 論壇:http://bugstack.cn
* 公衆号:bugstack蟲洞棧 {擷取學習源碼}
* create by fuzhengwei on 2019
*/
public class MyMonitorTransformer implements ClassFileTransformer {
private static final Set<String> classNameSet = new HashSet<>();
static {
classNameSet.add("org.itstack.demo.test.ApiTest");
}
@Override
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) {
try {
String currentClassName = className.replaceAll("/", ".");
if (!classNameSet.contains(currentClassName)) { // 提升classNameSet中含有的類
return null;
}
System.out.println("transform: [" + currentClassName + "]");
CtClass ctClass = ClassPool.getDefault().get(currentClassName);
CtBehavior[] methods = ctClass.getDeclaredBehaviors();
for (CtBehavior method : methods) {
enhanceMethod(method);
}
return ctClass.toBytecode();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
private void enhanceMethod(CtBehavior method) throws Exception {
if (method.isEmpty()) {
return;
}
String methodName = method.getName();
if ("main".equalsIgnoreCase(methodName)) {
return;
}
final StringBuilder source = new StringBuilder();
// 前置增強: 打入時間戳
// 保留原有的代碼處理邏輯
source.append("{")
.append("long start = System.nanoTime();\n") //前置增強: 打入時間戳
.append("$_ = $proceed($$);\n") //調用原有代碼,類似于method();($$)表示所有的參數
.append("System.out.print(\"method:[")
.append(methodName).append("]\");").append("\n")
.append("System.out.println(\" cost:[\" +(System.nanoTime() - start)+ \"ns]\");") // 後置增強,計算輸出方法執行耗時
.append("}");
ExprEditor editor = new ExprEditor() {
@Override
public void edit(MethodCall methodCall) throws CannotCompileException {
methodCall.replace(source.toString());
}
};
method.instrument(editor);
}
}
MANIFEST.MF
Manifest-Version: 1.0
Premain-Class: org.itstack.demo.agent.MyAgent
Can-Redefine-Classes: true
ApiTest.java
/**
* 部落格:http://itstack.org
* 論壇:http://bugstack.cn
* 公衆号:bugstack蟲洞棧 {擷取學習源碼}
* create by fuzhengwei on 2019
*
* VM options:
* -javaagent:E:\itstack\GIT\itstack.org\itstack-demo-agent\itstack-demo-agent-02\target\itstack-demo-agent-02-1.0.0-SNAPSHOT.jar=testargs
*
*/
public class ApiTest {
public static void main(String[] args) {
ApiTest apiTest = new ApiTest();
apiTest.echoHi();
}
private void echoHi(){
System.out.println("hi agent");
}
}
this is my agent:testargs
transform: [org.itstack.demo.test.ApiTest]
hi agent
method:[echoHi] cost:[294845ns]