1. 程式人生 > >java高階-動態注入替換類Instrumentation

java高階-動態注入替換類Instrumentation

介紹

  • 利用java.lang.instrument(容器類) 做動態 Instrumentation(執行容器) 是 Java SE 5 的新特性。
  • 使用 Instrumentation,開發者可以構建一個獨立於應用程式的代理程式(Agent),用來監測和協助執行在 JVM 上的程式,甚至能夠替換和修改某些類的定義。
  • 這個功能為虛擬機器監控提供了支撐。

基本用法

1. 編寫 premain 函式

編寫一個 Java 類,包含如下兩個方法當中的任何一個

public static void premain(String agentArgs, Instrumentation inst);  [1]
public static void premain(String agentArgs); [2]

其中,[1] 的優先順序比 [2] 高,將會被優先執行([1] 和 [2] 同時存在時,[2] 被忽略)。

agentArgs 是 premain 函式得到的程式引數,隨同 “– javaagent”一起傳入。與 main 函式不同的是,這個引數是一個字串而不是一個字串陣列,如果程式引數有多個,程式將自行解析這個字串。

Inst 是一個 java.lang.instrument.Instrumentation 的例項,由 JVM 自動傳入。java.lang.instrument.Instrumentation 是 instrument 包中定義的一個介面,也是這個包的核心部分,集中了其中幾乎所有的功能方法,例如類定義的轉換和操作等等。

2. jar 檔案打包

將這個 Java 類打包成一個 jar 檔案,並在其中的 manifest 屬性當中加入” Premain-Class”來指定步驟 1 當中編寫的那個帶有 premain 的 Java 類。(可能還需要指定其他屬性以開啟更多功能)

3. 執行

用如下方式執行帶有 Instrumentation 的 Java 程式:
java -javaagent:jar 檔案的位置 [= 傳入 premain 的引數 ]

案例

1,編寫正常的類

主類

package com.yixiu.javabase.modules.dynamic.instrumentation.demo1;
public class TestMainInJar {
    public static void main(String[] args) {
        System.out.println(new TransClass().getNumber());
    }
}

工具類

package com.yixiu.javabase.modules.dynamic.instrumentation.demo1;
public class TransClass {
    public int getNumber() {
        return 1;
    }
}

2,編寫代理類

新增Premain類

package com.yixiu.javabase.modules.dynamic.instrumentation.demo1;
import java.lang.instrument.Instrumentation;
import java.lang.instrument.UnmodifiableClassException;

public class Premain {
    public static void premain(String agentArgs,Instrumentation inst)
            throws ClassNotFoundException,UnmodifiableClassException
    {
        System.out.println("Premain");
        inst.addTransformer(new Transformer());
    }
}

類檔案轉化器:
注意:實際測試時,要去掉中文,否則javac編譯報錯。


package com.yixiu.javabase.modules.dynamic.instrumentation.demo1;

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;

class Transformer implements ClassFileTransformer {

   // 注意是完整路徑 
    public static final String classNumberReturns2 = "E:\\Git\\Java\\JavaLearning\\src\\main\\java\\com\\yixiu\\javabase\\modules\\dynamic\\instrumentation\\demo1\\classes\\TransClass2.java.2";

    public static byte[] getBytesFromFile(String fileName) {
        try {
            // precondition
            File file = new File(fileName);
            InputStream is = new FileInputStream(file);
            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();

            System.out.println("class length:" + String.valueOf( bytes.length ) );

            return bytes;
        } catch (Exception e) {
            System.out.println("error occurs in _ClassTransformer!"
                    + e.getClass().getName());
            return null;
        }
    }

    public byte[] transform(ClassLoader l, String className, Class<?> c,
                            ProtectionDomain pd, byte[] b) throws IllegalClassFormatException {


        System.out.println("load:" + className );
        
        // 注意,類名稱是完整的路徑  load:com/yixiu/javabase/modules/dynamic/instrumentation/demo1/TransClass
        if(!className.contains("TransClass") ) {
            return null;
        }
//        if (!className.equals("TransClass")) {
//            return null;
//        }
        return getBytesFromFile(classNumberReturns2);

    }
}

3,編譯,打包

建立目錄classes,編譯所有檔案到classes中  
javac -d .\classes .\*.java

切換目錄
cd classes

打包
jar -cvf my.jar .\*

使用rar開啟my.jar,修改MANIFEST.MF,新增
Premain-Class: com.yixiu.javabase.modules.dynamic.instrumentation.demo1.Premain

4,實際要動態注入替換的類

javac編譯時檢查(字尾名是.java,類名和檔名相同)
要新增的代替類,必須編譯為位元組碼後才可以新增
先修改類TransClass的返回值,編譯後,將檔名重新命名為TransClass2.java.2,和jar包放在同一個目錄中

package com.yixiu.javabase.modules.dynamic.instrumentation.demo1;
public class TransClass {
    public int getNumber() {
        return 2;
    }
}

5,編譯修改的包,執行

mentation\demo1\classes>java -javaagent:my.jar -cp my.jar  com.yixiu.javabase.modules.dynamic.instrumentation.demo1.TestMainInJar

輸出如下:
E:\Git\Java\JavaLearning\src\main\java\com\yixiu\javabase\modules\dynamic\instru
mentation\demo1\classes>java -javaagent:my.jar -cp my.jar com.yixiu.javabase.modules.dynamic.instrumentation.demo1.TestMainInJar
Premain
load:java/lang/invoke/MethodHandleImpl
load:java/lang/invoke/MethodHandleImpl$1
load:java/lang/invoke/MethodHandleImpl$2
load:java/util/function/Function
load:java/lang/invoke/MethodHandleImpl$3
load:java/lang/invoke/MethodHandleImpl$4
load:java/lang/ClassValue
load:java/lang/ClassValue$Entry
load:java/lang/ClassValue$Identity
load:java/lang/ClassValue$Version
load:java/lang/invoke/MemberName$Factory
load:java/lang/invoke/MethodHandleStatics
load:java/lang/invoke/MethodHandleStatics$1
load:sun/misc/PostVMInitHook
load:sun/usagetracker/UsageTrackerClient
load:java/util/concurrent/atomic/AtomicBoolean
load:sun/usagetracker/UsageTrackerClient$1
load:sun/usagetracker/UsageTrackerClient$4
load:sun/usagetracker/UsageTrackerClient$2
load:java/lang/ProcessEnvironment
load:java/lang/ProcessEnvironment$NameComparator
load:java/lang/ProcessEnvironment$EntryComparator
load:java/util/Collections$UnmodifiableMap
load:java/lang/ProcessEnvironment$CheckedEntrySet
load:java/util/HashMap$EntrySet
load:java/lang/ProcessEnvironment$CheckedEntrySet$1
load:java/util/HashMap$EntryIterator
load:java/util/HashMap$HashIterator
load:java/lang/ProcessEnvironment$CheckedEntry
load:sun/usagetracker/UsageTrackerClient$3
load:java/io/FileOutputStream$1
load:sun/launcher/LauncherHelper
load:com/yixiu/javabase/modules/dynamic/instrumentation/demo1/TestMainInJar
load:sun/launcher/LauncherHelper$FXHelper
load:java/lang/Class$MethodArray
load:java/lang/Void
load:com/yixiu/javabase/modules/dynamic/instrumentation/demo1/TransClass
class length:309
2
load:java/lang/Shutdown
load:java/lang/Shutdown$Lock

參考