一隔披、Java Agent是什么晒衩?
通過操作Instrumentation的api就可以實現(xiàn)不重啟服務(wù)對單個類進(jìn)行簡單的修改变逃。Instrumentation是一個interface振愿,它的實現(xiàn)類InstrumentationImpl只有一個private的構(gòu)造方法侍咱。
怎么拿到這個對象呢柠并?下面是Instrumentation類的一段注釋說明:
There are two ways to botain an instance of the Instrumentation interface:
- When a JVM is launched in a way that indicates an agent class. In that case an Instrumentation instance is passed to the parent method of the agent class.
- When a JVM provides a mechanism to start agents sometime after the JVM is
launched . In that case an Instrumentation instance is passed yo the agentmain method of the agent code.
These mechainsms are described in the package specification.
Once an agent acquires an Instrumentation instance, the agent may call methods on the instance at any time.
這里對上面的英文原文的注釋做一個簡單的理解:一共有兩種方式拿到Instrumentation對象:
- 在JVM啟動時指定agent岭接,Instrumentation對象會通過agent的premain方法傳遞過去。
- 在JVM啟動后通過JVM提供的機(jī)制加載agent臼予,Instrumentation對象會通過agent的agentmain方法傳遞過去鸣戴。
本文重點(diǎn)介紹,在主程序運(yùn)行之前啟動agent的基本用法粘拾。
二窄锅、如何使用Java Agent技術(shù)?
2.1 簡單示例
新起一個簡單的Java工程AgentDemo
缰雇,新建包路徑com.alibaba.ei.agent
入偷,新增一個類AgentDemo
,編寫代碼如下:
public class AgentDemo {
private static Instrumentation instrumentation;
/**
* 該方法在main方法之前運(yùn)行械哟,與main方法運(yùn)行在同一個JVM中
*
* @param agentArgs 是 premain 函數(shù)得到的程序參數(shù)疏之,隨同 “– javaagent”一起傳入。與 main 函數(shù)不同的是暇咆,這個參數(shù)是一個字符串而不是一個字符串?dāng)?shù)組锋爪,如果程序參數(shù)有多個,程序?qū)⒆孕薪馕鲞@個字符串爸业。
* @param inst 是一個 java.lang.instrument.Instrumentation 的實例其骄,由 JVM 自動傳入。java.lang.instrument.Instrumentation 是 instrument 包中定義的一個接口扯旷,也是這個包的核心部分拯爽,集中了其中幾乎所有的功能方法,例如類定義的轉(zhuǎn)換和操作等等钧忽。
*/
public static void premain(String agentArgs, Instrumentation inst) {
System.out.println("=========premain方法執(zhí)行1========");
System.out.println(agentArgs);
instrumentation = inst;
SimpleClassTransformer transformer = new SimpleClassTransformer();
inst.addTransformer(transformer);
}
/**
* 如果不存在 premain(String agentArgs, Instrumentation inst)
* 則會執(zhí)行 premain(String agentArgs)
*
* @param agentArgs
* @author xifeijian
* @create 2018年4月18日
*/
public static void premain(String agentArgs) {
System.out.println("=========premain方法執(zhí)行2========");
System.out.println(agentArgs);
}
}
在這個 premain 函數(shù)中某抓,開發(fā)者可以進(jìn)行對類的各種操作。
- agentArgs 是 premain 函數(shù)得到的程序參數(shù)惰瓜,隨同 “– javaagent”一起傳入。與 main 函數(shù)不同的是汉矿,這個參數(shù)是一個字符串而不是一個字符串?dāng)?shù)組崎坊,如果程序參數(shù)有多個,程序?qū)⒆孕薪馕鲞@個字符串洲拇。
- Inst 是一個 java.lang.instrument.Instrumentation 的實例奈揍,由 JVM 自動傳入曲尸。java.lang.instrument.Instrumentation 是 instrument 包中定義的一個接口,它是agent技術(shù)主要使用的API男翰,我們可以使用它來改變和重新定義類的行為另患。
編寫轉(zhuǎn)換類SimpleClassTransformer:
/**
* @Description
* @Author louxiujun
* @Date 2020/2/4 11:49
**/
public class SimpleClassTransformer implements ClassFileTransformer {
@Override
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
if (className.endsWith("sun/net/www/protocol/http/HttpURLConnection")) {
ClassPool classPool = ClassPool.getDefault();
CtClass clazz = null;
try {
clazz = classPool.get("sun.net.www.protocol.http.HttpURLConnection");
CtConstructor[] cs = clazz.getConstructors();
for (CtConstructor constructor : cs) {
// 在構(gòu)造函數(shù)結(jié)束的位置插入如下的內(nèi)容
constructor.insertAfter("System.out.println(this.getURL());");
}
byte[] byteCode = clazz.toBytecode();
// 將類移出
clazz.detach();
return byteCode;
} catch (NotFoundException | CannotCompileException | IOException e) {
e.printStackTrace();
}
}
return null;
}
}
寫完這個類后,我們還需要做一步配置工作蛾绎。
1)如果項目是普通Java項目的話昆箕,則在 src 目錄下生成 META-INF/MANIFEST.MF 文件。
切換到工程設(shè)置面板租冠,切換到Artifacts
面板鹏倘,點(diǎn)擊?
按鈕,新增一個JAR
顽爹,選擇From modules with dependencies...
選項纤泵,如下圖所示:
Main Class
一欄留空不填,下面的單選按鈕選擇copy to the output directory and link via manifest
選項镜粤,其他的按照默認(rèn)生成的走就可以捏题,完成之后點(diǎn)擊OK
按鈕。
完成之后面板顯示如下肉渴,點(diǎn)擊OK
按鈕完成配置公荧。
然后編輯META-INF/MANIFEST.MF,MANIFEST.MF文件用于描述Jar包的信息黄虱,例如指定入口函數(shù)等稚矿。我們需要在該文件中加入如下配置,指定我們編寫的含有premain方法類的全路徑捻浦,然后將agent類打成Jar包晤揣。
1 Manifest-Version: 1.0
2 Premain-Class: com.alibaba.ei.agent.AgentDemo
3
4
要特別注意的是:最后一行是空行,還有就是Premain-Class冒號后面有個空格朱灿。
接下來選擇菜單欄中的Build
下拉中的Build Artifacts..
選項昧识,
然后我們在彈出的快捷菜單中選擇Action為Build
,從而將整個工程打包代碼為 javaagent.jar
此時盗扒,會在工程目錄的out文件夾成生成一個jar文件跪楞,復(fù)制下這個jar文件的絕對路徑備用。本文的jar文件所在目錄為/Users/XXX/Documents/code/javaagent/agentDemo/out/artifacts/agentDemo_jar/agentDemo.jar
侣灶。當(dāng)前整個工程的結(jié)構(gòu)圖如下圖所示:
2)如果你是使用Maven來構(gòu)建的項目甸祭,則在pom.xml文件中添加如下的內(nèi)容,Maven幫助生成MANIFEST.MF
文件褥影。
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>2.3.1</version>
<configuration>
<archive>
<manifest>
<addClasspath>true</addClasspath>
</manifest>
<manifestEntries>
<!--方式1:在主程序運(yùn)行之前的代理程序-->
<Premain-Class>com.alibaba.ei.agent.AgentDemo</Premain-Class>
</manifestEntries>
</archive>
</configuration>
</plugin>
</plugins>
</build>
再添加一下pom依賴:
<javassist.version>3.20.0-GA</javassist.version>
<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<version>${javassist.version}</version>
</dependency>
執(zhí)行mvn clean install
指令池户,則會在當(dāng)前工程目錄下生成一個target文件夾,里面有一個agentDemo-1.0-SNAPSHOT.jar
的jar包,拷貝起文件路徑備用即可校焦,感興趣的可以解壓看一下里面是否有一個MANIFEST.MF
文件赊抖。
接著我們再創(chuàng)建一個新的工程agentTest
,新建包路徑com.alibaba.ei.agent
寨典,新建文件AgentTest.java
氛雪。
public class AgentTest {
public static void main(String[] args) {
System.out.println("===========執(zhí)行main方法=============");
HttpUtil.fetch("http://www.baidu.com");
HttpUtil.fetch("http://www.163.com");
}
}
這里的程序就是我們要代理的程序,我們在主程序的VM options添加上啟動參數(shù)
-javaagent: 你的路徑/test-1.0-SNAPSHOT.jar=hello
其中hello
為上文中傳入permain方法的agentArgs參數(shù)耸成。運(yùn)行我們的主程序
編輯應(yīng)用的JVM啟動參數(shù)如下
點(diǎn)擊運(yùn)行按鈕后輸出如下:
=========premain方法執(zhí)行1========
Hello
=========premain方法執(zhí)行2========
World
===========執(zhí)行main方法=============
http://www.baidu.com
Content size:2283
http://www.163.com
Content size:488569
我們也可以將項目打包成jar包报亩,再以命令行的方式啟動:
java -javaagent:/Users/XXX/Documents/code/javaagent/agentDemo/out/artifacts/agentDemo_jar/agentDemo.jar=Hello -javaagent:/Users/XXX/Documents/code/javaagent/agentDemo/out/artifacts/agentDemo_jar/agentDemo.jar=World -jar agentTest.jar
objc[18080]: Class JavaLaunchHelper is implemented in both /Library/Java/JavaVirtualMachines/jdk1.8.0_151.jdk/Contents/Home/bin/java (0x106aea4c0) and /Library/Java/JavaVirtualMachines/jdk1.8.0_151.jdk/Contents/Home/jre/lib/libinstrument.dylib (0x106b964e0). One of the two will be used. Which one is undefined.
=========premain方法執(zhí)行1========
Hello
=========premain方法執(zhí)行2========
World
=========Main主方法執(zhí)行=========
http://www.baidu.com
Content size:2283
http://www.163.com
Content size:488569
特別提醒:如果你把 -javaagent
相關(guān)的參數(shù)放在-jar
相關(guān)參數(shù)的后面,則不會生效墓猎。也就是說捆昏,放在主程序后面的 agent 是無效的。