<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,還有不會的嗎 ?

          共 19596字,需瀏覽 40分鐘

           ·

          2022-02-26 16:15

          點擊關(guān)注公眾號,Java干貨及時送達(dá)??





          1
          JavaAgent是什么




          Java agent 是 JDK1.5 中引入的一個強大工具,它在分析活動中非常有用,開發(fā)人員和應(yīng)用程序用戶可以在這些活動中監(jiān)視在服務(wù)器和應(yīng)用程序中執(zhí)行的任務(wù)。

          Agent 基于 JVMTI 接口規(guī)范,提供了一種在加載字節(jié)碼時,對字節(jié)碼進行修改的方式,他共有兩種方式執(zhí)行,一種是在 main 方法執(zhí)行之前,通過 premain 來實現(xiàn),另一種是在程序運行中,通過 attach api 來實現(xiàn)。

          這門技術(shù)對大多數(shù)人來說都比較陌生,就像一個黑盒子,但是日常工作中,又或多或少接觸過基于 JavaAgent 技術(shù)實現(xiàn)的工具。



          如上圖,這上面的一些工具都是基于 JavaAgent 來實現(xiàn)的,還有大名鼎鼎的 Skywalking 和 ja-netfilter,它們基本上對你的業(yè)務(wù)沒有入侵,但功能都很強悍,用起來非常爽。

          知其然也要知其所以然,用的舒服的同時,我們也要搞清楚它們是怎么工作的,只有把工作原理和流程弄清楚了,在使用的過程中才不會迷糊。

          今天就帶大家來入個門,了解一下 JavaAgent 到底是怎么玩的,磨刀不誤砍柴,我們先將 JavaAgent 中的一些基本概念搞清楚,請大家耐心看完,這樣在后面的講解中才不會一臉懵逼。



          2
          基礎(chǔ)概念




          premain



          這個 premain 方法跟我們以往使用的一些 API 不太一樣,它并沒有一些父類和接口預(yù)先定義好,然后我們自己去復(fù)寫實現(xiàn),而是固定寫死的(不能寫錯),在目標(biāo)程序啟動的時候,它會先于目標(biāo)程序加載。


          package com.wolffy.hello;
          import java.lang.instrument.Instrumentation;
          /** * 預(yù)先處理,程序啟動時優(yōu)先加載,JavaAgent.class - By 「Java者說」 -> https://www.jiweichengzhu.com/ * Created by wolffy on 2022/2/15. */public class HelloPremain {
          /** * premain()有兩種寫法,Instrumentation參數(shù)可以不傳遞,帶有Instrumentation參數(shù)的方法優(yōu)先級更高 * * @param agentArgs 字符串參數(shù),可以在啟動的時候手動傳入 * @param inst 此參數(shù)由jvm傳入,Instrumentation中包含了對class文件操作的一些api,可以讓我們基于此來進行class文件的編輯 */ public static void premain(String agentArgs, Instrumentation inst) { System.out.println("Yes, I am a real Agent for premain Class."); }
          /** * 這個方法沒有上面那個方法的優(yōu)先級高,程序運行時會優(yōu)先找上面那個方法,如果沒找到,才會用這個方法 * * @param agentArgs 字符串參數(shù),可以在啟動的時候手動傳入 */ public static void premain(String agentArgs) { System.out.println("Yes, I am a real Agent Class."); }
          }


          agentmain



          如果說 premain 是預(yù)先處理,那么與之相對應(yīng)的 agentmain 就可以理解為后置處理,無需在程序啟動的時候就指定它,而是可以在程序啟動之后,再利用 attach api 來加載它。


          package com.wolffy.hello;
          import java.lang.instrument.Instrumentation;
          /** * 后置處理,啟動時無需加載,可在程序啟動之后進行加載,JavaAgent.class - By 「Java者說」 -> https://www.jiweichengzhu.com/ * Created by wolffy on 2022/2/15. */public class HelloAgentmain {
          /** * agentmain()有兩種寫法,Instrumentation參數(shù)可以不傳遞,帶有Instrumentation參數(shù)的方法優(yōu)先級更高 * * @param agentArgs 字符串參數(shù),可以在啟動的時候手動傳入 * @param inst 此參數(shù)由jvm傳入,Instrumentation中包含了對class文件操作的一些api,可以讓我們基于此來進行class文件的編輯 */ public static void agentmain(String agentArgs, Instrumentation inst) { System.out.println("Yes, I am a real Agent for agentmain Class."); }
          /** * 這個方法沒有上面那個方法的優(yōu)先級高,程序運行時會優(yōu)先找上面那個方法,如果沒找到,才會用這個方法 * * @param agentArgs 字符串參數(shù),可以在啟動的時候手動傳入 */ public static void agentmain(String agentArgs) { System.out.println("Yes, I am a real Agent Class."); }
          }


          Instrumentation?



          大家重點看 premain 和 agentmain 的第二個參數(shù),如果我們想要在后續(xù)對 java 字節(jié)碼進行修改,那么就必須通過 Instrumentation 來實現(xiàn),它由 JVM 傳入,是 JDK1.5 提供的 API,用于攔截類加載事件,并對字節(jié)碼進行修改。


          Instrumentation 的功能非常強悍,提供了大概有十幾個 API,若我們只想要修改字節(jié)碼的話,就關(guān)心其中的三個主要方法即可。


          public?interface?Instrumentation?{    //注冊一個轉(zhuǎn)換器,類加載事件會被注冊的轉(zhuǎn)換器所攔截     void addTransformer(ClassFileTransformer transformer, boolean canRetransform);    //重新觸發(fā)類加載     void retransformClasses(Class... classes) throws UnmodifiableClassException;    //直接替換類的定義     void redefineClasses(ClassDefinition... definitions) throws  ClassNotFoundException, UnmodifiableClassException;}


          ClassFileTransformer



          Instrumentation 中又引入了一個新的 ClassFileTransformer ?類,名字取的非常語義化,一眼就能看出來是用來轉(zhuǎn)換 class 文件的。


          package com.wolffy.hello;
          import java.lang.instrument.ClassFileTransformer;import java.lang.instrument.IllegalClassFormatException;import java.security.ProtectionDomain;
          /** * Transformer.class - By 「Java者說」 -> https://www.jiweichengzhu.com/ * Created by wolffy on 2022/2/15. */public class HelloTransformer implements ClassFileTransformer {
          /** * 轉(zhuǎn)換提供的類文件并返回一個新的替換類文件 * * @param loader 要轉(zhuǎn)換的類的定義加載器,如果引導(dǎo)加載器可能為null * @param className Java 虛擬機規(guī)范中定義的完全限定類和接口名稱的內(nèi)部形式的類名稱。例如, "java/util/List" 。 * @param classBeingRedefined 如果這是由重新定義或重新轉(zhuǎn)換觸發(fā)的,則該類被重新定義或重新轉(zhuǎn)換;如果這是一個類加載, null * @param protectionDomain 被定義或重新定義的類的保護域 * @param classfileBuffer 類文件格式的輸入字節(jié)緩沖區(qū) - 不得修改 * @return 轉(zhuǎn)換后的字節(jié)碼數(shù)組 * @throws IllegalClassFormatException class文件格式異常 */ @Override public byte[] transform(ClassLoader loader, String className, Class classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
          // 實現(xiàn)ClassFileTransformer接口之后,這里默認(rèn)是return了一個[]空數(shù)組 // 千萬注意: // 如果不需要改變,那就return null,如果return了一個[],那么你的class就會被置空,程序就會報錯
          // return new byte[0];
          return null; }
          }


          理論太枯燥,所以每一個解釋下面我都放了實戰(zhàn)代碼來給大家做演示,這樣會更好理解一點兒,這幾個基礎(chǔ)概念,大家先簡單看一下,有一個大概的印象之后,接下來通過具體的代碼和案例來給大家講解。




          3
          JavaAgent使用方式




          在實戰(zhàn)之前,我們來了解一下 JavaAgent 的使用方式。


          正常我們運行一個 java 程序,都是需要找到一個 main 方法入口,如果是 jar 包的話,一般都是直接 java -jar 。


          如果我們是用的是 premain 方式,那我們直接通過追加 -javaagent 參數(shù)來引入 agent。



          java?-javaagent:/Users/wolffy/agent.jar?-jar?/Users/wolffy/test.jar


          如果我們是用的是 agent 方式,那么就需要借助于 JDK 的 tools.jar 中的 API 了。


          // 連接jvm,并利用相關(guān)的api找到HelloTest工程運行時的進程id,也就是PIDVirtualMachine vm = VirtualMachine.attach("12345");// 加載agent,大家注意使用自己的路徑vm.loadAgent("/Users/wolffy/agent.jar");// 脫離jvmvm.detach();



          4
          實戰(zhàn) - premain




          我們來創(chuàng)建兩個 java 工程,一個工程中含有 premain 方法的類,一個工程就用來打包測試 jar 包。

          當(dāng)我們編寫了一個 JavaAgent 之后,想讓它被加載用起來,必須要有一份?META-INF/MANIFEST.MF 文件作為指引


          Manifest-Version:?1.0Premain-Class:?com.wolffy.hello.HelloAgentCan-Redefine-Classes: trueCan-Retransform-Classes: trueCan-Set-Native-Method-Prefix: trueBuild-Jdk-Spec: 1.8Created-By: Maven Jar Plugin 3.2.0Main-Class:?com.wolffy.hello.HelloMain


          這種文件編寫起來不是很麻煩,但是有更方便的 Maven 了,所以就不再折騰手寫了,本文用來演示的 demo 也是構(gòu)建的 maven 工程。




          新建的?agent 工程名為 HelloAgent,路徑為D:\workspace_idea\HelloAgent



          工程中包含 premain 方法的類是 HelloPremain.java


          package com.wolffy.hello;
          import java.lang.instrument.Instrumentation;
          /** * 預(yù)先處理,程序啟動時優(yōu)先加載,JavaAgent.class - By 「Java者說」 -> https://www.jiweichengzhu.com/ * Created by wolffy on 2022/2/15. */public class HelloPremain {
          public static void premain(String agentArgs, Instrumentation inst) { System.out.println("Yes, I am a real Agent for premain Class."); }
          }


          pom.xml 文件中需要配置 Premain-Class,其中 mainClass 可以省略,因為 agent 是附著在其他應(yīng)用上的,類似于寄生蟲一樣,所以它本身能不能單獨運行關(guān)系不大。


              org.apache.maven.plugins    maven-jar-plugin    3.2.0                                                        com.wolffy.hello.HelloMain                                                        com.wolffy.hello.HelloPremain

          true true true




          新建的 test 工程名為 HelloTest,路徑為D:\workspace_idea\HelloTest



          工程中包含 main 方法運行入口的類是?AgentTest.java


          package com.wolffy.hello.test;
          /** * AgentTest.class - By 「Java者說」 -> https://www.jiweichengzhu.com/ * Created by wolffy on 2022/2/15. */public class AgentTest {
          public static void main(String[] args) { System.out.println("hello, here is hello test for agent."); }
          }


          pom.xml中打包的配置只需要指定 jar 運行的?mainClass 入口


              org.apache.maven.plugins    maven-jar-plugin    3.2.0                                                        com.wolffy.hello.test.AgentTest                        


          兩個工程的代碼編寫完畢,我們將其打成2個jar包:HelloAgent.jar、HelloTest.jar

          由于我都為其指定了 mainClass,所以我把兩個 jar 都執(zhí)行一下看看輸出的內(nèi)容



          如上圖,這是運行 agent 工程的 jar 文件輸出的內(nèi)容



          如上圖,這是運行 test 工程的 jar 文件輸出的內(nèi)容。

          現(xiàn)在我給 test 工程引入 agent 來看一下效果,直接在 java -jar 中間加入 -javaagent 參數(shù)。



          如上圖,可以看到有兩行字符串輸出,第二行輸出的文案是在 test 工程的 main 方法入口中定義的,而第一行輸出的文案,則不是在 test 工程中定義的,而是在 agent 工程中定義的。

          這么看起來,是不是很離譜?

          test 中根本沒有定義,但是它卻輸出了,僅僅是在啟動的時候配置了 -javaagent,可以看到,對 test 的業(yè)務(wù)代碼根本沒有任何侵入。



          好了,通過這個簡單的字符串打印程序,我們已經(jīng)可以很清晰的看到 premain 的工作模式了,只要你引入了 agent,那么它一定會在你的應(yīng)用程序啟動之前執(zhí)行。



          5
          實戰(zhàn) - agentmain



          雖然 premain 方式使用起來簡單便捷,但是有一個致命的問題,因為它在應(yīng)用程序啟動之前執(zhí)行,一旦它出現(xiàn)了問題,就會導(dǎo)致應(yīng)用程序也啟不來,所以必須保證 agent 程序100%可用。

          在 Jdk1.6 的時候,agentmain 出場了,這種方式它不要求在應(yīng)用程序啟動之前就加載,可以在程序運行中途進行加載,比如我們常用的 Arthas 和 Skywalking,它們就是在應(yīng)用程序啟動之后,再來收集信息做監(jiān)控和診斷。

          agentmain 要想被使用起來,也是需要有一份?META-INF/MANIFEST.MF 文件的,用來指定 agent class 文件位置。


          Manifest-Version: 1.0Agent-Class: com.wolffy.hello.HelloAgentmainCan-Redefine-Classes: trueCan-Retransform-Classes: trueCan-Set-Native-Method-Prefix: trueBuild-Jdk-Spec: 1.8Created-By: Maven Jar Plugin 3.2.0Main-Class: com.wolffy.hello.HelloMain




          光說不練,等于白干,我們來手動寫一個小案例模擬一下 arthas,這樣大家也能看的更加清晰,以小看大,就能明白 arthas 這種工具的工作原理。



          如上圖,我用紅色字體將 arthas 的工作流程簡單總結(jié)了一下,我們也來簡單模擬一下這幾步流程。

          工程就不再新建了,直接復(fù)用之前的,test 工程依舊是 HelloTest,只不過我們?yōu)榱四M程序真實運行,所以加了一個死循環(huán)讓其長時間運行。


          package com.wolffy.hello.test;
          /** * AgentTest.class - By 「Java者說」 -> https://www.jiweichengzhu.com/ * Created by wolffy on 2022/2/15. */public class AgentTest {
          public static void main(String[] args) { System.out.println("hello, here is hello test for agent.");
          while (true) { // 模擬應(yīng)用程序,讓其長時間運行 } }
          }


          agent 工程依舊是 HelloAgent,只不過我們在 HelloAgent 工程中啟動了一個 socket 服務(wù)來接收數(shù)據(jù),并加入?agentmain 的代碼


          package com.wolffy.hello;
          import com.sun.tools.attach.AgentInitializationException;import com.sun.tools.attach.AgentLoadException;import com.sun.tools.attach.AttachNotSupportedException;import com.sun.tools.attach.VirtualMachine;
          import java.io.BufferedReader;import java.io.IOException;import java.io.InputStream;import java.io.InputStreamReader;import java.net.ServerSocket;import java.net.Socket;import java.net.SocketAddress;import java.util.Scanner;
          /** * Main.class - By 「Java者說」 -> https://www.jiweichengzhu.com/ * Created by wolffy on 2022/2/15. */public class HelloMain {
          public static void main(String[] args) { System.out.println("Hello, I am a JavaAgent demo, created by https://www.jiweichengzhu.com/\n");
          try { // 創(chuàng)建socket服務(wù)器 ServerSocket server = new ServerSocket(9876); System.out.println("啟動Socket Server完畢,開始監(jiān)聽9876端口\n");
          // 選擇java應(yīng)用程序的pid Scanner scanner = new Scanner(System.in); System.out.print("請輸入PID: ");
          String pid = scanner.next(); System.out.println();
          // 模擬arthas選擇pid動作 + 加載agent simulationAndLoad(pid); System.out.println("為PID=" + pid + "的應(yīng)用程序加載agent完畢\n");
          // 接收socket客戶端鏈接,這里會阻塞,直到有客戶端連接上來 Socket client = server.accept();
          // 獲取客戶端遠(yuǎn)程地址信息 SocketAddress address = client.getRemoteSocketAddress(); System.out.println("客戶端[" + address + "]已連接\n");
          String msg = receiveMsg(client); System.out.println("客戶端[" + address + "]說: " + msg + "\n");
          } catch (IOException e) { e.printStackTrace(); } }
          /** * 模擬arthas選擇pid動作 + 加載agent * * @param pid 進程ID */ private static void simulationAndLoad(String pid) { try { // 連接jvm,并利用相關(guān)的api找到HelloTest工程運行時的進程id,也就是PID VirtualMachine vm = VirtualMachine.attach(pid); // 加載agent,大家注意使用自己的路徑 vm.loadAgent("D:\\workspace_idea\\HelloAgent\\target\\HelloAgent.jar"); // 脫離jvm vm.detach(); } catch (AttachNotSupportedException | IOException | AgentLoadException | AgentInitializationException e) { e.printStackTrace(); } }
          /** * 接收客戶端消息,只做演示使用,所以只使用一次就行了 * * @param socket 客戶端鏈接 * @return 客戶端發(fā)來的消息 * @throws IOException IO異常 */ private static String receiveMsg(Socket socket) throws IOException { // 打開客戶端的輸入流 InputStream is = socket.getInputStream(); // 創(chuàng)建字節(jié)流到字符流的橋接 InputStreamReader isr = new InputStreamReader(is); // 借助緩存流來進行緩沖文本讀取 BufferedReader br = new BufferedReader(isr); // 讀取一個文本行 String msg = br.readLine();
          // 關(guān)閉IO br.close(); isr.close(); is.close();
          return msg; }
          }


          從上面可以看到,main 入口的代碼很簡單,就是一個簡單的 ServerSocket,好讓應(yīng)用程序能連接過來上報數(shù)據(jù)。


          package com.wolffy.hello;
          import java.io.IOException;import java.io.OutputStream;import java.io.PrintWriter;import java.lang.instrument.Instrumentation;import java.net.Socket;
          /** * 后置處理,啟動時無需加載,可在程序啟動之后進行加載,JavaAgent.class - By 「Java者說」 -> https://www.jiweichengzhu.com/ * Created by wolffy on 2022/2/15. */public class HelloAgentmain {
          public static void agentmain(String agentArgs, Instrumentation inst) { System.out.println("Yes, I am a real Agent for agentmain Class.");
          try { // 連接socket服務(wù)端 Socket socket = new Socket("127.0.0.1", 9876);
          // 打開輸出流 OutputStream os = socket.getOutputStream();
          // 格式化輸出流,自帶刷新 PrintWriter pw = new PrintWriter(os, true);
          String project = System.getProperty("user.dir"); pw.println("hay, i am project [" + project + "]");
          // 關(guān)閉IO pw.close(); os.close(); socket.close();
          } catch (IOException e) { e.printStackTrace(); } }
          }


          agentmain 的代碼也很簡單,連接上 socket 服務(wù)之后,進行數(shù)據(jù)的上報。

          關(guān)于 attach api 中傳入的 pid,大家可以直接在本機進程中查看,找到 HelloTest 程序運行的那個 java 進程,將其的 pid 復(fù)制過來就行。



          如上圖,我已經(jīng)將 HelloTest 啟動了,大家可以使用 jps 命令查看 java 進程,如果是 windows 系統(tǒng),也可以直接使用任務(wù)管理器。



          如上圖,可以很清楚的找到 HelloTest 的進程,前面那個數(shù)據(jù)就是它的 PID,其他幾個是我機器上的其他 java 應(yīng)用程序。



          如上圖,這是 windows 任務(wù)管理器的界面,在里面也能找到 HelloTest 的應(yīng)用程序的 PID,就是如果有多個 java 程序的話,任務(wù)管理器就不太直觀了,所以推薦大家使用 jps 命令。

          測試的時候,我們先啟動 test 工程,然后再啟動 agent 工程,只有這樣才能拿到 test 工程的 PID。



          如上圖,是我將 HelloAgent 啟動之后看到的日志,根據(jù)日志打印的順序來看, 是不是跟 arthas 很像?



          如上圖,這是啟動了 HelloAgent 工程之后,在 HelloTest 工程中看到的輸出內(nèi)容,是在 test 自身輸出之后打印的。

          ok,我們簡單模擬 arthas 已經(jīng)完成,雖然只是一只小小的麻雀,但它的五臟俱全,還是很能體現(xiàn) agentmain 的工作過程的。



          吶,現(xiàn)在已經(jīng)基于 premain 和 agentmain 各自實現(xiàn)了一套代碼,也分別測試了效果,從測試結(jié)果來看,我們可看到這二者各自適合的使用場景:

          1、premain 適合用在激活各類 java 應(yīng)用中;
          2、agentmain 適用在收集各類 java 應(yīng)用的數(shù)據(jù)中;

          在一般的數(shù)據(jù)收集場景中,被收集方要主動上報數(shù)據(jù)給收集方,那么它就必須要在代碼中寫一段上報的邏輯,但是大家可以看到,HelloTest 工程中并沒有寫這樣的代碼,而是直接由 agent 來完成的,對 test 的代碼沒有任何的侵入,非常的犀利。




          6
          實戰(zhàn) - ClassFileTransformer



          上面將 premain 和 agentmain 講完了,這都是最基礎(chǔ)的功能,我們平時使用的一些功能強大的工具也都是基于這個 JavaAgent,正所謂萬丈高樓平地起,基礎(chǔ)打好了就不害怕了。

          現(xiàn)在我們來看一下上面提到的 ClassFileTransformer,我們用它來修改一個 class 文件看看效果(premian 方式)。

          test 工程不變,依舊只是輸出一段文字內(nèi)容,我們待會兒利用 agent 來將這行文字給篡改了。


          package com.wolffy.hello.test;
          /** * AgentTest.class - By 「Java者說」 -> https://www.jiweichengzhu.com/ * Created by wolffy on 2022/2/15. */public class AgentTest {
          public static void main(String[] args) { System.out.println("hello, here is hello test for agent.");
          // while (true) { // // 模擬應(yīng)用程序,讓其長時間運行 // } }
          }


          agent 工程中,我們自定義一個 Transformer 類


          package com.wolffy.hello;
          import jdk.internal.org.objectweb.asm.ClassWriter;import jdk.internal.org.objectweb.asm.MethodVisitor;
          import java.lang.instrument.ClassFileTransformer;import java.lang.instrument.IllegalClassFormatException;import java.security.ProtectionDomain;
          import static jdk.internal.org.objectweb.asm.Opcodes.*;
          /** * Transformer.class - By 「Java者說」 -> https://www.jiweichengzhu.com/ * Created by wolffy on 2022/2/15. */public class HelloTransformer implements ClassFileTransformer {
          /** * 轉(zhuǎn)換提供的類文件并返回一個新的替換類文件 * * @param loader 要轉(zhuǎn)換的類的定義加載器,如果引導(dǎo)加載器可能為null * @param className Java 虛擬機規(guī)范中定義的完全限定類和接口名稱的內(nèi)部形式的類名稱。例如, "java/util/List" 。 * @param classBeingRedefined 如果這是由重新定義或重新轉(zhuǎn)換觸發(fā)的,則該類被重新定義或重新轉(zhuǎn)換;如果這是一個類加載, null * @param protectionDomain 被定義或重新定義的類的保護域 * @param classfileBuffer 類文件格式的輸入字節(jié)緩沖區(qū) - 不得修改 * @return 轉(zhuǎn)換后的字節(jié)碼數(shù)組 * @throws IllegalClassFormatException class文件格式異常 */ @Override public byte[] transform(ClassLoader loader, String className, Class classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
          // 加入了自定義的transformer之后,所有需要加載的、但是還沒有加載的類,當(dāng)它們每一個要加載的時候,就需要通過transform方法 // 所以需要加一個判斷,只處理AgentTest類 if (className.equals("com/wolffy/hello/test/AgentTest")) { System.out.println("<----------------- agent加載生效,開始更改class字節(jié)碼 ----------------->"); return dumpAgentTest(); }
          // 其他AgentTest之外的類,我們直接返回null,就代表沒有任何修改,還是用它原來的class // return new byte[0]; return null; }
          /** * 更改AgentTest的字節(jié)碼,將system.out打印的內(nèi)容給替換掉 *

          * 設(shè)計到asm更改字節(jié)碼的知識點,網(wǎng)絡(luò)上很少有一個完整的知識體系,偶爾找到兩篇文章也還都是抄來抄去,這里給大家提供一個學(xué)習(xí)的地址:https://lsieun.github.io/java/asm/index.html * * @return 更改之后的class文件字節(jié)流 */ private static byte[] dumpAgentTest() { ClassWriter cw = new ClassWriter(0); MethodVisitor mv;
          cw.visit(52, ACC_PUBLIC + ACC_SUPER, "com/wolffy/hello/test/AgentTest", null, "java/lang/Object", null);
          { mv = cw.visitMethod(ACC_PUBLIC, "", "()V", null, null); mv.visitCode(); mv.visitVarInsn(ALOAD, 0); mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "", "()V", false); mv.visitInsn(RETURN); mv.visitMaxs(1, 1); mv.visitEnd(); }
          { mv = cw.visitMethod(ACC_PUBLIC + ACC_STATIC, "main", "([Ljava/lang/String;)V", null, null); mv.visitCode(); mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;"); mv.visitLdcInsn("hello world - https://www.jiweichengzhu.com/"); mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false); mv.visitInsn(RETURN); mv.visitMaxs(2, 1); mv.visitEnd(); }
          cw.visitEnd();
          return cw.toByteArray(); }
          }


          然后,我們在 premain 中將其引入,這樣它才會生效,我們才能通過它修改 class 文件。


          package com.wolffy.hello;
          import java.lang.instrument.Instrumentation;
          /** * 預(yù)先處理,程序啟動時優(yōu)先加載,JavaAgent.class - By 「Java者說」 -> https://www.jiweichengzhu.com/ * Created by wolffy on 2022/2/15. */public class HelloPremain {
          public static void premain(String agentArgs, Instrumentation inst) { System.out.println("Yes, I am a real Agent for premain Class.");
          // 引入自定義的transformer inst.addTransformer(new HelloTransformer(), true); }
          }


          引入完畢,我們直接用上面講過的 premain 方式來運行一下看看效果如何。

          直接打兩個 jar 包:HelloAgent.jar、HelloTest.jar,然后用 java -jar 命令運行。



          如上圖,HelloTest 最終輸出的文案,已經(jīng)不是我在代碼中寫的那一段了,代碼中寫的是“hello, here is hello test for agent.”,而最終打印出來的是“hello world - https://www.jiweichengzhu.com/”。

          over,演示完畢,大家是不是覺著這東西很神奇,同時也很可怕,這都不是在改你的 java 代碼了,直接在運行之前把你 class 字節(jié)碼給你改了?。。?/span>



          案例代碼:
          https://github.com/yuluowuying/JavaAgentDemo
          https://gitee.com/feisong/java-agent-demo



          掃描二維碼獲取

          更多精彩

          Java者說




          7
          字節(jié)碼框架學(xué)習(xí)




          上面通過一個非常簡單的 demo 給大家演示了如何去修改字節(jié)碼,以及修改字節(jié)碼都能夠達(dá)到的效果。

          這里給大家推薦幾個市面上常見的幾款字節(jié)碼工具,通過它們封裝的一些 API 也可以進行字節(jié)碼操作。

          BCEL:Byte Code Engineering Library,這是Apache Software Foundation 的Jakarta 項目的一部分,它可以讓您深入 JVM 匯編語言進行類操作的細(xì)節(jié)。

          JBET:Java Binary Enhancement Tool,用一種結(jié)構(gòu)化的方式來展現(xiàn)Javabinary (.class)文件的內(nèi)容,并且可以很容易的進行修改,可對Class文件進行分解,重新組合。

          Javassist:Javassist 是一個開源的分析、編輯和創(chuàng)建 Java 字節(jié)碼的類庫,已加入了開放源代碼 JBoss 應(yīng)用服務(wù)器項目,通過使用 Javassist 對字節(jié)碼操作為 JBoss 實現(xiàn)動態(tài) AOP 框架。

          ASM:是一個強大的、高性能、高質(zhì)量的 Code 生成類庫,它可以在運行期擴展Java類與實現(xiàn)Java接口,cglib封裝了asm,可以在運行期動態(tài)生成新的 class,Hibernate和Spring都用到過它,cglib用于AOP,jdk中的proxy必須基于接口,cglib卻沒有這個限制。

          bcel 和 jbet 我沒怎么用過,了解不多,javassist 和 asm 我都有親自使用過,二者雖然功能類似,但還是有不少區(qū)別的,從我個人使用角度來給大家總結(jié)幾點差異。

          1、javassist 提供的是基于源碼級別的 API,而 asm 則是基于字節(jié)碼層面的 API;
          2、javassist 使用起來更加簡單,而 asm 則要求有字節(jié)碼方面的知識;
          3、asm 直接操作字節(jié)碼,更加貼近底層,靈活度方面要高于 javassist;
          4、操作源碼比操作字節(jié)碼多了一層,所以 asm 的性能要高于 javassist;

          以上是從它們的技術(shù)實現(xiàn)上手難易度這兩個層面做的區(qū)分,其他層面的差異也有,這里不再細(xì)說。

          就我個人使用感受來說,還是 asm 來的實在,因為我們一般都是用 agent 技術(shù)來寄生于其他應(yīng)用之上,那么 agent 的性能就是我們的首要考慮因素,再怎么搞也不能影響到原來的應(yīng)用,其次我們既然是要操作底層字節(jié)碼,那除了性能之外,第二考慮的應(yīng)該就是靈活度,越靈活的東西,我們能用來實現(xiàn)的功能就越多。

          但是字節(jié)碼這個東西,可以說是一個冷門技術(shù),日常的工作中很少會直接使用它,所以網(wǎng)絡(luò)上找的一些資料也都不是很齊全,甚至有的直接就是翻譯官方文檔,非常的生硬,生搬硬套很不容易理解。

          在這里推薦給大家一個精講 asm 技術(shù)的網(wǎng)站:https://lsieun.github.io/,站長 lsieun 是研究生畢業(yè),從事 java 底層技術(shù)研究多年,對 asm 技術(shù)有很深入的理解,寫的一些文章中都配有 demo 講解,通俗易懂,大家有興趣的可以去逛逛。



          由于 asm 技術(shù)偏底層,入門有一定的門檻,大家直接看文章可能有難度,為此站長? lsieun 特意錄制了視頻教程,若有需要,大家可以去看一下,因為付出了不少時間和精力,所以收取一定的費用,價格不貴,絕對物超所值。

          如果有想要報名學(xué)習(xí)的,不要著急過去,先從下面的鏈接中領(lǐng)取一張免費的優(yōu)惠券,希望你們通過這套課程進行系統(tǒng)的學(xué)習(xí)之后,能夠徹底掌握 asm 這個框架



          課程報名地址:
          https://ke.qq.com/course/4335150

          優(yōu)惠領(lǐng)取地址:
          https://lsieun.github.io/java-agent/dream



          1.?5 道面試題,拿捏 String 底層原理!

          2.?JMH 和 Arthas 定位問題的案例分享 !

          3.?增加了一行代碼,讓我們提高了 3000% 的性能

          4.?private修飾的方法可以通過反射訪問,那么private的意義是什么?

          最近面試BAT,整理一份面試資料Java面試BATJ通關(guān)手冊,覆蓋了Java核心技術(shù)、JVM、Java并發(fā)、SSM、微服務(wù)、數(shù)據(jù)庫、數(shù)據(jù)結(jié)構(gòu)等等。

          獲取方式:點“在看”,關(guān)注公眾號并回復(fù)?Java?領(lǐng)取,更多內(nèi)容陸續(xù)奉上。

          PS:因公眾號平臺更改了推送規(guī)則,如果不想錯過內(nèi)容,記得讀完點一下在看,加個星標(biāo),這樣每次新文章推送才會第一時間出現(xiàn)在你的訂閱列表里。

          “在看”支持小哈呀,謝謝啦??

          瀏覽 43
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

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

          手機掃一掃分享

          分享
          舉報
          <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>
                  2021国产精品 | 婷婷五月天丁香成人社区 | 香蕉视频一级片 | 爱情岛亚洲品质自拍视频 | 肏屄网站在线观看 |