8.1 JavaAgent
Java Agent 是一種強大的技術,在運行時動態修改已加載類的字節碼,為應用程序注入額外的功能和行為。
JDK 1.5 支持靜態 Instrumentation,基本的思路是在 JVM 啟動的時候添加一個代理(javaagent),每個代理是一個 jar 包,其 MANIFEST.MF 文件里指定了代理類,這個代理類包含一個 premain 方法。JVM 在類加載時候會先執行代理類的 premain 方法,再執行 Java 程序本身的 main 方法,這就是 premain 名字的來源。在 premain 方法中可以對加載前的 class 文件進行修改。
8.1.1 應用場景:
代碼注入:通過 Java Agent 技術,我們可以在運行時將自定義的字節碼注入到已加載的類中。這樣可以實現 AOP 編程思想,將通用的邏輯代碼動態地注入到目標類中,簡化代碼的編寫和維護。
性能監控:Java Agent 可以用于實時監控應用程序的性能指標,如方法的執行時間、方法的調用次數等。通過在方法的前后插入字節碼,我們可以收集這些信息,并進行實時監控、統計和分析,幫助優化和調優應用程序的性能。
調試工具:Java Agent 可以用作調試工具的基礎。我們可以通過動態修改字節碼,將額外的調試信息插入到目標類中,例如打印方法的參數、返回值等。這樣,在調試過程中可以更加方便地獲取和分析這些信息,幫助快速定位問題。
安全檢查:Java Agent 還可以用于實現安全檢查機制。通過對正在加載的類進行攔截和檢查,我們可以在運行時進行代碼審計、防止惡意代碼注入等,提高應用程序的安全性。
8.1.2 啟動方式
啟動掛載(Agent):
所有的類在加載時都會執行transform,我們在此方法中通過 Instrumentation 增強目標類。
隨著JVM啟動一起啟動(java -javaagent:(jar包名) (方法類名))
premain(): 實現premain方法,并且添加一個自定義transformer,把操作放在自定義的transformer內,該transformer要實現ClassFileTransformer.transform() 主程序運行時:加載每個類前都會進入transform(),可以獲取到它的加載器、類名、字節碼buffer等
動態掛載(Attach):
動態掛載之后,類加載時都會執行transform,我們在此方法中通過 Instrumentation 增強目標類。
動態掛載前 已經加載的類,都未經歷增強的處理。 可通過調用retransformClasses方法,讓已加載的類重新加載, 重新加載時也會執行transform,我們在此方法中增強目標類。
在已經運行的JVM進程中,動態的插入。通過attach API調用,在sandbox中執行./sandbox -p pid(目標 jvm 進程 id)是使用的 attach api 方式進行 agent 的掛載
agentmain():對于agentmain也是一樣的流程,但多一步inst.retransformClasses()的操作讓JVM重新加載修改過的類的字節碼,否則修改不會生效 使用com.sun.tools.attach.VirtualMachine進行動態掛載Agent:
private void attachAgentToTargetJVM() throws Exception {List<VirtualMachineDescriptor> virtualMachineDescriptors = VirtualMachine.list();VirtualMachineDescriptor targetVM = null;for (VirtualMachineDescriptor descriptor : virtualMachineDescriptors) {if (descriptor.id().equals(configure.getPid())) {targetVM = descriptor;break;}}if (targetVM == null) {throw new IllegalArgumentException("could not find the target jvm by process id:" + configure.getPid());}VirtualMachine virtualMachine = null;try {virtualMachine = VirtualMachine.attach(targetVM);virtualMachine.loadAgent("{agent}", "{params}");} catch (Exception e) {if (virtualMachine != null) {virtualMachine.detach();}}}
它的 addTransformer 給 Instrumentation 注冊一個 transformer,transformer 是 ClassFileTransformer 接口的實例,這個接口就只有一個 transform 方法,調用 addTransformer 設置 transformer 以后,后續JVM 加載所有類之前都會被這個 transform 方法攔截,這個方法接收原類文件的字節數組,返回轉換過的字節數組,在這個方法中可以做任意的類文件改寫。
8.1.3 JavaAgent使用demo
使用premain的方式進行javaAgent的啟動(不詳細展示對java類的增強demo)
首先在一個maven項目中創建我們的核心demo-AgentDemo
package com.xhz.demo;import java.lang.instrument.Instrumentation;/*** @version V1.0* @Author : xiehuizhi* @create 2023/12/20 14:33*/public class AgentDemo {public static void premain(String agentArgs, Instrumentation instrumentation){System.out.println(agentArgs+"============尊貴的xxxx===============");}public static void premain(String agentArgs){System.out.println(agentArgs+"============尊貴的xxxx================");}}
創建完成之后可以手動在resources中創建MANIFEST.MF文件(也可以在pom中配置)
Manifest-Version: 1.0
Premain-Class: com.xhz.demo.AgentDemo
Can-Redefine-Classes: true
Can-Retransform-Classes: true
?
pom配置:
<plugins><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-jar-plugin</artifactId><version>3.1.2</version><configuration><archive><manifestEntries><Premain-Class>club.throwable.permain.PermainAgent</Premain-Class><Can-Redefine-Classes>true</Can-Redefine-Classes><Can-Retransform-Classes>true</Can-Retransform-Classes></manifestEntries></archive></configuration></plugin></plugins>
然后要實現agent的功能,需要將你的項目打成jar包
打包命令:mvn clean package
然后新建一個可以啟動的Main類
package Utils;/*** @version V1.0* @Author : xiehuizhi* @create 2023/12/20 14:29*/public class Main {public static void main(String[] args) {System.out.println("流量回放");}}
?
在springboot啟動項增加啟動參數
8.2 Sandbox啟動與Agent掛載的流程
sandbox-spy:這個包里的代碼是一系列的模板代碼,后期Sandbox會通過ASM框架將這些代碼轉化成字節碼增加到業務代碼的字節碼當中,實現對業務代碼的增強功能
但spy類為什么要被BootstrapClassLoader加載呢?我們不僅要增強業務代碼,也要增強JDK里面的代碼,將spy添加到BootstrapClassLoader的類路徑,只要不破壞雙親委托機制,都能夠被spy類增強,這是一種比較保險的做法。
8.3 JVM-SandBox實現字節碼增強
一般的Spring AOP:實現業務切面,針對于JAVA后端應用也需要AOP
JVM-Sandbox的設計目的是實現一種在不重啟、不侵入目標JVM應用情況下的AOP解決方案。
Jvm-SandBox字節碼增強流程梳理:
8.3.1 增強過程
8.3.2 實現字節碼增強邏輯
通過AsmMethods接口獲取Sandbox自定義Spy類完成字節碼增強
spy增強的靜態方法
JavaAgent的本質就是字節碼增強,這章主要介紹了Sandbox字節碼增強功能的核心邏輯,包括增強邏輯、SandboxClassFileTransformer類形變器、Spy增強的靜態方法。