溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Java中Agent如何動態(tài)修改字節(jié)碼

發(fā)布時間:2021-09-08 13:35:06 來源:億速云 閱讀:167 作者:小新 欄目:開發(fā)技術

這篇文章主要介紹了Java中Agent如何動態(tài)修改字節(jié)碼,具有一定借鑒價值,感興趣的朋友可以參考下,希望大家閱讀完這篇文章之后大有收獲,下面讓小編帶著大家一起了解一下。

1、什么是Java Agent

Java Agent是一種特殊類型的類,通過使用Java Instrumentation API,它可以攔截JVM上運行的應用程序,修改它們的字節(jié)碼。Java Agent非常強大,也非常危險。

在開始之前,我將解釋Java Agent如何使用簡單的HelloWorld示例攔截類。

public class Hello {
    public static void main(String[] args){
        System.out.println("hello world");
    }
}

如下圖所示:

Java中Agent如何動態(tài)修改字節(jié)碼

類加載器負責將類從二進制加載到內(nèi)存中。運行編譯后的HelloWorld應用程序(HelloWorld.class)時,可以將Agent視為在運行時攔截類加載器行為的一種方式。您可能會想,java字節(jié)代碼是如何被重新構造的,以便Agent可以在正確的位置添加相關代碼的。有趣的是,對于Java程序來說,字節(jié)碼的結構非常接近原始Java程序源代碼。因此,雖然我們不為Java程序本身添加工具,但我們使用了它的一個非常接近的表示形式。需要注意的是,有一些非Java語言可以編譯成Java字節(jié)碼(如Scala、Clojure和Kotlin),這意味著程序字節(jié)碼的結構和形狀可能會非常不同。

2、實現(xiàn)Java Agent

JavaAgent基于來自Java平臺的facility,它的入口點是Java.lang instrument包,它提供了允許Agent為JVM上運行的程序提供工具的服務。該包非常簡單且自包含,因為它包含兩個異常類、一個數(shù)據(jù)類、類定義和兩個接口。在這兩種方法中,如果我們想編寫Java Agent,我們只需要實現(xiàn)classFileTransformer接口。

定義代理有兩種方法。

第一個是靜態(tài)代理,這意味著我們構建Agent代理,將其打包為jar文件,當我們啟動Java應用程序時,我們傳入一個名為java agent的特殊JVM參數(shù)。然后我們給它代理jar在磁盤上的位置,然后JVM發(fā)揮它的魔力。

$ java -javaagent:<path of agent jar file> -jar <path of the packaged jar file you want to intecept>

我們需要添加一個特殊的清單條目,稱為pre-main類,當然,這是一個完全限定的名稱類定義。

Premain-Class : org.example.JavaAgent

這個類看起來像這樣

public class JavaAgent {
    /**
     * As soon as the JVM initializes, This  method will be called.
     *
     * @param agentArgs       The list of agent arguments
     * @param instrumentation The instrumentation object
     * @throws InstantiationException
     */
    public static void premain(String agentArgs, Instrumentation instrumentation) throws InstantiationException {
        InterceptingClassTransformer interceptingClassTransformer = new InterceptingClassTransformer();
        interceptingClassTransformer.init();
        instrumentation.addTransformer(interceptingClassTransformer);
    }
}

premain方法接受兩個參數(shù):

  • agentArgs—字符串參數(shù),用戶選擇作為參數(shù)傳遞給Java Agent調(diào)用的任何參數(shù)。

  • instrumentation來自java.lang instrument包,我們可以添加一個新的ClassFileTransformer對象,它包含Agent的實際邏輯。

第二個選項稱為動態(tài)代理。

我們可以做的不是檢測啟動應用程序的方式,而是編寫一小段代碼,接收并連接到現(xiàn)有JVM,并告訴它加載某個代理。

VirtualMachine vm = VirtualMachine.attach(vmPid);
vm.load(agentFilePath);
vm.detach();

此參數(shù)agentFilePath與靜態(tài)代理方法中的參數(shù)完全相同。它必須是agent jar的文件名,因此沒有輸入流也沒有字節(jié)。這種方法有兩個警告。第一個是,這是生活在com.sun空間下的私有API,它通常適用于熱點實現(xiàn)。第二個問題是,使用Java9進行排序時,您不能再使用此代碼連接到它正在運行的JVM。

2.1 類轉換

這是我們需要為agent實現(xiàn)的接口,以便轉換類。

public interface ClassFileTransformer {
    byte[] transform(ClassLoader loader, 
                     String className, 
                     Class<?> classBeingRedefined,
                     ProtectionDomain protectionDomain, 
                     byte[] classfileBuffer) 
            throws IllegalClassFormatException;
}

這有點多,但我將解釋方法簽名中的必要參數(shù)。第一個重要的是類名。此參數(shù)的主要目的是幫助查找并區(qū)分要攔截的正確類和其他類。顯然,您可能不想截取應用程序中的每個類,最簡單的方法是使用條件語句進行檢查。

然后是類加載器,它主要用于基本應用程序沒有平坦類空間的環(huán)境中,您可能不需要查看它,但一旦遇到更復雜或模塊化的平臺,您就需要查看類加載器。classfileBuffer是檢測前類的當前定義。要截取它,您需要使用庫讀取這個字節(jié)數(shù)組并截取代碼,然后必須再次轉換回字節(jié)碼才能返回。

有幾個字節(jié)碼生成庫。您需要進行研究,并根據(jù)它是高級API還是低級API、社區(qū)大小和許可證自行決定。我放在下面的演示是Javassist,因為我認為它在高級和低級API之間有一個很好的平衡,并且是一個三重許可證,所以幾乎任何人都可以使用它。這

就是ClassFileTransformer實現(xiàn)的主體。

@Override
public byte[] transform(ClassLoader loader, ..)
        throws .. {
    byte[] byteCode = classfileBuffer;
    // If you wanted to intercept all the classs then you can remove this conditional check.
    if (className.equals("Example")) {
        try {
            ClassPool classPool = scopedClassPoolFactory.create(loader, rootPool,
                    ScopedClassPoolRepositoryImpl.getInstance());
            CtClass ctClass = classPool.makeClass(new ByteArrayInputStream(classfileBuffer));
            CtMethod[] methods = ctClass.getDeclaredMethods();
            for (CtMethod method : methods) {
                if (method.equals("main")) {
                    method.insertAfter("System.out.println(\"Logging using Agent\");");
                }
            }
byteCode = ctClass.toBytecode();
            ctClass.detach();
        } catch (Throwable ex) {
            log.log(Level.SEVERE, "Error in transforming the class: " + className, ex);
        }
    }
    return byteCode;
}
ctClass.detach();
        } catch (Throwable ex) {
            log.log(Level.SEVERE, "Error in transforming the class: " + className, ex);
        }
    }
    return byteCode;
}

在這里,從類池中,我們可以直接繞過classfileBuffer獲取類,因為我想使用main方法。我們循環(huán)遍歷類定義中的所有方法,得到我們想要的類。我們根本不需要使用字節(jié)碼。我們可以簡單地向它傳遞一些合法的Java代碼,然后Javassist將編譯它,生成新的字節(jié)碼,并給出定義。

有三種方法可以向方法中插入一些Java代碼。insertAfter(..)在正文末尾插入字節(jié)碼。它在正文的末尾插入字節(jié)碼。insertAt(..)在正文的指定行插入字節(jié)碼,insertBefore(..)在正文的開頭插入字節(jié)碼。

2.2 使用Java代理進行實際操作

從指定的鏈接下載示例應用程序和Java Agent。

使用進入路徑并執(zhí)行命令mvn clean install來構建這兩個repo

現(xiàn)在,您將獲得目標中的jar文件。復制示例應用程序中.jar文件的路徑,并復制Java Agent中-dependencies.jar文件的路徑。

首先,使用命令$java-jar<path of the packaged jar>僅與示例應用程序一起運行應用程序,并觀察輸出。Hi I am main。將在控制臺中打印。

然后,使用命令$ java -javaagent:<path of agent jar file> -jar <path of the packaged jar
file you want to intercept>
并觀察輸出。將在控制臺中另外打印使用代理的日志記錄。這確保java agent被攔截并添加到main方法的主體中。

總之,如果要實現(xiàn)Java Agent,請執(zhí)行以下操作:

1. 您需要創(chuàng)建兩個Java類。一個是premain方法(JavaAgent),另一個是擴展ClassFileTransformer的類(CustomTransformer

2. 在premain方法的主體內(nèi),需要添加擴展ClassFileTransformer的類的對象

3. 然后,您需要在CustomTransformer中的重寫方法transform中添加邏輯。

4. 在轉換方法內(nèi)轉換字節(jié)碼時,可能需要根據(jù)用途使用字節(jié)碼生成庫。

5. 您需要在清單中指定premain類并構建jar。

6. 使用javaagent標記將代理加載到要攔截的應用程序中。

感謝你能夠認真閱讀完這篇文章,希望小編分享的“Java中Agent如何動態(tài)修改字節(jié)碼”這篇文章對大家有幫助,同時也希望大家多多支持億速云,關注億速云行業(yè)資訊頻道,更多相關知識等著你來學習!

向AI問一下細節(jié)

免責聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權內(nèi)容。

AI