概要
javassist本身是一個(gè)類庫(kù)溉知,旨在幫助開(kāi)發(fā)者簡(jiǎn)化對(duì)java字節(jié)碼的操作庐椒。開(kāi)發(fā)者可以藉由javassist在源碼級(jí)別(source level)和字節(jié)碼級(jí)別(bytecode level)對(duì)java字節(jié)碼進(jìn)行操作颅悉。作為一般開(kāi)發(fā)者衔统,我理解我們只需要重點(diǎn)關(guān)注source level級(jí)別的就夠了提针。然后再借助JDPA機(jī)制藕甩,就可以實(shí)現(xiàn)無(wú)侵入地對(duì)jar進(jìn)行字節(jié)碼增強(qiáng)了芝囤。
核心類
- ClassPool
可以理解為是一個(gè)入口容器,所有需要修改的類(對(duì)應(yīng)CtClass)均需由這個(gè)類獲取辛萍。
典型例子如下ClassPool pool = ClassPool.getDefault(); CtClass cc = pool.get("test.Rectangle");
- CtClass: compile-time class
由ClassPool返回悯姊,任何要對(duì)類的字節(jié)碼操作,均通過(guò)此類進(jìn)行贩毕。通過(guò)調(diào)用writeFile()和toBytecode()方法獲取修改后的字節(jié)碼文件悯许。
常見(jiàn)基本流程
//獲取CtClass
ClassPool cp = ClassPool.getDefault();
CtClass cc = cp.get("<example class>");
//字節(jié)碼增強(qiáng)
CtMethod method = cc.getDeclaredMethod("<exampleMethod>");
method.insertBefore("System.out.println(\"insert before : \" + $1);");
method.insertAfter("{$_ = $_ + \"[insert after]\";}");
// 返回字節(jié)碼
// byte[] clazzByte = cc.toBytecode();
//返回修改后的字節(jié)碼
Class clazz = cc.toClass();
// 實(shí)例對(duì)象
Object newEnhancedObject = clazz.newInstance();
常見(jiàn)操作
1. 定義一個(gè)新類
ClassPool pool = ClassPool.getDefault();
CtClass cc = pool.makeClass("Point");
定義新方法可以通過(guò)CtNewMethod類完成,方法或者接口的添加方法有:CtClass#addMethod辉阶、CtClass#makeInterface
2.類凍結(jié)
如果調(diào)用了CtClass的writeFile(), toClass()或者toBytecode()先壕,那么CtClass就會(huì)被凍結(jié)(預(yù)期類已經(jīng)加載到j(luò)vm了,用戶應(yīng)該不會(huì)有其它修改)谆甜,不允許編輯垃僚。可以通過(guò)defrost()方法取消凍結(jié)规辱。
3.類查找路徑設(shè)置
默認(rèn)情況下谆棺,ClassPool的類查找目錄和底層依托的JVM的類查找目錄相同。但是很多情況下罕袋,用戶可能會(huì)有多個(gè)自定義的類目錄改淑。因此,可以通過(guò)insertClassPath命令浴讯,添加比如來(lái)自本地文件朵夏、網(wǎng)絡(luò)或者字節(jié)碼數(shù)組到ClassPool的類查找目錄中。
注意事項(xiàng)
1. 避免內(nèi)存損耗
1.1 新增方法內(nèi)存損耗
在從ClassPool中獲取CtClass后榆纽,比如增加了一個(gè)新方法仰猖,那么需要找到原先類的所有實(shí)例,這樣會(huì)引起極大的內(nèi)存損耗奈籽,因此饥侵,需要注意及時(shí)清理內(nèi)存。
CtClass cc = ... ;
cc.writeFile();
cc.detach();
1.2 ClassPool內(nèi)存損耗
不要一直使用同一個(gè)ClassPool唠摹。要在需要的時(shí)候爆捞,重新實(shí)例化ClassPool(丟棄舊的)
ClassPool cp = new ClassPool(true);
// if needed, append an extra search path by appendClassPath()
2.局部變量訪問(wèn)
增強(qiáng)區(qū)塊可以訪問(wèn)類字段或者方法,也可以訪問(wèn)增強(qiáng)方法的入?yún)⒐蠢5菬o(wú)法訪問(wèn)方法內(nèi)的局部變量(新增局部變量倒是可以的-CtMethod#addLocalVariable)煮甥。有一個(gè)特殊盗温,如果使用的是insertAt方法,如果在插入的那行有某個(gè)局部變量并且對(duì)應(yīng)方法使用-g選項(xiàng)編譯成肘,那么就可以訪問(wèn)到這個(gè)局部變量卖局。
總結(jié): javassist提供的支持及局限
支持:
(1)對(duì)方法進(jìn)行增強(qiáng);
(2)新增字段双霍、方法砚偶;
(3)修改原方法體;
借助
ExprEditor
對(duì)方法體進(jìn)行修改洒闸,例如:
CtMethod cm = ... ;
cm.instrument(
new ExprEditor() {
public void edit(MethodCall m)
throws CannotCompileException
{
if (m.getClassName().equals("Point")
&& m.getMethodName().equals("move"))
m.replace("{ $1 = 0; $_ = $proceed($$); }");
}
});
(4)移除字段染坯、方法。
局限:
(1)不支持內(nèi)部類丘逸;
(2)不支持枚舉单鹿、泛型(源碼層面);
(3)針對(duì)方法重載深纲,會(huì)產(chǎn)生混淆(錯(cuò)誤調(diào)用)仲锄;
(4)不支持continue和break標(biāo)簽;
(5)不支持?jǐn)?shù)組初始化方式湃鹊;
(6)javassist的編譯器不支持自動(dòng)拆裝箱(即類似儒喊,"Integer i = 1"這種語(yǔ)句是非法的)
javassist調(diào)試功能
設(shè)置 CtClass.debugDump,指定一個(gè)目錄币呵,則所有編譯過(guò)的類文件會(huì)在那個(gè)目錄下怀愧。如果需要取消debug,將屬性置null即可富雅。
例如:CtClass.debugDump = "./dump";
附錄
常見(jiàn)插入符號(hào)
$0, $1, $2, ... this and actual parameters
$args An array of parameters. The type of $args is Object[].
$$ All actual parameters.
For example, m($$) is equivalent to m($1,$2,...)
$cflow(...) cflow variable
$r The result type. It is used in a cast expression.
$w The wrapper type. It is used in a cast expression.
$_ The resulting value
$sig An array of java.lang.Class objects representing the formal parameter types.
$type A java.lang.Class object representing the formal result type.
$class A java.lang.Class object representing the class currently edited.