天天看點

位元組碼程式設計,基于javassist的第一個案例helloworld

作者:小傅哥

作者:小傅哥

部落格:https://bugstack.cn

沉澱、分享、成長,讓自己和他人都能有所收獲!

一、前言

在位元組碼程式設計方面有三個比較常見的架構;ASM、byte-buddy、Javassist,他們都可以對這位元組碼進行操作,隻是操作方式和控制粒度不同。

其中 ASM 更偏向于底層,需要了解 JVM 虛拟機中指定規範以及對局部變量以及操作數棧的知識。雖然在編寫起來比較麻煩,但是它也是性能最好功能最強的位元組碼操作架構。常見的會用在 CGLIB 動态代理類中,以及一些非入侵的探針監控場景中。

另外兩個架構都是有強大的 API,操作使用上更加容易控制。雖然對對比上會比 ASM 性能差一些,但不是說性能完全不好。同樣在一些監控場景中也用的非常多。如果你細心可以在你的工程 jar 包搜尋一下。

在這之前我已經編寫了 Javaagent全鍊路監控 和 ASM 的部分文章,雖然這部分技術内容在 CRUD 開發中并不常用,但随着自動化測試、非入侵監控的大量使用,還是蠻多人需要這樣的技能學習的。同時我也是這樣一個技能的學習者,為此後面會陸續編寫和完善關于 位元組碼程式設計 這個專欄。也希望這個專欄在提升自己技術棧的同時也幫助他人成長。

那麼,小傅哥計劃從 Javassist 到 ASM 陸續完成整套專欄學習的文章編寫。從簡單入門到應用操作,一步步來完成成體系的技術知識棧學習。

好!,現在開始第一個Helloworld案例。相關源碼可以通過關注 公衆号:bugstack蟲洞棧 擷取

二、開發環境

  1. JDK 1.8.0
  2. javassist 3.12.1.GA

    <dependency>

    <groupId>javassist</groupId>

    <artifactId>javassist</artifactId>

    <version>3.12.1.GA</version>

    <type>jar</type>

    </dependency>

三、案例目标

不看實作過程的話,我們的案例目标其實很簡單,就是使用 javassist 輸出一行 Helloworld 。這話像不像産品說的

public class HelloWorld {
    public static void main(String[] args) {
        System.out.println("javassist hi helloworld by 小傅哥(bugstack.cn)");
    }

    public HelloWorld() {
    }
}
           

以上的這段代碼就是我們接下來需要使用位元組碼程式設計技術來實作的内容。

四、技術實作

其實輸出一個 Helloworld 還是蠻簡單的,主要是從這裡面去學習一下 Javassist 的基本文法結構,也能為後續的學習有一個基礎的概念。

javassist Helloworld
/**
 * 公衆号:bugstack蟲洞棧
 * 部落格棧:https://bugstack.cn - 沉澱、分享、成長,讓自己和他人都能有所收獲!
 * 本專欄是小傅哥多年從事一線網際網路Java開發的學習曆程技術彙總,旨在為大家提供一個清晰詳細的學習教程。如果能為您提供幫助,請給予支援(關注、點贊、分享)!
 */
public class GenerateClazzMethod {


    public static void main(String[] args) throws IOException, CannotCompileException, NotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {

        ClassPool pool = ClassPool.getDefault();

        // 建立類 classname:建立類路徑和名稱
        CtClass ctClass = pool.makeClass("org.itstack.demo.javassist.HelloWorld");

        // 添加方法
        CtMethod mainMethod = new CtMethod(CtClass.voidType, "main", new CtClass[]{pool.get(String[].class.getName())}, ctClass);
        mainMethod.setModifiers(Modifier.PUBLIC + Modifier.STATIC);
        mainMethod.setBody("{System.out.println(\"javassist hi helloworld by 小傅哥(bugstack.cn)\");}");
        ctClass.addMethod(mainMethod);

        // 建立無參數構造方法
        CtConstructor ctConstructor = new CtConstructor(new CtClass[]{}, ctClass);
        ctConstructor.setBody("{}");
        ctClass.addConstructor(ctConstructor);

        // 輸出類内容
        ctClass.writeFile();

        // 測試調用
        Class clazz = ctClass.toClass();
        Object obj = clazz.newInstance();

        Method main = clazz.getDeclaredMethod("main", String[].class);
        main.invoke(obj, (Object)new String[1]);

    }

}
           

這段代碼分為幾塊内容來實作功能,分别包括;

  1. 建立 ClassPool,它是一個基于HashMap實作的 CtClass 對象容器。
  2. 使用 CtClass,建立我們的類資訊,也就是類的路徑和名稱。
  3. 接下來就是給類添加方法。包括;方法的屬性、類型、名稱、入參、出參和方法體的内容。
  4. 在方法建立好後還需要建立一個空的構造函數,每一個類都會在編譯後生成這樣一個構造函數。
  5. 當方法建立完成後,我們使用 ctClass.writeFile() 進行輸出方法的内容資訊。也就可以看到通過我們使用 Javassist 生成類的樣子。
  6. 最後就是我們的反射調用 main 方法,測試輸出結果。

五、測試結果

當我們執行測試的時候會輸出類資訊到工程檔案夾下,同時會輸出我們的測試結果;

1. 使用Javassist生成的類

位元組碼程式設計,基于javassist的第一個案例helloworld

使用Javassist生成的類,在工程檔案夾下

2. 輸出的測試結果

javassist hi helloworld by 小傅哥(bugstack.cn)

Process finished with exit code 0
           

六、總結

  • 關于 Javassist 的使用在完整的且強大的 API 下,确實還是蠻容易使用的。并且代碼的使用上并不是很難了解。
  • 後續會陸續推出位元組碼程式設計的案例文章,逐漸完善這部分技術知識棧的内容。最終嘗試使用這樣的技術知識完成一個案例級别的品質檢測系統。也歡迎喜歡此類内容的小夥伴跟進學習。
  • 後續的文章可能在專欄類的文章裡,文章内容上會短一點。盡可能在一篇文章中描述清楚一個詳盡的知識點,也友善後續整理成 PDF 書籍,友善學習使用。