Javassist如何操作Java 字節(jié)碼
一、開(kāi)篇
說(shuō)起 AOP 小伙伴們肯定很熟悉,無(wú)論是 JDK 動(dòng)態(tài)代理或者是 CGLIB 等,其底層都是通過(guò)操作 Java 字節(jié)碼來(lái)實(shí)現(xiàn)代理。常用的一些操作字節(jié)碼的技術(shù)有 ASM、AspectJ、Javassist 等。
ASM 其設(shè)計(jì)和實(shí)現(xiàn)是盡可能小而且快,更專注于性能。它在指令的層面來(lái)操作,所以使用它需要對(duì) JVM 的指令有所了解,門檻較高,CGLIB 就使用了 ASM 技術(shù)。
AspectJ 擴(kuò)展了 Java 語(yǔ)言,定義了一系列 AOP 語(yǔ)法,在 JVM 中運(yùn)行需要使用特定的編譯器生成遵守 Java 字節(jié)碼規(guī)范的 Class 文件,Spring AOP 使用了 AspectJ 。
Javassist 直接使用 Java 編碼的形式操作字節(jié)碼,簡(jiǎn)單易上手,性能高于反射,相比于 ASM 稍低。
二、Javassist 常用類
Javassist 抽象出一個(gè) ClassPool 對(duì)象來(lái)操作 Java 類,可以通過(guò) ClassPool.getDefault() 來(lái)獲取默認(rèn)的 ClassPool 。常用的對(duì)象:
CtClass:代表一個(gè) Class 的實(shí)例,可以通過(guò)類的全限定名來(lái)獲取 CtClass 對(duì)象,其中包含了對(duì) Class 的各種操作。
ClassPool:通過(guò) HashTable 保存了路徑下的 CtClass 信息,key為類的全限定名稱,value 為類名對(duì)應(yīng)的 CtClass 對(duì)象。
CtMethod、CtField:抽象出類的方法和屬性,可以用于定義或修改方法和字段。
三、Javassist 的使用
1、依賴
<dependency> <groupId>org.javassist</groupId> <artifactId>javassist</artifactId> <version>3.27.0-GA</version> </dependency>
2、代碼示例
// 獲取默認(rèn)類池 ClassPool classPool = ClassPool.getDefault(); // 1. 創(chuàng)建空類 CtClass ctClass = classPool.makeClass("com.aysaml.demo.javassist.User"); // 2. 創(chuàng)建 String 類型的 name 字段 CtField field = new CtField(classPool.get("java.lang.String"), "name", ctClass); // 設(shè)置字段訪問(wèn)級(jí)別 private field.setModifiers(Modifier.PRIVATE); // 增加字段 ctClass.addField(field); // 3. 增加 getter & setter 方法 ctClass.addMethod(CtNewMethod.getter("getName", field)); ctClass.addMethod(CtNewMethod.setter("setName", field)); // 4. 增加無(wú)參構(gòu)造方法:其中 $0 表示 this,$1 表示參數(shù) CtConstructor noArgsCons = new CtConstructor(new CtClass[] {}, ctClass); noArgsCons.setBody("{$0.name=\"mark\";}"); ctClass.addConstructor(noArgsCons); // 5. 增加有參構(gòu)造方法 CtConstructor hasArgsCons = new CtConstructor(new CtClass[] {classPool.get("java.lang.String")}, ctClass); hasArgsCons.setBody("{$0.name=$1;}"); ctClass.addConstructor(hasArgsCons); // 6. 創(chuàng)建方法 CtMethod method = new CtMethod(CtClass.voidType, "printName", new CtClass[] {}, ctClass); method.setBody("{System.out.println($0.name);}"); ctClass.addMethod(method); // 7. 生成類文件:可指定路徑,默認(rèn)為當(dāng)前項(xiàng)目根目錄 ctClass.writeFile(); // 8. 創(chuàng)建類實(shí)例 Object person = ctClass.toClass().newInstance();
3、如何實(shí)現(xiàn)類似 AOP 的功能
由上可見(jiàn),Javassist 對(duì)于編程化的操作字節(jié)碼是很簡(jiǎn)單易懂的,我們以在方法的開(kāi)頭結(jié)尾打印信息為例:
public class Cat { /** 記錄喵喵喵的次數(shù) */ private int num; public void miao() { this.num++; } }
我們要在 miao( ) 方法的前增加聲音輸出:
public static void main(String[] args) throws NotFoundException, CannotCompileException { ClassPool classPool = ClassPool.getDefault(); // 獲取 Cat 類的 CtClass 對(duì)象 CtClass catClass = classPool.get("com.aysaml.demo.javassist.Cat"); // 獲取 miao( ) 方法 CtMethod method = catClass.getDeclaredMethod("miao"); method.insertBefore("System.out.println(\"miao~\");"); // 加載修改過(guò)的類,注意必須要保證調(diào)用前這個(gè)類沒(méi)有被加載過(guò) catClass.toClass(); //測(cè)試 Cat cat = new Cat(); cat.miao(); }
注意到,在使用 catClass.toClass() 加載被修改過(guò)的類時(shí),強(qiáng)調(diào)必須保證在調(diào)用前這個(gè)類沒(méi)有被加載過(guò),否則會(huì)報(bào) attempted duplicate class definition for name 異常。
我們知道一個(gè)類是不能被一個(gè)類加載器加載兩次的,所以為了解決這個(gè)問(wèn)題,需要制定一個(gè)沒(méi)有加載過(guò)該類的 Classloader,Javassist 提供了一個(gè) ClassLoader ,如下:
public class Cat { /** 記錄喵喵喵的次數(shù) */ private int num; public void miao() { System.out.println("調(diào)用了 miao 方法"); this.num++; } public static void main(String[] args) throws Exception{ ClassPool classPool = ClassPool.getDefault(); // 獲取 Cat 類的 CtClass 對(duì)象 CtClass catClass = classPool.get("com.aysaml.demo.javassist.Cat"); // 獲取 miao( ) 方法 CtMethod method = catClass.getDeclaredMethod("miao"); method.insertBefore("System.out.println(\"miao~\");"); // 重新設(shè)置一個(gè) Classloader Loader classLoader = new Loader(classPool); Class clazz = classLoader.loadClass("com.aysaml.demo.javassist.Cat"); // 調(diào)用修改過(guò)的類的方法 clazz.getDeclaredMethod("miao").invoke(clazz.newInstance()); } }
執(zhí)行結(jié)果為:
四、結(jié)語(yǔ)
關(guān)于 Javassist 暫時(shí)就說(shuō)這么多了,更多使用方法參考官方 github wiki :
以上就是Javassist如何操作Java 字節(jié)碼的詳細(xì)內(nèi)容,更多關(guān)于Javassist 操作Java 字節(jié)碼的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
解讀springboot配置mybatis的sql執(zhí)行超時(shí)時(shí)間(mysql)
這篇文章主要介紹了解讀springboot配置mybatis的sql執(zhí)行超時(shí)時(shí)間(mysql),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-01-01java項(xiàng)目怎么集成stable diffusion圖文生成算法
在開(kāi)發(fā)Java項(xiàng)目過(guò)程中,我們經(jīng)常需要使用消息傳遞來(lái)實(shí)現(xiàn)不同組件之間的通信,Stable Diffusion是一種基于消息傳遞的實(shí)時(shí)通信解決方案,使用Java調(diào)用外部服務(wù)(如Python腳本或API服務(wù)),這些服務(wù)運(yùn)行Stable Diffusion模型,本文將介紹如何將Stable Diffusion集成到Java項(xiàng)目2024-07-07從?PageHelper?到?MyBatis?Plugin執(zhí)行概要及實(shí)現(xiàn)原理
這篇文章主要為大家介紹了從?PageHelper?到?MyBatis?Plugin執(zhí)行概要及實(shí)現(xiàn)原理,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-09-09java Spring松耦合高效應(yīng)用簡(jiǎn)單實(shí)例分析
在Java項(xiàng)目,龐大的對(duì)象依賴關(guān)系將一直緊密耦合引起對(duì)象難以管理或修改。在這種情況下,可以使用Spring框架作為一個(gè)核心模塊輕松高效地管理所有的對(duì)象依賴。本文章向大家介紹Spring松耦合的實(shí)例,需要的朋友可以參考一下。2016-12-12MybatisPlus為何可以不用@MapperScan詳解
這篇文章主要給大家介紹了關(guān)于MybatisPlus為何可以不用@MapperScan的相關(guān)資料,文中通過(guò)圖文介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用MybatisPlus具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2023-04-04解決Spring或SpringBoot開(kāi)啟事務(wù)以后無(wú)法返回自增主鍵的問(wèn)題
這篇文章主要介紹了解決Spring或SpringBoot開(kāi)啟事務(wù)以后無(wú)法返回自增主鍵的問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-07-07