作者:小傅哥
博客:https://bugstack.cn
沉澱、分享、成長,讓自己和他人都能有所收穫!
一、前言
在上一篇 Helloworld 中,我們初步嘗試使用了 Javassist
字節編程的方式,來創建我們的方法體並通過反射調用運行了結果。大致瞭解到創建在使用字節碼編程的時候基本離不開三個核心類;ClassPool
、CtClass
、CtMethod
,它們分別管理着對象容器、類和方法。但是我們還少用一樣就是字段;CtFields
,在這一章節中我們不止會使用字段,還會創建多個不同入參類型和返回值的學習。
在學習之前先重點列一下相關的知識點,如下;
CtClass.doubleType
、intType
、floatType
等 8 個基本類型和一個voidType
,也就是空的返回類型。- 傳遞和返回的是對象類型時,那麼需要時用;
pool.get(Double.class.getName()
,進行設置。 - 當需要設置多個入參時,需要在數組中以此設置入參類型;
new CtClass[]{CtClass.doubleType, CtClass.doubleType}
。 - 在方法體中需要取得入參並計算時,需要使用
$1
、$2
…,數字表示入參的位置。$0
是 this。 - 設置屬性字段,並賦值
Javassist
中的裝箱/拆箱
好!那麼我們就開始對這些知識點進行應用,創建出類和對應的方法。「所有代碼都可以關注公衆號:bugstack蟲洞棧
,回覆碼下載獲取」
二、開發環境
- JDK 1.8.0
- 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();
}
}
這裏面有幾個核心點,講解如下;
CtField
,屬性字段的創建。這就像我們正常寫代碼一樣,需要設定屬性的;名稱、類型以及是public
的還是private
的以及static
和final
等。都可以通過Modifier.PRIVATE
+Modifier.STATIC
+Modifier.FINAL
,通過組合來控制。同樣這也適用於對方法類型的設置。同時需要在添加屬性的地方,設置初始值。- 接下來是我們設置了一個求圓面積的方法,如果說在方法體中需要使用到入參類型。那麼需要通過符號 $+數字,來獲取入參。這個數字就是當前入參的位置。比如取第一個入參:
$1
,以此類推。 - 之後是我們的多種入參類型,在這開始我們也提到了。如果是基本類型入參都可以使用
CtClass.doubleType
,對象類型入參使用pool.get(類.class.getName)
獲取。 - 最終同樣我們會把使用字節碼編譯的 class 輸出到工程目錄下
ctClass.writeFile()
。 - 在Javassist中並不會給類型做拆箱和裝箱操作,需要顯式的處理。例如上面案例中,需要將
double
使用Double.valueOf
進行轉換。
下面這張基本描述了一個類方法在創建時候不同參數的含義,可以參考。
五、測試結果
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
來獲取。這也是後續做一些監控獲取入參的方法。