天天看點

通過位元組碼程式設計,在程式中動态的建立方法

作者:小傅哥

部落格:https://bugstack.cn

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

一、前言

在上一篇 Helloworld 中,我們初步嘗試使用了 Javassist位元組程式設計的方式,來建立我們的方法體并通過反射調用運作了結果。大緻了解到建立在使用位元組碼程式設計的時候基本離不開三個核心類;ClassPool、CtClass、CtMethod,它們分别管理着對象容器、類和方法。但是我們還少用一樣就是字段;CtFields,在這一章節中我們不止會使用字段,還會建立多個不同入參類型和傳回值的學習。

在學習之前先重點列一下相關的知識點,如下;

  1. CtClass.doubleType、intType、floatType等 8 個基本類型和一個voidType,也就是空的傳回類型。
  2. 傳遞和傳回的是對象類型時,那麼需要時用;pool.get(Double.class.getName(),進行設定。
  3. 當需要設定多個入參時,需要在數組中以此設定入參類型;new CtClass[]{CtClass.doubleType, CtClass.doubleType}。
  4. 在方法體中需要取得入參并計算時,需要使用 $1、$2 ...,數字表示入參的位置。$0 是 this。
  5. CtField 設定屬性字段,并指派。
  6. Javassist 中的裝箱/拆箱。

好!那麼我們就開始對這些知識點進行應用,建立出類和對應的方法。「所有代碼都可以關注公衆号: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 建立如下這樣的方法。當然你也可以嘗試去擴充其他類型的方法。

public class ApiTest {

    private double π = 3.14D;

    //S = πr²
    public double calculateCircularArea(int r) {
        return π * r * r;
    }

    //S = a + b
    public double sumOfTwoNumbers(double a, double b) {
        return a + b;
    }

}
           

四、技術實作

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

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

        ClassPool pool = ClassPool.getDefault();

        CtClass ctClass = pool.makeClass("org.itstack.demo.javassist.MathUtil");

        // 屬性字段
        CtField ctField = new CtField(CtClass.doubleType, "π", ctClass);
        ctField.setModifiers(Modifier.PRIVATE + Modifier.STATIC + Modifier.FINAL);
        ctClass.addField(ctField, "3.14");

        // 方法:求圓面積
        CtMethod calculateCircularArea = new CtMethod(CtClass.doubleType, "calculateCircularArea", new CtClass[]{CtClass.doubleType}, ctClass);
        calculateCircularArea.setModifiers(Modifier.PUBLIC);
        calculateCircularArea.setBody("{return π * $1 * $1;}");
        ctClass.addMethod(calculateCircularArea);

        // 方法;兩數之和
        CtMethod sumOfTwoNumbers = new CtMethod(pool.get(Double.class.getName()), "sumOfTwoNumbers", new CtClass[]{CtClass.doubleType, CtClass.doubleType}, ctClass);
        sumOfTwoNumbers.setModifiers(Modifier.PUBLIC);
        sumOfTwoNumbers.setBody("{return Double.valueOf($1 + $2);}");
        ctClass.addMethod(sumOfTwoNumbers);
        // 輸出類的内容
        ctClass.writeFile();

    }

}

           

這裡面有幾個核心點,講解如下;

  1. CtField,屬性字段的建立。這就像我們正常寫代碼一樣,需要設定屬性的;名稱、類型以及是 public 的還是 private 的以及 static 和 final 等。都可以通過 Modifier.PRIVATE + Modifier.STATIC + Modifier.FINAL,通過組合來控制。同樣這也适用于對方法類型的設定。同時需要在添加屬性的地方,設定初始值。
  2. 接下來是我們設定了一個求圓面積的方法,如果說在方法體中需要使用到入參類型。那麼需要通過符号 $+數字,來擷取入參。這個數字就是目前入參的位置。比如取第一個入參:$1,以此類推。
  3. 之後是我們的多種入參類型,在這開始我們也提到了。如果是基本類型入參都可以使用 CtClass.doubleType,對象類型入參使用 pool.get(類.class.getName) 擷取。
  4. 最終同樣我們會把使用位元組碼編譯的 class 輸出到工程目錄下 ctClass.writeFile()。
  5. 在Javassist中并不會給類型做拆箱和裝箱操作,需要顯式的處理。例如上面案例中,需要将 double 使用 Double.valueOf 進行轉換。

下面這張基本描述了一個類方法在建立時候不同參數的含義,可以參考。

通過位元組碼程式設計,在程式中動态的建立方法

Javassist 建立類方法入參描述

五、測試結果

1. 反射調用位元組碼類方法

在測試之前,我們需要寫一點反射代碼來調用類的方法

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

Method method_calculateCircularArea = clazz.getDeclaredMethod("calculateCircularArea", double.class);
Object obj_01 = method_calculateCircularArea.invoke(obj, 1.23);
System.out.println("圓面積:" + obj_01);

Method method_sumOfTwoNumbers = clazz.getDeclaredMethod("sumOfTwoNumbers", double.class, double.class);
Object obj_02 = method_sumOfTwoNumbers.invoke(obj, 1, 2);
System.out.println("兩數和:" + obj_02);
           

測試結果:

圓面積:4.750506
兩數和:3.0

Process finished with exit code 0
           

2. 檢視使用Javassist生成的類

通過位元組碼程式設計,在程式中動态的建立方法

Javassist 生成的類内容

六、總結

  1. 本篇案例中重點強調了屬性字段建立,同時需要給屬性字段指派。
  2. 在 Javassist 是不會進行類型的自動裝箱和拆箱的,需要我們進行手動處理,否則生成類在執行會報類型錯誤。
  3. 當需要使用入參的時候,可以使用 $1 來擷取。這也是後續做一些監控擷取入參的方法。

繼續閱讀