JavaAgent探針技術(shù)第一篇:在主程序運(yùn)行之前的代理程序

一隔披、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:

  1. 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.
  2. 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對象:

  1. JVM啟動時指定agent岭接,Instrumentation對象會通過agent的premain方法傳遞過去。
  2. 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)行對類的各種操作。

  1. agentArgs 是 premain 函數(shù)得到的程序參數(shù)惰瓜,隨同 “– javaagent”一起傳入。與 main 函數(shù)不同的是汉矿,這個參數(shù)是一個字符串而不是一個字符串?dāng)?shù)組崎坊,如果程序參數(shù)有多個,程序?qū)⒆孕薪馕鲞@個字符串洲拇。
  2. 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...選項纤泵,如下圖所示:

20191222203456.jpg

Main Class一欄留空不填,下面的單選按鈕選擇copy to the output directory and link via manifest選項镜粤,其他的按照默認(rèn)生成的走就可以捏题,完成之后點(diǎn)擊OK按鈕。

20191222203522.jpg

完成之后面板顯示如下肉渴,點(diǎn)擊OK按鈕完成配置公荧。

20191222203534.jpg

然后編輯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..選項昧识,

20191222203943.jpg

然后我們在彈出的快捷菜單中選擇Action為Build,從而將整個工程打包代碼為 javaagent.jar

20191222203957.jpg

此時盗扒,會在工程目錄的out文件夾成生成一個jar文件跪楞,復(fù)制下這個jar文件的絕對路徑備用。本文的jar文件所在目錄為/Users/XXX/Documents/code/javaagent/agentDemo/out/artifacts/agentDemo_jar/agentDemo.jar侣灶。當(dāng)前整個工程的結(jié)構(gòu)圖如下圖所示:

image.png

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ù)如下


image.png

點(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 是無效的。

四毙沾、參考資料

  1. 一個最簡單的javaagent demo實例
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末骗卜,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子左胞,更是在濱河造成了極大的恐慌寇仓,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,817評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件烤宙,死亡現(xiàn)場離奇詭異遍烦,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)躺枕,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,329評論 3 385
  • 文/潘曉璐 我一進(jìn)店門服猪,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人拐云,你說我怎么就攤上這事罢猪。” “怎么了叉瘩?”我有些...
    開封第一講書人閱讀 157,354評論 0 348
  • 文/不壞的土叔 我叫張陵膳帕,是天一觀的道長。 經(jīng)常有香客問我薇缅,道長危彩,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,498評論 1 284
  • 正文 為了忘掉前任泳桦,我火速辦了婚禮汤徽,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘灸撰。我一直安慰自己谒府,他們只是感情好漆羔,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,600評論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著狱掂,像睡著了一般。 火紅的嫁衣襯著肌膚如雪谤辜。 梳的紋絲不亂的頭發(fā)上逢勾,一...
    開封第一講書人閱讀 49,829評論 1 290
  • 那天员帮,我揣著相機(jī)與錄音,去河邊找鬼器虾。 笑死,一個胖子當(dāng)著我的面吹牛蹦锋,可吹牛的內(nèi)容都是我干的兆沙。 我是一名探鬼主播,決...
    沈念sama閱讀 38,979評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼莉掂,長吁一口氣:“原來是場噩夢啊……” “哼葛圃!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起憎妙,我...
    開封第一講書人閱讀 37,722評論 0 266
  • 序言:老撾萬榮一對情侶失蹤库正,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后厘唾,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體褥符,經(jīng)...
    沈念sama閱讀 44,189評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,519評論 2 327
  • 正文 我和宋清朗相戀三年抚垃,在試婚紗的時候發(fā)現(xiàn)自己被綠了喷楣。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,654評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡鹤树,死狀恐怖铣焊,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情魂迄,我是刑警寧澤粗截,帶...
    沈念sama閱讀 34,329評論 4 330
  • 正文 年R本政府宣布,位于F島的核電站捣炬,受9級特大地震影響熊昌,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜湿酸,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,940評論 3 313
  • 文/蒙蒙 一婿屹、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧推溃,春花似錦昂利、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,762評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽犁苏。三九已至,卻和暖如春扩所,著一層夾襖步出監(jiān)牢的瞬間围详,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,993評論 1 266
  • 我被黑心中介騙來泰國打工祖屏, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留助赞,地道東北人。 一個月前我還...
    沈念sama閱讀 46,382評論 2 360
  • 正文 我出身青樓袁勺,卻偏偏與公主長得像,于是被迫代替她去往敵國和親期丰。 傳聞我的和親對象是個殘疾皇子群叶,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,543評論 2 349

推薦閱讀更多精彩內(nèi)容