Javassist字節(jié)碼增強(qiáng)探秘

Javassist介紹

通過(guò)【Java開發(fā)者必讀:掌握ASM技術(shù)的利器與實(shí)戰(zhàn)應(yīng)用】我們知道ASM是在指令層次上操作字節(jié)碼的,通過(guò)字節(jié)碼增強(qiáng)技術(shù)-ASM,我們的直觀感受是在指令層次上操作字節(jié)碼的框架實(shí)現(xiàn)起來(lái)比較晦澀。我們?cè)俸?jiǎn)單介紹另外一類框架:強(qiáng)調(diào)源代碼層次操作字節(jié)碼的框架Javassist。

Javassist(Java Programming Assistant)是一個(gè)用于在運(yùn)行時(shí)操作字節(jié)碼的 Java 庫(kù),它允許開發(fā)人員動(dòng)態(tài)生成、修改和分析 Java 類的字節(jié)碼。Javassist提供了一種更高級(jí)別的 API,以 Java 代碼的方式來(lái)操作字節(jié)碼,而不需要直接操作復(fù)雜的字節(jié)碼指令。這使得動(dòng)態(tài)代碼生成和修改變得更加容易和可維護(hù)。以下是 Javassist 的一些重要特點(diǎn)和使用方式:

  1. 動(dòng)態(tài)代碼生成: Javassist 允許您在運(yùn)行時(shí)通過(guò)編寫 Java 代碼的方式來(lái)生成新的類和方法。這種方式使動(dòng)態(tài)代碼生成變得非常直觀和易于理解。

  2. 類修改和增強(qiáng): 您可以使用 Javassist 修改已經(jīng)存在的類的字節(jié)碼,例如添加、修改或刪除字段、方法等。

  3. 字節(jié)碼操作: Javassist 提供了一套 API,用于操作類的字節(jié)碼指令,如創(chuàng)建方法、添加指令、修改參數(shù)等。這使得您能夠以更高級(jí)別的抽象方式來(lái)進(jìn)行字節(jié)碼操作,而無(wú)需深入了解底層字節(jié)碼細(xì)節(jié)。

  4. AOP 支持: Javassist 可以用于實(shí)現(xiàn) AOP(面向切面編程)的功能,通過(guò)在方法前后插入代碼來(lái)實(shí)現(xiàn)橫切關(guān)注點(diǎn)的處理。

  5. 簡(jiǎn)化反射: Javassist 可以幫助您避免使用繁瑣的 Java 反射 API,通過(guò)生成字節(jié)碼來(lái)直接調(diào)用方法和訪問(wèn)字段。

  6. 跨版本支持: Javassist 支持處理不同版本的 Java 字節(jié)碼,因此您可以在不同的 Java 版本間進(jìn)行字節(jié)碼操作。

Javassist操作步驟

使用 Javassist 的基本步驟如下:

  1. 導(dǎo)入庫(kù): 首先,您需要將 Javassist 庫(kù)添加到項(xiàng)目的依賴中。

  2. 創(chuàng)建 ClassPool: ClassPool 是 Javassist 的核心類,用于加載和保存類文件。您可以通過(guò) ClassPool 加載要操作的類。從開發(fā)視角來(lái)看,ClassPool是一張保存CtClass信息的HashTable,key為類名,value為類名對(duì)應(yīng)的CtClass對(duì)象。當(dāng)我們需要對(duì)某個(gè)類進(jìn)行修改時(shí),就是通過(guò)pool.getCtClass(“className”)方法從pool中獲取到相應(yīng)的CtClass。

  3. 創(chuàng)建 CtClass: 使用 ClassPool 創(chuàng)建一個(gè) CtClass 對(duì)象,該對(duì)象表示要操作的類。它是一個(gè)class文件在代碼中的抽象表現(xiàn)形式,可以通過(guò)一個(gè)類的全限定名來(lái)獲取一個(gè)CtClass對(duì)象,用來(lái)表示這個(gè)類文件。

  4. 進(jìn)行修改: 在 CtClass 對(duì)象上進(jìn)行修改,例如添加新方法、修改字段、添加注解等。

  • CtMethod、CtField:這兩個(gè)比較好理解,對(duì)應(yīng)的是類中的方法和屬性。

  • 保存修改: 將修改后的 CtClass 對(duì)象保存到類文件或加載到 ClassLoader 中。

  • 使用生成的類: 修改后的類現(xiàn)在可以被實(shí)例化和使用,從而實(shí)現(xiàn)所需的功能。

  • 下面我們寫一個(gè)小Demo來(lái)展示Javassist簡(jiǎn)單、快速的特點(diǎn)。我們依然是對(duì)MathUtils中的add()方法做增強(qiáng),在方法調(diào)用前后分別輸出”start”和”end”,實(shí)現(xiàn)代碼如下。我們需要做的就是從pool中獲取到相應(yīng)的CtClass對(duì)象和其中的方法,然后執(zhí)行method.insertBefore和insertAfter方法,參數(shù)為要插入的Java代碼,再以字符串的形式傳入即可,實(shí)現(xiàn)起來(lái)也極為簡(jiǎn)單。

    首先引用jar包:

    <dependency>??
    ????<groupId>org.javassist</groupId>??
    ????<artifactId>javassist</artifactId>??
    ????<version>3.28.0-GA</version>??
    </dependency>

    我們還用上篇文章的測(cè)試類

    public?class?MathUtils?{??
    ????public?int?add(int?a,?int?b)?{??
    ????????return?a?+?b;??
    ????}??
    }
    public?class?JavassistTest?{??
    ????public?static?void?main(String[]?args)?throws?NotFoundException,?CannotCompileException,?IllegalAccessException,?InstantiationException,?IOException?{??
    ????????ClassPool?cp?=?ClassPool.getDefault();??
    ????????CtClass?cc?=?cp.get("com.demo.bytecode.MathUtils");??
    ????????CtMethod?m?=?cc.getDeclaredMethod("add");??
    ????????m.insertBefore("{?System.out.println(\"start\");?}");??
    ????????m.insertAfter("{?System.out.println(\"end\");?}");??
    ????????Class?c?=?cc.toClass();??
    ????????cc.writeFile("/Users/xx/work/spring-demo/");??
    ????????MathUtils?h?=?(MathUtils)?c.newInstance();??
    ????????int?result?=?h.add(1,?2);??
    ????????System.out.println(result);??
    ????}??
    }

    運(yùn)行后輸出結(jié)果為:

    我們打開生成的class文件,可以看到已經(jīng)生成了相關(guān)代碼:

    ClassPool需要關(guān)注的方法:

    1. getDefault : 返回默認(rèn)的ClassPool 是單例模式的,一般通過(guò)該方法創(chuàng)建我們的ClassPool;

    2. appendClassPath, insertClassPath : 將一個(gè)ClassPath加到類搜索路徑的末尾位置 或 插入到起始位置。通常通過(guò)該方法寫入額外的類搜索路徑,以解決多個(gè)類加載器環(huán)境中找不到類的尷尬;

    3. toClass : 將修改后的CtClass加載至當(dāng)前線程的上下文類加載器中,CtClasstoClass方法是通過(guò)調(diào)用本方法實(shí)現(xiàn)。需要注意的是一旦調(diào)用該方法,則無(wú)法繼續(xù)修改已經(jīng)被加載的class

    4. get , getCtClass : 根據(jù)類路徑名獲取該類的CtClass對(duì)象,用于后續(xù)的編輯。

    CtClass需要關(guān)注的方法:

    1. freeze : 凍結(jié)一個(gè)類,使其不可修改;

    2. isFrozen : 判斷一個(gè)類是否已被凍結(jié);

    3. prune : 刪除類不必要的屬性,以減少內(nèi)存占用。調(diào)用該方法后,許多方法無(wú)法將無(wú)法正常使用,慎用;

    4. defrost : 解凍一個(gè)類,使其可以被修改。如果事先知道一個(gè)類會(huì)被defrost, 則禁止調(diào)用 prune 方法;

    5. detach : 將該class從ClassPool中刪除;

    6. writeFile : 根據(jù)CtClass生成 .class 文件;

    7. toClass : 通過(guò)類加載器加載該CtClass。

    上面我們創(chuàng)建一個(gè)新的方法使用了CtMethod類。CtMthod代表類中的某個(gè)方法,可以通過(guò)CtClass提供的API獲取或者CtNewMethod新建,通過(guò)CtMethod對(duì)象可以實(shí)現(xiàn)對(duì)方法的修改。

    CtMethod中的一些重要方法:

    1. insertBefore : 在方法的起始位置插入代碼;

    2. insterAfter : 在方法的所有 return 語(yǔ)句前插入代碼以確保語(yǔ)句能夠被執(zhí)行,除非遇到exception;

    3. insertAt : 在指定的位置插入代碼;

    4. setBody : 將方法的內(nèi)容設(shè)置為要寫入的代碼,當(dāng)方法被 abstract修飾時(shí),該修飾符被移除;

    5. make : 創(chuàng)建一個(gè)新的方法。

    使用`Javassist`寫個(gè)`Bean Copy`的工具

    首先定義一個(gè)對(duì)象轉(zhuǎn)換的接口,生成的轉(zhuǎn)換類實(shí)現(xiàn)這個(gè)接口

    public?interface?Converter?{??
    ????/**??
    ????*?將一個(gè)對(duì)象復(fù)制到另一個(gè)對(duì)象??
    ????*??
    ????*?@param?from?from??
    ????*?@param?to?to??????*/??
    ????void?convert(Object?from,?Object?to);??
    }

    實(shí)現(xiàn)一個(gè)工具類CopyUtil生成轉(zhuǎn)換器

    public?class?CopyUtil?{??

    ????private?static?final?ConcurrentHashMap<ConverterKey,?Converter>?CACHE?=?new?ConcurrentHashMap<>(32);??
    ????private?static?final?AtomicInteger?ID?=?new?AtomicInteger();??
    ????private?static?final?ClassPool?pool?=?ClassPool.getDefault();??
    ????private?static?final?CtClass?converterInterface;??

    ????static?{??
    ????????try?{??
    ????????????converterInterface?=?pool.getCtClass(Converter.class.getName());??
    ????????}?catch?(NotFoundException?e)?{??
    ????????????throw?new?RuntimeException(e);??
    ????????}??
    ????}??

    ????public?static?void?copy(Object?from,?Object?to)?{??
    ????????Class<?>?fromClass?=?from.getClass();??
    ????????Class<?>?toClass?=?to.getClass();??

    ????????Converter?converter?=?getConverter(fromClass,?toClass);??
    ????????converter.convert(from,?to);??
    ????}??

    ????/**??
    ????*?從緩存獲取converter??
    ????*??
    ????*?@param?fromClass?源類??
    ????*?@param?toClass?目標(biāo)類??
    ????*?@return?轉(zhuǎn)換器??????*/??
    ????private?static?Converter?getConverter(Class<?>?fromClass,?Class<?>?toClass)?{??
    ????????ConverterKey?key?=?new?ConverterKey(fromClass,?toClass);??
    ????????return?CACHE.computeIfAbsent(key,?CopyUtil::generateConverter);??
    ????}??


    ????/**??
    ????*?使用javassist生成一個(gè)轉(zhuǎn)換器??
    ????*??
    ????*?@param?key?key??
    ????*?@return?converter??????*/??
    ????private?static?Converter?generateConverter(ConverterKey?key)?{??

    ????????Class<?>?fromClass?=?key.fromClass;??
    ????????Class<?>?toClass?=?key.toClass;??

    ????????CtClass?converterClass?=?pool.makeClass("BeanConverter"?+?ID.getAndIncrement());??

    ????????try?{??
    ????????????converterClass.addInterface(converterInterface);??
    ????????????//?創(chuàng)建一個(gè)新的方法??
    ????????????CtMethod?convertMethod?=?CtNewMethod.make(generateMethod(fromClass,?toClass),?converterClass);??
    ????????????converterClass.addMethod(convertMethod);??

    ????????????Class<?>?type?=?converterClass.toClass(CopyUtil.class.getClassLoader(),?CopyUtil.class.getProtectionDomain());??
    ????????????return?(Converter)?type.newInstance();??
    ????????}?catch?(Exception?e)?{??
    ????????????throw?new?RuntimeException("-?generate?converter?error",?e);??
    ????????}??
    ????}??

    ????/**??
    ????*?生成轉(zhuǎn)換器方法??
    ????*??
    ????*?@param?fromClass?原始類??
    ????*?@param?toClass?目標(biāo)類??
    ????*?@return?方法代碼??????*/??
    ????private?static?String?generateMethod(Class<?>?fromClass,?Class<?>?toClass)?{??
    ????????String?prefix?=?"public?void?convert(Object?from,?Object?to)?{\n";??
    ????????//?對(duì)象轉(zhuǎn)換??
    ????????String?castFromCode?=?fromClass.getName()?+?"?a?=?("?+?fromClass.getName()?+?")?from;\n";??
    ????????String?castToCode?=?toClass.getName()?+?"?b?=?("?+?toClass.getName()?+?")?to;\n";??
    ????????String?postfix?=?"}\n";??
    ????????//?獲取原始類字段??
    ????????Set<String>?fromFields?=?getFields(fromClass);??
    ????????//?獲取目標(biāo)類字段??
    ????????Set<String>?toFields?=?getFields(toClass);??

    ????????fromFields.retainAll(toFields);??

    ????????StringBuilder?code?=?new?StringBuilder();??
    ????????for?(String?field?:?fromFields)?{??
    ????????????field?=?StringUtils.capitalize(field);??
    ????????????code.append("b.set").append(field).append("(a.get").append(field).append("());\n");??
    ????????}??

    ????????return?prefix?+?castFromCode?+?castToCode?+?code?+?postfix;??
    ????}??

    ????/**??
    ????*?獲取一個(gè)類(包含父類)的所有屬性??
    ????*??
    ????*?@param?type?type??
    ????*?@return?屬性list??????*/??
    ????private?static?Set<String>?getFields(Class<?>?type)?{??

    ????????Field[]?fields?=?type.getDeclaredFields();??
    ????????Set<String>?fieldSet?=?Stream.of(fields).map(Field::getName).collect(toSet());??

    ????????Class<?>?parent?=?type.getSuperclass();??
    ????????if?(type.equals(Object.class)?||?parent.equals(Object.class))?{??
    ????????????return?fieldSet;??
    ????????}??

    ????????Set<String>?parentFieldSet?=?getFields(parent);??
    ????????fieldSet.addAll(parentFieldSet);??

    ????????return?fieldSet;??
    ????}??


    ????/**??
    ????*?用于緩存的鍵??????*/??
    ????@EqualsAndHashCode??
    ????@AllArgsConstructor??
    ????private?static?class?ConverterKey?{??
    ????????Class<?>?fromClass;??
    ????????Class<?>?toClass;??
    ????}??
    }

    下面我們測(cè)試一下

    public?static?void?main(String[]?args)?{??
    ????Person?person1?=?new?Person("zhangsan",?25);??
    ????Person?person2?=?new?Person();??
    ????CopyUtil.copy(person1,?person2);??
    ????System.out.println(person2.getName());??
    ????System.out.println(person2.getAge());??
    }??

    結(jié)果正常輸出:

    總結(jié)

    總體而言,Javassist 是通過(guò)在運(yùn)行時(shí)操作字節(jié)碼,使用高級(jí)別的 API 和類抽象(如 CtClass、CtMethod 等),使得動(dòng)態(tài)代碼生成和修改變得更加直觀和容易。這種方式使開發(fā)人員可以在不直接操作底層字節(jié)碼指令的情況下,實(shí)現(xiàn)對(duì) Java 類的動(dòng)態(tài)操作。

    ?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
    【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
    平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

    相關(guān)閱讀更多精彩內(nèi)容

    友情鏈接更多精彩內(nèi)容