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)和使用方式:
動(dòng)態(tài)代碼生成:
Javassist允許您在運(yùn)行時(shí)通過(guò)編寫 Java 代碼的方式來(lái)生成新的類和方法。這種方式使動(dòng)態(tài)代碼生成變得非常直觀和易于理解。類修改和增強(qiáng): 您可以使用
Javassist修改已經(jīng)存在的類的字節(jié)碼,例如添加、修改或刪除字段、方法等。字節(jié)碼操作:
Javassist提供了一套 API,用于操作類的字節(jié)碼指令,如創(chuàng)建方法、添加指令、修改參數(shù)等。這使得您能夠以更高級(jí)別的抽象方式來(lái)進(jìn)行字節(jié)碼操作,而無(wú)需深入了解底層字節(jié)碼細(xì)節(jié)。AOP 支持:
Javassist可以用于實(shí)現(xiàn) AOP(面向切面編程)的功能,通過(guò)在方法前后插入代碼來(lái)實(shí)現(xiàn)橫切關(guān)注點(diǎn)的處理。簡(jiǎn)化反射:
Javassist可以幫助您避免使用繁瑣的 Java 反射 API,通過(guò)生成字節(jié)碼來(lái)直接調(diào)用方法和訪問(wèn)字段。跨版本支持:
Javassist支持處理不同版本的 Java 字節(jié)碼,因此您可以在不同的 Java 版本間進(jìn)行字節(jié)碼操作。
Javassist操作步驟
使用 Javassist 的基本步驟如下:
導(dǎo)入庫(kù): 首先,您需要將
Javassist庫(kù)添加到項(xiàng)目的依賴中。創(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。創(chuàng)建 CtClass: 使用 ClassPool 創(chuàng)建一個(gè) CtClass 對(duì)象,該對(duì)象表示要操作的類。它是一個(gè)class文件在代碼中的抽象表現(xiàn)形式,可以通過(guò)一個(gè)類的全限定名來(lái)獲取一個(gè)CtClass對(duì)象,用來(lái)表示這個(gè)類文件。
進(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)注的方法:
getDefault: 返回默認(rèn)的ClassPool是單例模式的,一般通過(guò)該方法創(chuàng)建我們的ClassPool;appendClassPath,insertClassPath: 將一個(gè)ClassPath加到類搜索路徑的末尾位置 或 插入到起始位置。通常通過(guò)該方法寫入額外的類搜索路徑,以解決多個(gè)類加載器環(huán)境中找不到類的尷尬;toClass: 將修改后的CtClass加載至當(dāng)前線程的上下文類加載器中,CtClass的toClass方法是通過(guò)調(diào)用本方法實(shí)現(xiàn)。需要注意的是一旦調(diào)用該方法,則無(wú)法繼續(xù)修改已經(jīng)被加載的class;get,getCtClass: 根據(jù)類路徑名獲取該類的CtClass對(duì)象,用于后續(xù)的編輯。
CtClass需要關(guān)注的方法:
freeze: 凍結(jié)一個(gè)類,使其不可修改;isFrozen: 判斷一個(gè)類是否已被凍結(jié);prune: 刪除類不必要的屬性,以減少內(nèi)存占用。調(diào)用該方法后,許多方法無(wú)法將無(wú)法正常使用,慎用;defrost: 解凍一個(gè)類,使其可以被修改。如果事先知道一個(gè)類會(huì)被defrost, 則禁止調(diào)用prune方法;detach: 將該class從ClassPool中刪除;writeFile: 根據(jù)CtClass生成.class文件;toClass: 通過(guò)類加載器加載該CtClass。
上面我們創(chuàng)建一個(gè)新的方法使用了CtMethod類。CtMthod代表類中的某個(gè)方法,可以通過(guò)CtClass提供的API獲取或者CtNewMethod新建,通過(guò)CtMethod對(duì)象可以實(shí)現(xiàn)對(duì)方法的修改。
CtMethod中的一些重要方法:
insertBefore: 在方法的起始位置插入代碼;insterAfter: 在方法的所有 return 語(yǔ)句前插入代碼以確保語(yǔ)句能夠被執(zhí)行,除非遇到exception;insertAt: 在指定的位置插入代碼;setBody: 將方法的內(nèi)容設(shè)置為要寫入的代碼,當(dāng)方法被abstract修飾時(shí),該修飾符被移除;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)操作。