字節碼插樁技術


字節碼插樁

我們知道JVM是不能直接執行.java 代碼,也不能直接執行.class文件,它只能執行.class 文件中存儲的指令碼。這就是為什么class需要通過classLoader 裝載以后才能運行。基於此機制可否在ClassLoader裝載之前攔截修改class當中的內容(jvm 指令碼)從而讓程序中包含我們的埋點邏輯呢?答案是肯定的,但需要用到兩個技術 javaagent與javassist 。前者用於攔截ClassLoad裝載,后者用於操作修改class文件。

 

javaagent

javaagent介紹

javaagent 是java1.5之后引入的特性,其主要作用是在class 被加載之前對其攔截,以插入我們的監聽字節碼

javaagent jar包

javaagent 最后展現形式是一個Jar包,有以下特性:

1.必須 META-INF/MANIFEST.MF中指定Premain-Class 設定啟agent啟動類。

2.在啟類需寫明啟動方法 public static void main(String arg,)

3.不可直接運行,只能通過 jvm 參數-javaagent:xxx.jar 附着於其它jvm 進程運行。

 

javaagent使用

1、編寫agent方法

public class MyAgent {
    public static void premain(String args, Instrumentation instrumentation) throws Exception {
        System.out.println("Hello javaagent permain:"+args);
    }
}

2、添加premain-class參數

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-jar-plugin</artifactId>
    <version>2.2</version>
    <configuration>
        <archive>
            <manifestEntries>
                <Project-name>${project.name}</Project-name>
                <Project-version>${project.version}</Project-version>
                <Premain-Class>com.javaagent.MyAgent</Premain-Class>
                <Can-Redefine-Classes>true</Can-Redefine-Classes>
                <Can-Retransform-Classes>true</Can-Retransform-Classes>
            </manifestEntries>
        </archive>
        <skip>true</skip>
    </configuration>
</plugin>

3、構建打包

4、在任一JAVA應用中 添加jvm 參數並啟動 -javaagent:xxx.jarjavaagent META-INF/MANIFEST.MF

參數說明:

Premain-Class:必填,agent啟動

classCan-Redefine-Classes:默認為false ,是否允許重新定義

classCan-Retransform-Classes:默認為false,是否允許重置Class,重置后相當於class 從classLoade中清除,下次有需要的時候會重新裝載,也會重新走Transformer 流程。

Boot-Class-Path:agent 所依賴的jar 路徑,多個用空格分割

創建一個測試類MyAgentTest並運行查看結果

public class MyAgentTest {
    public static void main(String[] args) {
        System.out.println("main");
    }
}
//運行結果:main

添加jvm參數

參數內容:-javaagent:/Users/jinyunlong/IdeaProjects/test-agent/target/test-agent-1.0-SNAPSHOT.jar=123


再次運行測試類MyAgentTest並查看結果

javassist

javassist介紹

javassist是一個開源的分析、編輯和創建Java字節碼的類庫。其主要的優點,在於簡單,而且快速。直接使用java編碼的形式,而不需要了解虛擬機指令,就能動態改變類的結構,或者動態生成(注:也可以使用ASM實現,但需要會操作字節碼指令,學習使用成本高)

javassist使用

使用javassist需要引入javasssist的jar包,添加內容如下:

<dependencies>
    <dependency>
        <groupId>org.javassist</groupId>
        <artifactId>javassist</artifactId>
        <version>3.18.1-GA</version>
    </dependency>
</dependencies>
<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-jar-plugin</artifactId>
            <version>2.2</version>
            <configuration>
                <archive>
                    <manifestEntries>
                        <Project-name>${project.name}</Project-name>
                        <Project-version>${project.version}</Project-version>
                        <Premain-Class>com.javaagent.MyAgent</Premain-Class>
                        <Can-Redefine-Classes>true</Can-Redefine-Classes>
                        <Can-Retransform-Classes>true</Can-Retransform-Classes>
                        <Boot-Class-Path>javassist-3.18.1-GA.jar</Boot-Class-Path>
                    </manifestEntries>
                </archive>
                <skip>true</skip>
            </configuration>
        </plugin>
    </plugins>
</build>

演示插入打印當前時間

創建類MyServer

public class MyServer {
    public Integer sayHello(String name,String message){
        System.out.println("hello");
        return 0;
    }
}

myAgent類

 

創建測試類並調用MyServer中的sayHello方法

演示計算方法調用時間

類MyAgent

public class MyAgent {
    public static void premain(String args, Instrumentation instrumentation) throws Exception {
        instrumentation.addTransformer(new ClassFileTransformer() {
            public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, 
            byte[] classfileBuffer) throws IllegalClassFormatException {
                if(!"com/javaagent/MyServer".equals(className)){
                    return null;
                }
                try {
                    return buildMonitorClass();
                } catch (Exception e) {
                    e.printStackTrace();
                }
                return null;
            }
        },true);

    }
    private static byte[] buildMonitorClass() throws Exception{
        /**
         * 1、拷貝一個新的方法
         * 2、修改原方法名
         * 3、加入監聽代碼
         */
        ClassPool pool = new ClassPool();
        pool.appendSystemPath();
        CtClass ctClass = pool.get("com.javaagent.MyServer");
        CtMethod ctMethod = ctClass.getDeclaredMethod("sayHello");
        CtMethod copyMethod = CtNewMethod.copy(ctMethod,ctClass,new ClassMap());
        ctMethod.setName("sayHello$agent");
        copyMethod.setBody("{\n" +
                "    long begin = System.nanoTime();\n" +
                "    try {\n" +
                "        return sayHello$agent($1,$2);\n" +
                "    } finally {\n" +
                "        System.out.println(System.nanoTime() - begin);}\n" +
                "    }");
        ctClass.addMethod(copyMethod);
        return ctClass.toBytecode();
    }
}

修改類MyServer

public class MyServer {

    public Integer sayHello(String name,String message){
        System.out.println("hello name:"+name+",message:"+message);
        return 0;
    }
}

修改測試類並運行

public class MyAgentTest {
    public static void main(String[] args) {
        MyServer myServer = new MyServer();
        myServer.sayHello("paul","1234");
    }
}
//運行結果:
hello name:paul,message:1234
186537

javassist 特殊語法

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM