學會Java Agent你能作什麼?html
來源:網易有道詞典-專業釋義-計算機科學技術
Instrumentation: 在計算機科學技術中的英文釋義是插樁、植入。
instrument: 儀器(儀器是指用以檢出、測量、觀察、計算各物理量、物質成分、物性參數等的器具或設備。)java
動態 Instrumentation 是 Java SE 5 的新特性,它在 java.lang.instrument
包中,它把 Java 的 instrument 功能從本地代碼中釋放出來,使其能夠用 Java 代碼的方式解決問題。使用 Instrumentation
,開發者能夠構建一個獨立於應用程序的代理程序(Agent),用來監測和協助運行在 JVM 上的程序,甚至能夠替換和修改某些類的定義。有了這樣的功能,開發者就能夠實現更爲靈活的虛擬機監控和 Java的 類操做了,這樣的特性實際上提供了一種虛擬機級別支持的 AOP方式,使得開發者無需對原有應用作任何修改,就能夠實現類的動態修改和加強面試
java.lang.instrument
包被賦予了更強大的功能:啓動後的 監測、本地代碼(native code)監測,以及動態改變 classpath
等等。這些改變,意味着 Java 具備了更強的動態控制與解釋能力,它使得 Java 語言變得更加靈活多變。apache
在 Java SE6 裏面,最大的改變是可以植入代碼到運行時的JVM 程序。在 Java SE 5 中,Instrument
要求在運行前利用命令行參數或者系統參數來設置代理類,在實際的運行之中,虛擬機在初始化之時(在絕大多數的 Java 類庫被載入以前),instrumentation
的設置已經啓動,並在虛擬機中設置了回調函數,檢測特定類的加載狀況,並完成實際工做。可是在實際的不少的狀況下,咱們沒有辦法在虛擬機啓動之時就爲其設定代理,這樣實際上限制了 instrument
的應用。而 Java SE 6 的新特性改變了這種狀況,經過 Java Tool API 中的 attach 方式,咱們能夠很方便地在運行過程當中動態地設置加載代理類,以達到 instrumentation
的目的。編程
另外,對 native method
的 Instrumentation 也是 Java SE 6 的一個嶄新的功能,這使之前沒法完成的功能,能夠在 Java SE 6 中經過對 native method
接口的 Instrumentation,經過一個或者一系列的 prefix 添加而得以完成。api
Java SE 6 裏的 Instrumentation 也增長了動態添加 class path 的功能。這些新的功能,都使得 java.lang.instrument
包的功能更加豐富,使得 Java 語言更增強大。數組
java.lang.instrument
包的具體實現,依賴於 JVMTI(Java Virtual Machine Tool Interface)
這是一套由 Java 虛擬機提供的,爲 JVM 相關工具提供的本地編程接口集合。JVMTI 是從 Java SE 5 開始引入,JVMTI 提供了一套「代理」程序機制,能夠支持第三方工具程序以代理的方式鏈接和訪問 JVM,並利用 JVMTI 提供的編程接口,完成不少跟 JVM 相關的功能。事實上,java.lang.instrument
包的實現,也就是基於這種機制的bash
在
Instrumentation
的實現當中,存在一個JVMTI
的代理程序,經過調用JVMTI
當中與Java 類相關的函數,來完成對 Java 類的動態操做。微信
除了 Instrumentation
功能外,JVMTI
還在虛擬機內存管理,線程控制,方法和變量操做等等方面提供了大量可用的函數。關於 JVMTI
的詳細信息,能夠參考 Java SE 6 JVM TI文檔oracle
在java中如何實現 Instrumentation 呢,簡單來講有如下幾步
建立一個普通的類,內含靜態方法premain(),這個方法名是java agent內定的方法名,它總會在main函數以前執行
package cn.jpsite.learning.javaagent01;
import java.lang.instrument.Instrumentation;
public class JpAgent {
public static void premain(String agentArgs, Instrumentation instrumentation) {
/*轉換髮生在 premain 函數執行以後,main 函數執行以前,這時每裝載一個類,transform 方法就會執行一次,看看是否須要轉換,
因此,在 transform(Transformer 類中)方法中,程序用 className.equals("TransClass") 來判斷當前的類是否須要轉換。*/
// 方式一:
System.out.println("我是兩個參數的 Java Agent premain");
}
public static void premain(String agentArgs){
System.out.println("我是一個參數的 Java Agent premain");
}
}
複製代碼
如上有2個premain()
方法,當1個參數和2個參數的premain()的方法同事存在的時候,premain(String agentArgs)
將被忽略
在resource目錄下新建META-INF/MANIFEST.MF文件,內容以下:
Manifest-Version: 1.0
Premain-Class: cn.jpsite.learning.javaagent01.JpAgent
Can-Redefine-Classes: true
Can-Retransform-Classes: true
複製代碼
以上內容能夠經過Maven的org.apache.maven.plugins
和maven-assembly-plugin
插件配合完成,在mvn install的時候生成MANIFEST.MF文件內容
目錄結構是這樣的
mvn clean install
打包,生成了
jpAgent.jar
文件 5. 新建一個maven工程example01,內含Main.java、Dog.java,並最終打包成
example01-1.0-SNAPSHOT.jar
public class Dog { public String say() { return "dog"; } }
public class Main { public static void main(String[] args) { System.out.println("夜太黑"); System.out.println("----"+new Dog().say()); } }
6. 執行
jpAgent.jar
須要經過
-javaagent
參數來指定Java代理包, >
-javaagent
這個參數的個數是不限的,能夠指定多個,會按指定的前後順序執行,執行完各個 agent 後,纔會執行主程序的 main 方法。
```
// 爲了執行方便,把jar文件放在同一層級目錄下
java -javaagent:jpAgent.jar -cp example01-1.0-SNAPSHOT.jar cn.jpsite.learning.Main
```
其中`example01-1.0-SNAPSHOT.jar`的`Main()`方法只是簡單的輸出2行內容,經過agent代理後多輸出了一段內容。
複製代碼
對 Java 類文件的操做,能夠理解爲對一個 byte 數組的操做(將類文件的二進制字節流讀入一個 byte 數組)。開發者能夠在 interface ClassFileTransformer
的 transform
方法(經過 classfileBuffer 參數)中獲得,操做並最終返回一個類的定義(一個 byte 數組),接下來演示下transform
轉換類的用法,採用簡單的類文件替換方式。
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.security.ProtectionDomain;
public class JpClassFileTransformerDemo implements ClassFileTransformer {
@Override
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined,
ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
System.out.println("loader className: " + className);
if (!className.equalsIgnoreCase("cn/jpsite/learning/Dog")) {
return null;
}
return getBytesFromFile("D:\\learning\\Dog.class");
}
public static byte[] getBytesFromFile(String fileName) {
File file = new File(fileName);
try (InputStream is = new FileInputStream(file)) {
// precondition
long length = file.length();
byte[] bytes = new byte[(int) length];
// Read in the bytes
int offset = 0;
int numRead = 0;
while (offset < bytes.length
&& (numRead = is.read(bytes, offset, bytes.length - offset)) >= 0) {
offset += numRead;
}
if (offset < bytes.length) {
throw new IOException("Could not completely read file "
+ file.getName());
}
is.close();
return bytes;
} catch (Exception e) {
System.out.println("error occurs in _ClassTransformer!"
+ e.getClass().getName());
return null;
}
}
}
複製代碼
instrumentation.addTransformer(new JpClassFileTransformerDemo());
內容以下,從新打包public class JpAgent {
public static void premain(String agentArgs, Instrumentation instrumentation) {
/*轉換髮生在 premain 函數執行以後,main 函數執行以前,這時每裝載一個類,transform 方法就會執行一次,看看是否須要轉換,
因此,在 transform(Transformer 類中)方法中,程序用 className.equals("TransClass") 來判斷當前的類是否須要轉換。*/
// 方式一:
instrumentation.addTransformer(new JpClassFileTransformerDemo());
System.out.println("我是兩個參數的 Java Agent premain");
}
public static void premain(String agentArgs){
System.out.println("我是一個參數的 Java Agent premain");
}
}
複製代碼
getBytesFromFile("D:\\learning\\Dog.class")
就是讀取修改後的class文件。java -javaagent:jpAgent.jar -cp example01-1.0-SNAPSHOT.jar cn.jpsite.learning.Main
查看結果以下:轉換髮生在 premain
函數執行以後,main 函數執行完成以前,這時每裝載一個類,transform
方法就會執行一次,看看是否須要轉換,因此,在 transform
方法中,這裏用了 className.equals("cn/jpsite/learning/Dog")
來判斷當前的類是否須要轉換。
除了用 addTransformer 的方式,Instrumentation 當中還有另一個方法「redefineClasses」來實現 premain 當中指定的轉換。用法相似,以下:
ClassDefinition def = new ClassDefinition(Dog.class, Objects.requireNonNull(JpClassFileTransformerDemo
.getBytesFromFile("D:\\learning\\Dog.class")));
instrumentation.redefineClasses(new ClassDefinition[] { def });
複製代碼
Java虛擬機參數分析平臺
java.lang.instrument api doc
Java SE 6 JVM TI文檔
Java SE 8 doc Java Attach API
JavaAgent源碼分析
Java探針-Java Agent技術-阿里面試題
本身實現一個Native方法的調用
本身實現一個Native方法的調用2
文章每週持續更新,能夠微信搜索「 十分鐘學編程 」第一時間閱讀和催更,若是這個文章寫得還不錯,以爲有點東西的話 ~求點贊👍 求關注❤️ 求分享❤️
各位的支持和承認,就是我創做的最大動力,咱們下篇文章見!