這個(gè)是之前寫類加載器篇時(shí)候挖的坑祸轮,來填坑了。
引言
以前在做后臺服務(wù)開發(fā)的時(shí)候犬绒,SpringBoot每次改動(dòng)代碼都需要手動(dòng)重啟才能生效檩禾,感覺賊麻煩挂签,后來使用Spring提供的一款熱部署插件,它只是部分重啟盼产,相當(dāng)于重新加載了我們自己寫的代碼饵婆,效率提高很多。后來遇到了Jrebel戏售,它只重新加載我們修改的那個(gè)類侨核,比Springboot熱部署插件重啟速度更快,連改mybatis的xml文件都能熱部署灌灾,太方便了有不有4暌搿(順便安利一下同一家公司的另一個(gè)軟件XRebel,實(shí)時(shí)監(jiān)控服務(wù)請求)后來又接觸了BTrace锋喜,它可以線上調(diào)試代碼而不需要重啟項(xiàng)目些己,也是很吊的一個(gè)東西豌鸡。通過了解,上面所說的幾個(gè)東西都是通過Java Agent來實(shí)現(xiàn)的段标,那么Java Agent到底是啥涯冠,為啥這么吊?
簡介
先說一下它的用途逼庞,在JDK1.5以后蛇更,我們可以使用agent技術(shù)構(gòu)建一個(gè)獨(dú)立于應(yīng)用程序的代理程序(即為Agent),用來協(xié)助監(jiān)測赛糟、運(yùn)行甚至替換其他JVM上的程序械荷。使用它可以實(shí)現(xiàn)虛擬機(jī)級別的AOP功能。
Agent實(shí)例
Agent分為兩種虑灰,一種是在主程序之前運(yùn)行的Agent,一種是在主程序之后運(yùn)行的Agent(前者的升級版痹兜,1.6以后提供)穆咐,這兩種我們都會(huì)舉個(gè)??。
一字旭、在主程序運(yùn)行之前的代理程序
1对湃、首先寫一個(gè)agent程序
代碼很簡單,只有一個(gè)premain方法遗淳,顧名思義它代表著他將在主程序的main方法之前運(yùn)行拍柒,agentArgs代表傳遞過來的參數(shù),inst則是agent技術(shù)主要使用的API屈暗,我們可以使用它來改變和重新定義類的行為(這篇文章不會(huì)介紹拆讯,想了解的同學(xué)可以看文末的鏈接),這里我們簡單的進(jìn)行一下參數(shù)打印养叛。
2种呐、編寫MANIFEST.MF文件
MANIFEST.MF文件用于描述Jar包的信息,例如指定入口函數(shù)等弃甥。我們需要在該文件中加入如下配置爽室,指定我們編寫的含有premain方法類的全路徑,然后將agent類打成Jar包淆攻。
如果你是使用Maven來構(gòu)建的項(xiàng)目阔墩,在構(gòu)建的時(shí)候加入如下代碼,否則Maven會(huì)生成自己的MANIFEST.MF覆蓋掉你的瓶珊。
3啸箫、編寫我們的主程序
這里的程序就是我們要代理的程序,我們在主程序的VM options添加上啟動(dòng)參數(shù)
-javaagent: 你的路徑/test-1.0-SNAPSHOT.jar=hah
其中hah為上文中傳入permain方法的agentArgs參數(shù)艰毒。運(yùn)行我們的主程序
可以看到筐高,我們Jar包中premain方法中的的代碼在主函數(shù)運(yùn)行之前就已經(jīng)成功運(yùn)行了!
二、在主程序運(yùn)行之后的代理程序
在主程序運(yùn)行之前的agent模式有一些缺陷柑土,例如需要在主程序運(yùn)行前就指定javaagent參數(shù)蜀肘,premain方法中代碼出現(xiàn)異常會(huì)導(dǎo)致主程序啟動(dòng)失敗等,為了解決這些問題稽屏,JDK1.6以后提供了在程序運(yùn)行之后改變程序的能力扮宠。它的實(shí)現(xiàn)步驟和之前的模式類似
1、編寫agent類
我們復(fù)用上面的類狐榔,將premain方法修改為agentmain方法坛增,由于是在主程序運(yùn)行后再執(zhí)行,意味著我們可以獲取主程序運(yùn)行時(shí)的信息薄腻,這里我們打印出來主程序中加載的類名收捣。
2、修改MANIFEST.MF文件
添加Agent-Class參數(shù)庵楷,打成Jar包
3罢艾、啟動(dòng)主程序,編寫加載agent類的程序
在程序運(yùn)行后加載尽纽,我們不可能在主程序中編寫加載的代碼咐蚯,只能另寫程序,那么另寫程序如何與主程序進(jìn)行通信弄贿?這里用到的機(jī)制就是attach機(jī)制春锋,它可以將JVM A連接至JVM B,并發(fā)送指令給JVM B執(zhí)行差凹,JDK自帶常用工具如jstack期奔,jps等就是使用該機(jī)制來實(shí)現(xiàn)的。這里我們先用tomcat啟動(dòng)一個(gè)程序用作主程序B直奋,再來寫A程序代碼
我們使用VirtualMachine attach到目標(biāo)進(jìn)程能庆,其中78256為tomcat進(jìn)程的PID,可以使用jps命令獲得脚线,也可以使用VirtualMachine.list方法獲取本機(jī)上所有的Java進(jìn)程搁胆,再來判斷tomcat進(jìn)程,loadAgent方法第一個(gè)參數(shù)為Jar包在本機(jī)中的路徑邮绿,第二個(gè)參數(shù)為傳入agentmain的args參數(shù)渠旁,此處為null,運(yùn)行程序
然而什么都沒有打印按顾腊!是不是什么地方寫錯(cuò)了呢?仔細(xì)想想就會(huì)發(fā)現(xiàn)挖胃,我們是將進(jìn)程attach到了tomcat進(jìn)程上杂靶,agent其實(shí)是在主程序B中運(yùn)行的梆惯,所以程序A中自然就不會(huì)進(jìn)行打印,我們跳回tomcat程序的控制臺吗垮,查看結(jié)果辫封。
可以看到鸠删,agentmain方法中的代碼已經(jīng)在主程序中順利運(yùn)行了易阳,并且打印出了程序中加載的類!
總結(jié)
以上就是Java Agent的倆個(gè)簡單小栗子了饵沧,Java Agent十分強(qiáng)大锨络,它能做到的不僅僅是打印幾個(gè)監(jiān)控?cái)?shù)值而已,還包括使用Transformer(推薦觀看)等高級功能進(jìn)行類替換狼牺,方法修改等羡儿,要使用Instrumentation的相關(guān)API則需要對字節(jié)碼等技術(shù)有較深的認(rèn)識。
最后是钥,繼續(xù)給自己挖坑失受,以后有機(jī)會(huì)寫字節(jié)碼相關(guān)的東西。咏瑟。