一. javassist簡介
Javassist是一個開源的分析、編輯和創建Java位元組碼的類別庫,是由東京技術學院的數學和計算機科學系的 Shigeru Chiba 所創建的,它已加入了開放源代碼JBoss 應用服務器專案,通過使用Javassist對位元組碼操作為JBoss實作動態AOP框架,
Javassist(JAVA編程ASSISTant)使Java位元組碼操作變得簡單, 它是一個用Java編輯位元組碼的類別庫; 它使Java程式能夠在運行時定義新類,并在JVM加載時修改類檔案, 與其他類似的位元組碼編輯器不同,Javassist提供兩個級別的API:源級別和位元組碼級別, 如果用戶使用源級API,他們可以在不知道Java位元組碼規范的情況下編輯類檔案, 整個API僅使用Java語言的詞匯表進行設計, 您甚至可以以源文本的形式指定插入的位元組碼; Javassist即時編譯它, 另一方面,位元組碼級API允許用戶直接編輯類檔案作為其他編輯器,
二. javassist使用
- 引入依賴
<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.25.0-GA</version>
</dependency>
- 代碼示例:
package com.ygz.demo.javassist;
import javassist.*;
/**
* @author jingchuan
*/
public class Test {
public static void main(String[] args) throws Exception {
ClassPool pool = ClassPool.getDefault();
//創建一個空的類
CtClass cc = pool.makeClass("com.ygz.demo.javassist.JavassistObj1");
// 新增一個欄位名為name String 型別
CtField name = new CtField(pool.getCtClass("java.lang.String"), "name", cc);
// 訪問級別是 private
name.setModifiers(Modifier.PRIVATE);
// 初始值是 "jingchuan"
cc.addField(name, CtField.Initializer.constant("jingchuan"));
// 新增一個欄位名為id int 型別
CtField id = new CtField(pool.getCtClass("int"), "id", cc);
// 訪問級別是 private
id.setModifiers(Modifier.PRIVATE);
// 初始值是 1
cc.addField(id, CtField.Initializer.constant(1));
// 給name欄位添加 get、set 方法
cc.addMethod(CtNewMethod.setter("setName", name));
cc.addMethod(CtNewMethod.getter("getName", name));
// 給id欄位添加 get、set 方法
cc.addMethod(CtNewMethod.setter("setId", id));
cc.addMethod(CtNewMethod.getter("getId", id));
// 添加無參的建構式
CtConstructor cons = new CtConstructor(new CtClass[]{}, cc);
cons.setBody("{name = \"jingchuanya\"; id = 1;}");
cc.addConstructor(cons);
// 添加有參的建構式
cons = new CtConstructor(new CtClass[]{pool.get("int"), pool.get("java.lang.String")}, cc);
// $0=this / $1,$2,$3... 代表方法引數
cons.setBody("{$0.id = $1;$0.name = $2;}");
cc.addConstructor(cons);
// 6. 創建一個名為printToString方法,無引數,無回傳值,輸出id和name值
CtMethod ctMethod = new CtMethod(CtClass.voidType, "printToString", new CtClass[]{}, cc);
ctMethod.setModifiers(Modifier.PUBLIC);
ctMethod.setBody("{System.out.println(name);System.out.println(id);}");
cc.addMethod(ctMethod);
//todo 這里改成自己的路徑
cc.writeFile("/Users/****/jingchuanSpace/xxx-demo/demo/src/main/java/");
Class<?> aClass = cc.toClass();
Object o = aClass.newInstance();
o.getClass().getMethod("printToString", new Class[]{}).invoke(o);
}
}
執行這段代碼會在自己填寫的絕對路徑下生成一個JavassistObj1.class檔案
然后還會在列印臺輸出:
jingchuanya
1
這就是動態生成class檔案并通過反射呼叫其中的方法的例子,javassist不止可以創建類,還可以對已有的類進行修改,這些這里就不細說了,大家可以自行去網上查閱資料,比如這個:https://www.cnblogs.com/scy251147/p/11100961.html
三. dubbo使用javassist動態編譯
dubbo中的動態編譯有一個體現的地方就是對于標注了@Adaptive的方法,會自動生成一個代理類,在我上一篇分析SPI的時候提到了這里,感興趣可以先看看SPI的知識:dubbo的SPI
那么這里就直接進入代碼分析:org.apache.dubbo.common.extension.ExtensionLoader#createAdaptiveExtensionClass

首先是獲取org.apache.dubbo.common.compiler.Compiler,看過上一篇SPI的應該很熟悉這個代碼
可以看到org.apache.dubbo.common.compiler.Compiler注解了@SPI("javassist"),所以其默認實作應該是org.apache.dubbo.common.compiler.support.JavassistCompiler
那么再回到org.apache.dubbo.common.extension.ExtensionLoader#createAdaptiveExtensionClass方法中,獲取到Compiler的實作之后,呼叫了org.apache.dubbo.common.compiler.Compiler#compile方法那么到底呼叫的是這個介面的哪個實作呢,org.apache.dubbo.common.compiler.support.AdaptiveCompiler實作上面標記有注解:@Adaptive,所以很明顯是
因為他是通過:org.apache.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(org.apache.dubbo.common.compiler.Compiler.class).getAdaptiveExtension();獲取出來的,那么來看實作方法:org.apache.dubbo.common.compiler.support.AdaptiveCompiler#compile,在這個方法中有個org.apache.dubbo.common.compiler.support.AdaptiveCompiler#DEFAULT_COMPILER變數,如果沒有給他setDefaultCompiler,那么他就是空的,所以會直接走到上圖紅框圈起來的方法loader.getDefaultExtension(),那么這個loader其實就是Compiler的擴展類,所以他的默認實作就是@SPI("javassist")的org.apache.dubbo.common.compiler.support.JavassistCompiler,那么當我們想點進去compiler.compile(code, classLoader)這個方法的時候,會發現這個介面實作的是一個抽象類org.apache.dubbo.common.compiler.support.AbstractCompiler

這個抽象類其實是被org.apache.dubbo.common.compiler.support.JavassistCompiler繼承了的
所以自然就進入了org.apache.dubbo.common.compiler.support.AbstractCompiler#compile這個方法,那么這個方法的try catch之前實際是獲取了一個className,表示從入參code里面獲取的類包路徑+類名稱,然后Class.forName是加載編譯后的Class,但是這個code才是剛生成的代碼,還沒有被編譯過,所以自然就拋了例外,那么就進入了doCompile(className, code)里面,這個時候就真正進入了org.apache.dubbo.common.compiler.support.JavassistCompiler#doCompile
那么來看這個doCompile方法,看過文章第二小節的同學或者對javssist有了解的同學,應該能看懂這個方法的意思就是根據入參code生成了一個對應的Class并回傳
那到這里基本就看完了dubbo使用javassist進行動態編譯的程序
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/259709.html
標籤:java
上一篇:云原生系列1 pod基礎
下一篇:JavaWeb-01--JavaWeb的概念--Servlet 技術-- ServletConfig類--ServletContext 類- HTTP協議--get,post區別
