<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          偷天換日,用JavaAgent欺騙你的JVM

          共 13716字,需瀏覽 28分鐘

           ·

          2021-12-09 11:36

          熟悉Spring的小伙伴們應(yīng)該都對aop比較了解,面向切面編程允許我們在目標方法的前后織入想要執(zhí)行的邏輯,而今天要給大家介紹的Java Agent技術(shù),在思想上與aop比較類似,翻譯過來可以被稱為Java代理、Java探針技術(shù)。

          Java Agent出現(xiàn)在JDK1.5版本以后,它允許程序員利用agent技術(shù)構(gòu)建一個獨立于應(yīng)用程序的代理程序,用途也非常廣泛,可以協(xié)助監(jiān)測、運行、甚至替換其他JVM上的程序,先從下面這張圖直觀的看一下它都被應(yīng)用在哪些場景:

          看到這里你是不是也很好奇,究竟是什么神仙技術(shù),能夠應(yīng)用在這么多場景下,那今天我們就來挖掘一下,看看神奇的Java Agent是如何工作在底層,默默支撐了這么多優(yōu)秀的應(yīng)用。

          回到文章開頭的類比,我們還是用和aop比較的方式,來先對Java Agent有一個大致的了解:

          • 作用級別:aop運行于應(yīng)用程序內(nèi)的方法級別,而agent能夠作用于虛擬機級別
          • 組成部分:aop的實現(xiàn)需要目標方法和邏輯增強部分的方法,而Java Agent要生效需要兩個工程,一個是agent代理,另一個是需要被代理的主程序
          • 執(zhí)行場合:aop可以運行在切面的前后或環(huán)繞等場合,而Java Agent的執(zhí)行只有兩種方式,jdk1.5提供的preMain模式在主程序運行前執(zhí)行,jdk1.6提供的agentMain在主程序運行后執(zhí)行

          下面我們就分別看一下在兩種模式下,如何動手實現(xiàn)一個agent代理程序。

          Premain模式

          Premain模式允許在主程序執(zhí)行前執(zhí)行一個agent代理,實現(xiàn)起來非常簡單,下面我們分別實現(xiàn)兩個組成部分。

          agent

          先寫一個簡單的功能,在主程序執(zhí)行前打印一句話,并打印傳遞給代理的參數(shù):

          public?class?MyPreMainAgent?{
          ????public?static?void?premain(String?agentArgs,?Instrumentation?inst)?{
          ????????System.out.println("premain?start");
          ????????System.out.println("args:"+agentArgs);
          ????}
          }

          在寫完了agent的邏輯后,需要把它打包成jar文件,這里我們直接使用maven插件打包的方式,在打包前進行一些配置。

          <build>
          ????<plugins>
          ????????<plugin>
          ????????????<groupId>org.apache.maven.pluginsgroupId>
          ????????????<artifactId>maven-jar-pluginartifactId>
          ????????????<version>3.1.0version>
          ????????????<configuration>
          ????????????????<archive>
          ????????????????????<manifest>
          ????????????????????????<addClasspath>trueaddClasspath>
          ????????????????????manifest>
          ????????????????????<manifestEntries>
          ????????????????????????<Premain-Class>com.cn.agent.MyPreMainAgentPremain-Class>????????????????????????????
          ????????????????????????<Can-Redefine-Classes>trueCan-Redefine-Classes>
          ????????????????????????<Can-Retransform-Classes>trueCan-Retransform-Classes>
          ????????????????????????<Can-Set-Native-Method-Prefix>trueCan-Set-Native-Method-Prefix>
          ????????????????????manifestEntries>
          ????????????????archive>
          ????????????configuration>
          ????????plugin>
          ????plugins>
          build>

          配置的打包參數(shù)中,通過manifestEntries的方式添加屬性到MANIFEST.MF文件中,解釋一下里面的幾個參數(shù):

          • Premain-Class:包含premain方法的類,需要配置為類的全路徑
          • Can-Redefine-Classes:為true時表示能夠重新定義class
          • Can-Retransform-Classes:為true時表示能夠重新轉(zhuǎn)換class,實現(xiàn)字節(jié)碼替換
          • Can-Set-Native-Method-Prefix:為true時表示能夠設(shè)置native方法的前綴

          其中Premain-Class為必須配置,其余幾項是非必須選項,默認情況下都為false,通常也建議加入,這幾個功能我們會在后面具體介紹。在配置完成后,使用mvn命令打包:

          mvn?clean?package

          打包完成后生成myAgent-1.0.jar文件,我們可以解壓jar文件,看一下生成的MANIFEST.MF文件:

          可以看到,添加的屬性已經(jīng)被加入到了文件中。到這里,agent代理部分就完成了,因為代理不能夠直接運行,需要附著于其他程序,所以下面新建一個工程來實現(xiàn)主程序。

          主程序

          在主程序的工程中,只需要一個能夠執(zhí)行的main方法的入口就可以了。

          public?class?AgentTest?{
          ????public?static?void?main(String[]?args)?{
          ????????System.out.println("main?project?start");
          ????}
          }

          在主程序完成后,要考慮的就是應(yīng)該如何將主程序與agent工程連接起來。這里可以通過-javaagent參數(shù)來指定運行的代理,命令格式如下:

          java?-javaagent:myAgent.jar?-jar?AgentTest.jar

          并且,可以指定的代理的數(shù)量是沒有限制的,會根據(jù)指定的順序先后依次執(zhí)行各個代理,如果要同時運行兩個代理,就可以按照下面的命令執(zhí)行:

          java?-javaagent:myAgent1.jar?-javaagent:myAgent2.jar??-jar?AgentTest.jar

          以我們在idea中執(zhí)行程序為例,在VM options中加入添加啟動參數(shù):

          -javaagent:F:\Workspace\MyAgent\target\myAgent-1.0.jar=Hydra
          -javaagent:F:\Workspace\MyAgent\target\myAgent-1.0.jar=Trunks

          執(zhí)行main方法,查看輸出結(jié)果:

          根據(jù)執(zhí)行結(jié)果的打印語句可以看出,在執(zhí)行主程序前,依次執(zhí)行了兩次我們的agent代理??梢酝ㄟ^下面的圖來表示執(zhí)行代理與主程序的執(zhí)行順序。

          缺陷

          在提供便利的同時,premain模式也有一些缺陷,例如如果agent在運行過程中出現(xiàn)異常,那么也會導(dǎo)致主程序的啟動失敗。我們對上面例子中agent的代碼進行一下改造,手動拋出一個異常。

          public?static?void?premain(String?agentArgs,?Instrumentation?inst)?{
          ????System.out.println("premain?start");
          ????System.out.println("args:"+agentArgs);
          ????throw?new?RuntimeException("error");
          }

          再次運行主程序:

          可以看到,在agent拋出異常后主程序也沒有啟動。針對premain模式的一些缺陷,在jdk1.6之后引入了agentmain模式。

          Agentmain模式

          agentmain模式可以說是premain的升級版本,它允許代理的目標主程序的jvm先行啟動,再通過attach機制連接兩個jvm,下面我們分3個部分實現(xiàn)。

          agent

          agent部分和上面一樣,實現(xiàn)簡單的打印功能:

          public?class?MyAgentMain?{
          ????public?static?void?agentmain(String?agentArgs,?Instrumentation?instrumentation)?{
          ????????System.out.println("agent?main?start");
          ????????System.out.println("args:"+agentArgs);
          ????}
          }

          修改maven插件配置,指定Agent-Class

          <plugin>
          ????<groupId>org.apache.maven.pluginsgroupId>
          ????<artifactId>maven-jar-pluginartifactId>
          ????<version>3.1.0version>
          ????<configuration>
          ????????<archive>
          ????????????<manifest>
          ????????????????<addClasspath>trueaddClasspath>
          ????????????manifest>
          ????????????<manifestEntries>
          ????????????????<Agent-Class>com.cn.agent.MyAgentMainAgent-Class>
          ????????????????<Can-Redefine-Classes>trueCan-Redefine-Classes>
          ????????????????<Can-Retransform-Classes>trueCan-Retransform-Classes>
          ????????????manifestEntries>
          ????????archive>
          ????configuration>
          plugin>

          主程序

          這里我們直接啟動主程序等待代理被載入,在主程序中使用了System.in進行阻塞,防止主進程提前結(jié)束。

          public?class?AgentmainTest?{
          ????public?static?void?main(String[]?args)?throws?IOException?{
          ????????System.in.read();
          ????}
          }

          attach機制

          和premain模式不同,我們不能再通過添加啟動參數(shù)的方式來連接agent和主程序了,這里需要借助com.sun.tools.attach包下的VirtualMachine工具類,需要注意該類不是jvm標準規(guī)范,是由Sun公司自己實現(xiàn)的,使用前需要引入依賴:

          <dependency>
          ????<groupId>com.sungroupId>
          ????<artifactId>toolsartifactId>
          ????<version>1.8version>
          ????<scope>systemscope>
          ????<systemPath>${JAVA_HOME}\lib\tools.jarsystemPath>
          dependency>

          VirtualMachine代表了一個要被附著的java虛擬機,也就是程序中需要監(jiān)控的目標虛擬機,外部進程可以使用VirtualMachine的實例將agent加載到目標虛擬機中。先看一下它的靜態(tài)方法attach

          public?static?VirtualMachine?attach(String?var0);

          通過attach方法可以獲取一個jvm的對象實例,這里傳入的參數(shù)是目標虛擬機運行時的進程號pid。也就是說,我們在使用attach前,需要先獲取剛才啟動的主程序的pid,使用jps命令查看線程pid

          11140
          16372?RemoteMavenServer36
          16392?AgentmainTest
          20204?Jps
          2460?Launcher

          獲取到主程序AgentmainTest運行時pid是16392,將它應(yīng)用于虛擬機的連接。

          public?class?AttachTest?{
          ????public?static?void?main(String[]?args)?{
          ????????try?{
          ????????????VirtualMachine??vm=?VirtualMachine.attach("16392");
          ????????????vm.loadAgent("F:\\Workspace\\MyAgent\\target\\myAgent-1.0.jar","param");
          ????????}?catch?(Exception?e)?{
          ????????????e.printStackTrace();
          ????????}
          ????}
          }

          在獲取到VirtualMachine實例后,就可以通過loadAgent方法可以實現(xiàn)注入agent代理類的操作,方法的第一個參數(shù)是代理的本地路徑,第二個參數(shù)是傳給代理的參數(shù)。執(zhí)行AttachTest,再回到主程序AgentmainTest的控制臺,可以看到執(zhí)行了了agent中的代碼:

          這樣,一個簡單的agentMain模式代理就實現(xiàn)完成了,可以通過下面這張圖再梳理一下三個模塊之間的關(guān)系。

          應(yīng)用

          到這里,我們就已經(jīng)簡單地了解了兩種模式的實現(xiàn)方法,但是作為高質(zhì)量程序員,我們肯定不能滿足于只用代理單純地打印語句,下面我們再來看看能怎么利用Java Agent搞點實用的東西。

          在上面的兩種模式中,agent部分的邏輯分別是在premain方法和agentmain方法中實現(xiàn)的,并且,這兩個方法在簽名上對參數(shù)有嚴格的要求,premain方法允許以下面兩種方式定義:

          public?static?void?premain(String?agentArgs)
          public?static?void?premain(String?agentArgs,?Instrumentation?inst)

          agentmain方法允許以下面兩種方式定義:

          public?static?void?agentmain(String?agentArgs)
          public?static?void?agentmain(String?agentArgs,?Instrumentation?inst)

          如果在agent中同時存在兩種簽名的方法,帶有Instrumentation參數(shù)的方法優(yōu)先級更高,會被jvm優(yōu)先加載,它的實例inst會由jvm自動注入,下面我們就看看能通過Instrumentation實現(xiàn)什么功能。

          Instrumentation

          先大體介紹一下Instrumentation接口,其中的方法允許在運行時操作java程序,提供了諸如改變字節(jié)碼,新增jar包,替換class等功能,而通過這些功能使Java具有了更強的動態(tài)控制和解釋能力。在我們編寫agent代理的過程中,Instrumentation中下面3個方法比較重要和常用,我們來著重看一下。

          addTransformer

          addTransformer方法允許我們在類加載之前,重新定義Class,先看一下方法的定義:

          void?addTransformer(ClassFileTransformer?transformer);

          ClassFileTransformer是一個接口,只有一個transform方法,它在主程序的main方法執(zhí)行前,裝載的每個類都要經(jīng)過transform執(zhí)行一次,可以將它稱為轉(zhuǎn)換器。我們可以實現(xiàn)這個方法來重新定義Class,下面就通過一個例子看看具體如何使用。

          首先,在主程序工程創(chuàng)建一個Fruit類:

          public?class?Fruit?{
          ????public?void?getFruit(){
          ????????System.out.println("banana");
          ????}
          }

          編譯完成后復(fù)制一份class文件,并將其重命名為Fruit2.class,再修改Fruit中的方法為:

          public?void?getFruit(){
          ????System.out.println("apple");
          }

          創(chuàng)建主程序,在主程序中創(chuàng)建了一個Fruit對象并調(diào)用了其getFruit方法:

          public?class?TransformMain?{
          ????public?static?void?main(String[]?args)?{
          ????????new?Fruit().getFruit();
          ????}
          }

          這時執(zhí)行結(jié)果會打印apple,接下來開始實現(xiàn)premain代理部分。

          在代理的premain方法中,使用InstrumentationaddTransformer方法攔截類的加載:

          public?class?TransformAgent?{
          ????public?static?void?premain(String?agentArgs,?Instrumentation?inst)?{
          ????????inst.addTransformer(new?FruitTransformer());
          ????}
          }

          FruitTransformer類實現(xiàn)了ClassFileTransformer接口,轉(zhuǎn)換class部分的邏輯都在transform方法中:

          public?class?FruitTransformer?implements?ClassFileTransformer?{
          ????@Override
          ????public?byte[]?transform(ClassLoader?loader,?String?className,?Class?classBeingRedefined,
          ????????????????????????????ProtectionDomain?protectionDomain,?byte[]?classfileBuffer){
          ????????if?(!className.equals("com/cn/hydra/test/Fruit"))
          ????????????return?classfileBuffer;

          ????????String?fileName="F:\\Workspace\\agent-test\\target\\classes\\com\\cn\\hydra\\test\\Fruit2.class";
          ????????return?getClassBytes(fileName);
          ????}

          ????public?static?byte[]?getClassBytes(String?fileName){
          ????????File?file?=?new?File(fileName);
          ????????try(InputStream?is?=?new?FileInputStream(file);
          ????????????ByteArrayOutputStream?bs?=?new?ByteArrayOutputStream()){
          ????????????long?length?=?file.length();
          ????????????byte[]?bytes?=?new?byte[(int)?length];

          ????????????int?n;
          ????????????while?((n?=?is.read(bytes))?!=?-1)?{
          ????????????????bs.write(bytes,?0,?n);
          ????????????}
          ????????????return?bytes;
          ????????}catch?(Exception?e)?{
          ????????????e.printStackTrace();
          ????????????return?null;
          ????????}
          ????}
          }

          transform方法中,主要做了兩件事:

          • 因為addTransformer方法不能指明需要轉(zhuǎn)換的類,所以需要通過className判斷當前加載的class是否我們要攔截的目標class,對于非目標class直接返回原字節(jié)數(shù)組,注意className的格式,需要將類全限定名中的.替換為/
          • 讀取我們之前復(fù)制出來的class文件,讀入二進制字符流,替換原有classfileBuffer字節(jié)數(shù)組并返回,完成class定義的替換

          將agent部分打包完成后,在主程序添加啟動參數(shù):

          -javaagent:F:\Workspace\MyAgent\target\transformAgent-1.0.jar

          再次執(zhí)行主程序,結(jié)果打?。?/p>

          banana

          這樣,就實現(xiàn)了在main方法執(zhí)行前class的替換。

          redefineClasses

          我們可以直觀地從方法的名字上來理解它的作用,重定義class,通俗點來講的話就是實現(xiàn)指定類的替換。方法定義如下:

          void?redefineClasses(ClassDefinition...?definitions)?throws??ClassNotFoundException,?UnmodifiableClassException;

          它的參數(shù)是可變長的ClassDefinition數(shù)組,再看一下ClassDefinition的構(gòu)造方法:

          public?ClassDefinition(Class?theClass,byte[]?theClassFile)?{...}

          ClassDefinition中指定了的Class對象和修改后的字節(jié)碼數(shù)組,簡單來說,就是使用提供的類文件字節(jié),替換了原有的類。并且,在redefineClasses方法重定義的過程中,傳入的是ClassDefinition的數(shù)組,它會按照這個數(shù)組順序進行加載,以便滿足在類之間相互依賴的情況下進行更改。

          下面通過一個例子來看一下它的生效過程,premain代理部分:

          public?class?RedefineAgent?{
          ????public?static?void?premain(String?agentArgs,?Instrumentation?inst)?
          ????????????throws?UnmodifiableClassException,?ClassNotFoundException?
          {
          ????????String?fileName="F:\\Workspace\\agent-test\\target\\classes\\com\\cn\\hydra\\test\\Fruit2.class";
          ????????ClassDefinition?def=new?ClassDefinition(Fruit.class,
          ????????????????FruitTransformer.getClassBytes(fileName))
          ;
          ????????inst.redefineClasses(new?ClassDefinition[]{def});
          ????}
          }

          主程序可以直接復(fù)用上面的,執(zhí)行后打?。?/p>

          banana

          可以看到,用我們指定的class文件的字節(jié)替換了原有類,即實現(xiàn)了指定類的替換。

          retransformClasses

          retransformClasses應(yīng)用于agentmain模式,可以在類加載之后重新定義Class,即觸發(fā)類的重新加載。首先看一下該方法的定義:

          void?retransformClasses(Class...?classes)?throws?UnmodifiableClassException;

          它的參數(shù)classes是需要轉(zhuǎn)換的類數(shù)組,可變長參數(shù)也說明了它和redefineClasses方法一樣,也可以批量轉(zhuǎn)換類的定義。

          下面,我們通過例子來看看如何使用retransformClasses方法,agent代理部分代碼如下:

          public?class?RetransformAgent?{
          ????public?static?void?agentmain(String?agentArgs,?Instrumentation?inst)
          ????????????throws?UnmodifiableClassException?
          {
          ????????inst.addTransformer(new?FruitTransformer(),true);
          ????????inst.retransformClasses(Fruit.class);
          ????????System.out.println("retransform?success");
          ????}
          }

          看一下這里調(diào)用的addTransformer方法的定義,與上面略有不同:

          void?addTransformer(ClassFileTransformer?transformer,?boolean?canRetransform);

          ClassFileTransformer轉(zhuǎn)換器依舊復(fù)用了上面的FruitTransformer,重點看一下新加的第二個參數(shù),當canRetransformtrue時,表示允許重新定義class。這時,相當于調(diào)用了轉(zhuǎn)換器ClassFileTransformer中的transform方法,會將轉(zhuǎn)換后class的字節(jié)作為新類定義進行加載。

          主程序部分代碼,我們在死循環(huán)中不斷的執(zhí)行打印語句,來監(jiān)控類是否發(fā)生了改變:

          public?class?RetransformMain?{
          ????public?static?void?main(String[]?args)?throws?InterruptedException?{
          ????????while(true){
          ????????????new?Fruit().getFruit();
          ????????????TimeUnit.SECONDS.sleep(5);
          ????????}
          ????}
          }

          最后,使用attach api注入agent代理到主程序中:

          public?class?AttachRetransform?{
          ????public?static?void?main(String[]?args)?throws?Exception?{
          ????????VirtualMachine?vm?=?VirtualMachine.attach("6380");
          ????????vm.loadAgent("F:\\Workspace\\MyAgent\\target\\retransformAgent-1.0.jar");
          ????}
          }

          回到主程序控制臺,查看運行結(jié)果:

          可以看到在注入代理后,打印語句發(fā)生變化,說明類的定義已經(jīng)被改變并進行了重新加載。

          其他

          除了這幾個主要的方法外,Instrumentation中還有一些其他方法,這里僅簡單列舉一下常用方法的功能:

          • removeTransformer:刪除一個ClassFileTransformer類轉(zhuǎn)換器
          • getAllLoadedClasses:獲取當前已經(jīng)被加載的Class
          • getInitiatedClasses:獲取由指定的ClassLoader加載的Class
          • getObjectSize:獲取一個對象占用空間的大小
          • appendToBootstrapClassLoaderSearch:添加jar包到啟動類加載器
          • appendToSystemClassLoaderSearch:添加jar包到系統(tǒng)類加載器
          • isNativeMethodPrefixSupported:判斷是否能給native方法添加前綴,即是否能夠攔截native方法
          • setNativeMethodPrefix:設(shè)置native方法的前綴

          Javassist

          在上面的幾個例子中,我們都是直接讀取的class文件中的字節(jié)來進行class的重定義或轉(zhuǎn)換,但是在實際的工作環(huán)境中,可能更多的是去動態(tài)的修改class文件的字節(jié)碼,這時候就可以借助javassist來更簡單的修改字節(jié)碼文件。

          簡單來說,javassist是一個分析、編輯和創(chuàng)建java字節(jié)碼的類庫,在使用時我們可以直接調(diào)用它提供的api,以編碼的形式動態(tài)改變或生成class的結(jié)構(gòu)。相對于ASM等其他要求了解底層虛擬機指令的字節(jié)碼框架,javassist真的是非常簡單和快捷。

          下面,我們就通過一個簡單的例子,看看如何將Java agent和Javassist結(jié)合在一起使用。首前先引入javassist的依賴:

          <dependency>
          ????<groupId>org.javassistgroupId>
          ????<artifactId>javassistartifactId>
          ????<version>3.20.0-GAversion>
          dependency>

          我們要實現(xiàn)的功能是通過代理,來計算方法執(zhí)行的時間。premain代理部分和之前基本一致,先添加一個轉(zhuǎn)換器:

          public?class?Agent?{
          ????public?static?void?premain(String?agentArgs,?Instrumentation?inst)?{
          ????????inst.addTransformer(new?LogTransformer());
          ????}

          ????static?class?LogTransformer?implements?ClassFileTransformer?{
          ????????@Override
          ????????public?byte[]?transform(ClassLoader?loader,?String?className,?Class?classBeingRedefined,?
          ????????????????????????????????ProtectionDomain?protectionDomain,?byte[]?classfileBuffer)?
          ????????????throws?IllegalClassFormatException?{
          ????????????if?(!className.equals("com/cn/hydra/test/Fruit"))
          ????????????????return?null;

          ????????????try?{
          ????????????????return?calculate();
          ????????????}?catch?(Exception?e)?{
          ????????????????e.printStackTrace();
          ????????????????return?null;
          ????????????}
          ????????}
          ????}
          }

          calculate方法中,使用javassist動態(tài)的改變了方法的定義:

          static?byte[]?calculate()?throws?Exception?{
          ????ClassPool?pool?=?ClassPool.getDefault();
          ????CtClass?ctClass?=?pool.get("com.cn.hydra.test.Fruit");
          ????CtMethod?ctMethod?=?ctClass.getDeclaredMethod("getFruit");
          ????CtMethod?copyMethod?=?CtNewMethod.copy(ctMethod,?ctClass,?new?ClassMap());
          ????ctMethod.setName("getFruit$agent");

          ????StringBuffer?body?=?new?StringBuffer("{\n")
          ????????????.append("long?begin?=?System.nanoTime();\n")
          ????????????.append("getFruit$agent($$);\n")
          ????????????.append("System.out.println(\"use?\"+(System.nanoTime()?-?begin)?+\"?ns\");\n")
          ????????????.append("}");
          ????copyMethod.setBody(body.toString());
          ????ctClass.addMethod(copyMethod);
          ????return?ctClass.toBytecode();
          }

          在上面的代碼中,主要實現(xiàn)了這些功能:

          • 利用全限定名獲取類CtClass
          • 根據(jù)方法名獲取方法CtMethod,并通過CtNewMethod.copy方法復(fù)制一個新的方法
          • 修改舊方法的方法名為getFruit$agent
          • 通過setBody方法修改復(fù)制出來方法的內(nèi)容,在新方法中進行了邏輯增強并調(diào)用了舊方法,最后將新方法添加到類中

          主程序仍然復(fù)用之前的代碼,執(zhí)行查看結(jié)果,完成了代理中的執(zhí)行時間統(tǒng)計功能:

          這時候我們可以再通過反射看一下:

          for?(Method?method?:?Fruit.class.getDeclaredMethods())?{
          ????System.out.println(method.getName());
          ????method.invoke(new?Fruit());
          ????System.out.println("-------");
          }

          查看結(jié)果,可以看到類中確實已經(jīng)新增了一個方法:

          除此之外,javassist還有很多其他的功能,例如新建Class、設(shè)置父類、讀取和寫入字節(jié)碼等等,大家可以在具體的場景中學習它的用法。

          總結(jié)

          雖然我們在平常的工作中,直接用到Java Agent的場景可能并不是很多,但是在熱部署、監(jiān)控、性能分析等工具中,它們可能隱藏在業(yè)務(wù)系統(tǒng)的角落里,一直在默默發(fā)揮著巨大的作用。

          本文從Java Agent的兩種模式入手,手動實現(xiàn)并簡要分析了它們的工作流程,雖然在這里只利用它們完成了一些簡單的功能,但是不得不說,正是Java Agent的出現(xiàn),讓程序的運行不再循規(guī)蹈矩,也為我們的代碼提供了無限的可能性。


          ············? END? ··············

          也許你還想看
          ??|?官宣!我升級了!!!
          ? |?我在 B 站淘了 2 個 Java 實戰(zhàn)項目! 小破站,YYDS!
          ? |?官宣!Mybatis-Plus 官方神器發(fā)布!?。?/a>
          ? |?7年前,24歲,出版了一本 Redis 神書
          ? |?京東二面:為什么需要分布式ID?你項目中是怎么做的?
          ? |?再見 Spring Task,這個定時任務(wù)框架真香!
          ? |?一鍵生成數(shù)據(jù)庫文檔,堪稱數(shù)據(jù)庫界的Swagger
          ? |?好家伙!國外小哥也看 Java 八股文?
          ? |?阿里開源的15個頂級Java項目!!!

          我是 Guide哥,一個工作2年有余,接觸編程已經(jīng)6年有余的程序員。大三開源 JavaGuide,目前已經(jīng) 100k+ Star。未來幾年,希望持續(xù)完善 JavaGuide,爭取能夠幫助更多學習 Java 的小伙伴!共勉!凎!點擊即可了解我的個人經(jīng)歷

          簡歷指導(dǎo)/Java 學習/面試指導(dǎo)/面試小冊,歡迎加入我的知識星球(公眾號后臺回復(fù)“星球”即可)。

          瀏覽 82
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  一级A级毛片 | 一到本在线视频无码 | 爱爱视频天天看 | 狠狠狠狠狠狠狠 | 五月天成人激情视频 |