Java 编程的动态性,第 7 部分: 用 BCEL 设计字节码--转载
在本系列的最后三篇文章中,我展示了如何用 Javassist 框架操作類。這次我將用一種很不同的方法操縱字節(jié)碼——使用 Apache Byte Code Engineering Library (BCEL)。與 Javassist 所支持的源代碼接口不同,BCEL 在實(shí)際的 JVM 指令層次上進(jìn)行操作。在希望對程序執(zhí)行的每一步進(jìn)行控制時,底層方法使 BCEL 很有用,但是當(dāng)兩者都可以勝任時,它也使 BCEL 的使用比 Javassist 要復(fù)雜得多。
我將首先討論 BCEL 基本體系結(jié)構(gòu),然后本文的大部分內(nèi)容將討論用 BCEL 重新構(gòu)建我的第一個 Javassist 類操作的例子。最后簡要介紹 BCEL 包中提供的一些工具和開發(fā)人員用 BCEL 構(gòu)建的一些應(yīng)用程序。
BCEL 類訪問
BCEL 使您能夠同樣具備 Javassist 提供的分析、編輯和創(chuàng)建 Java 二進(jìn)制類的所有基本能力。BCEL 的一個明顯區(qū)別是每項(xiàng)內(nèi)容都設(shè)計(jì)為在 JVM 匯編語言的級別、而不是 Javassist 所提供的源代碼接口上工作。除了表面上的差別,還有一些更深層的區(qū)別,包括在 BCEL 中組件的兩個不同層次結(jié)構(gòu)的使用——一個用于檢查現(xiàn)有的代碼,另一個用于創(chuàng)建新代碼。我假定讀者已經(jīng)通過本系列前面的文章熟悉了 Javassist(請參閱側(cè)欄?不要錯過本系列的其余部分)。 因此我將主要介紹在開始使用 BCEL 時,可能會讓您感到迷惑的那些不同之處。
與 Javassist 一樣, BCEL 在類分析方面的功能基本上與 Java 平臺通過 Relfection API 直接提供的功能是重復(fù)的。這種重復(fù)對于類操作工具箱來說是必要的,因?yàn)橐话悴幌M谒僮鞯念惐恍薷?之前就裝載它們。
BCEL 在?org.apache.bcel?包中提供了一些基本常量定義,但是除了這些定義,所有分析相關(guān)的代碼都在?org.apache.bcel.classfile?包中。這個包中的起點(diǎn)是?JavaClass?類。這個類在用 BCEL 訪問類信息時起的作用與使用常規(guī) Java 反射時,?java.lang.Class?的作用一樣。?JavaClass?定義了得到這個類的字段和方法信息,以及關(guān)于父類和接口的結(jié)構(gòu)信息的方法。 與?java.lang.Class 不同,JavaClass?還提供了對類的內(nèi)部信息的訪問,包括常量池和屬性,以及作為字節(jié)流的完整二進(jìn)制類表示。
JavaClass?實(shí)例通常是通過解析實(shí)際的二進(jìn)制類創(chuàng)建的。BCEL 提供了org.apache.bcel.Repository?類用于處理解析。在默認(rèn)情況下,BCEL 解析并緩沖在 JVM 類路徑中找到的類表示,從?org.apache.bcel.util.Repository?實(shí)例中得到實(shí)際的二進(jìn)制類表示(注意包名的不同)。?org.apache.bcel.util.Repository?實(shí)際上是二進(jìn)制類表示的源代碼的接口。在默認(rèn)源代碼中使用類路徑的地方,可以用查詢類文件的其他路徑或者其他訪問類信息的方法替換。
改變類
除了對類組件的反射形式的訪問,?org.apache.bcel.classfile.JavaClass?還提供了改變類的方法。可以用這些方法將任何組件設(shè)置為新值。不過一般不直接使用它們,因?yàn)榘械钠渌惒灰匀魏魏侠淼姆绞街С謽?gòu)建新版本的組件。相反,在?org.apache.bcel.generic?包中有完全單獨(dú)的一組類,它提供了?org.apache.bcel.classfile?類所表示的同一組件的可編輯版本。
就?像 org.apache.bcel.classfile.JavaClass?是使用 BCEL 分析現(xiàn)有類的起點(diǎn)一樣,?org.apache.bcel.generic.ClassGen?是創(chuàng)建新類的起點(diǎn)。它還用于修改現(xiàn)有的類——為了處理這種情況,有一個以?JavaClass?實(shí)例為參數(shù)的構(gòu)造函數(shù),并用它初始化?ClassGen?類信息。修改了類以后,可以通過調(diào)用一個返回?JavaClass?的方法從?ClassGen?實(shí)例得到可使用的類表示,它又可以轉(zhuǎn)換為一個二進(jìn)制類表示。
聽起來有些亂?我想是的。事實(shí)上,在兩個包之間來回轉(zhuǎn)是使用 BCEL 的一個最主要的缺點(diǎn)。重復(fù)的類結(jié)構(gòu)總有些礙手礙腳,所以如果頻繁使用 BCEL,那么可能需要編寫一個包裝器類,它可以隱藏其中一些不同之處。在本文中,我將主要使用?org.apache.bcel.generic?包類,并避免使用包裝器。不過在您自己進(jìn)行開發(fā)時要記住這一點(diǎn)。
除了?ClassGen?,?org.apache.bcel.generic?包還定義了管理不同類組件的結(jié)構(gòu)的類。這些結(jié)構(gòu)類包括用于處理常量池的?ConstantPoolGen、用于字段和方法的?FieldGen?和?MethodGen?和處理一系列 JVM 指令的?InstructionList?。最后,?org.apache.bcel.generic?包還定義了表示每一種類型的 JVM 指令的類。可以直接創(chuàng)建這些類的實(shí)例,或者在某些情況下使用?org.apache.bcel.generic.InstructionFactoryhelper 類。使用?InstructionFactory?的好處是它處理了許多指令構(gòu)建的簿記細(xì)節(jié)(包括根據(jù)指令的需要在常量池中添加項(xiàng))。在下面一節(jié)您將會看到如何使所有這些類協(xié)同工作。
回頁首
用 BCEL 進(jìn)行類操作
作為使用 BCEl 的一個例子,我將使用?第 4 部分中的一個 Javassist 例子——測量執(zhí)行一個方法的時間。我甚至采用了與使用 Javassist 時的相同方式:用一個改過的名字創(chuàng)建要計(jì)時的原方法的一個副本,然后,通過調(diào)用改名后的方法,利用包裝了時間計(jì)算的代碼來替換原方法的主體。
選擇一個試驗(yàn)品
清單 1 給出了一個用于展示目的示例方法:?StringBuilder?類的?buildString?方法。正如我在?第 4 部分所說的,這個方法采用了所有 Java 性能專家告誡您?不要?使用的方式來構(gòu)建一個?String?—— 它重復(fù)地在字符串的未尾附加單個字符以創(chuàng)建更長的字符串。因?yàn)樽址遣豢勺兊?#xff0c;所以這種方式意味著每次循環(huán)時會構(gòu)建一個新的字符串,從老的字符串拷貝數(shù)據(jù)并在最后增加一個字符。總的效果就是用這個方法創(chuàng)建更長的字符串時,它會產(chǎn)生越來越大的開銷。
清單 1. 要計(jì)時的方法
public class StringBuilder {private String buildString(int length) {String result = "";for (int i = 0; i < length; i++) {result += (char)(i%26 + 'a');}return result;}public static void main(String[] argv) {StringBuilder inst = new StringBuilder();for (int i = 0; i < argv.length; i++) {String result = inst.buildString(Integer.parseInt(argv[i]));System.out.println("Constructed string of length " +result.length());}} }清單 2 顯示了等同于用 BCEL 進(jìn)行類操作改變的源代碼。這里包裝器方法只是保存當(dāng)前時間,然后調(diào)用改名后的原方法,并在返回調(diào)用原方法的結(jié)果之前打印時間報(bào)告。
清單 2. 在原方法中加入計(jì)時
public class StringBuilder {private String buildString$impl(int length) {String result = "";for (int i = 0; i < length; i++) {result += (char)(i%26 + 'a');}return result;}private String buildString(int length) {long start = System.currentTimeMillis();String result = buildString$impl(length);System.out.println("Call to buildString$impl took " +(System.currentTimeMillis()-start) + " ms.");return result;}public static void main(String[] argv) {StringBuilder inst = new StringBuilder();for (int i = 0; i < argv.length; i++) {String result = inst.buildString(Integer.parseInt(argv[i]));System.out.println("Constructed string of length " +result.length());}} }編寫轉(zhuǎn)換代碼
用我在?BCEL 類訪問一節(jié)中描述的 BCEL API 實(shí)現(xiàn)添加方法計(jì)時的代碼。在 JVM 指令級別上的操作使代碼比?第 4 部分?中 Javassist 的例子要長得多,所以這里我準(zhǔn)備在提供完整的實(shí)現(xiàn)之前,一段一段地介紹。在最后的代碼中,所有片段構(gòu)成一個方法,它有兩個參數(shù):?cgen?——它是org.apache.bcel.generic.ClassGen?類的一個實(shí)例,用被修改的類的現(xiàn)有信息初始化,和方法——要計(jì)時方法的org.apache.bcel.classfile.Method?實(shí)例。
清單 3 是轉(zhuǎn)換方法的第一段代碼。可以從注釋中看到,第一部分只是初始化要使用的基本 BCEL 組件,它包括用要計(jì)時方法的信息初始化一個新的?org.apache.bcel.generic.MethodGen?實(shí)例。我為這個?MethodGen?設(shè)置一個空的指令清單,在后面我將用實(shí)際的計(jì)時代碼填充它。在第 2 部分,我用原來的方法創(chuàng)建第二個?org.apache.bcel.generic.MethodGen?實(shí)例,然后從類中刪除原來的方法。在第二個?MethodGen?實(shí)例中,我只是讓名字加上“$impl”后綴,然后調(diào)用?getMethod()?以將可修改的方法信息轉(zhuǎn)換為固定形式的?org.apache.bcel.classfile.Method實(shí)例。然后調(diào)用?addMethod()?以便在類中添加改名后的方法。
清單 3. 添加攔截方法
// set up the construction tools InstructionFactory ifact = new InstructionFactory(cgen); InstructionList ilist = new InstructionList(); ConstantPoolGen pgen = cgen.getConstantPool(); String cname = cgen.getClassName(); MethodGen wrapgen = new MethodGen(method, cname, pgen); wrapgen.setInstructionList(ilist);// rename a copy of the original method MethodGen methgen = new MethodGen(method, cname, pgen); cgen.removeMethod(method); String iname = methgen.getName() + "$impl"; methgen.setName(iname); cgen.addMethod(methgen.getMethod());清單 4 給出了轉(zhuǎn)換方法的下一段代碼。這里的第一部分計(jì)算方法調(diào)用參數(shù)在堆棧上占用的空間。之所以需要這段代碼,是因?yàn)闉榱嗽谡{(diào)用包裝方法之前在堆棧幀上存儲開始時間,我需要知道局部變量可以使用什么偏移值(注意,我可以用 BCEL 的局部變量處理得到同樣的效果,但是在本文中我選擇使用顯式的方式)。這段代碼的第二部分生成對?java.lang.System.currentTimeMillis()?的調(diào)用,以得到開始時間,并將它保存到堆棧幀中計(jì)算出的局部變量偏移處。
您可能會奇怪為什么在開始參數(shù)大小計(jì)算時要檢查方法是否是靜態(tài)的,如果是靜態(tài)的,將堆棧幀槽初始化為零(不是靜態(tài)正好相反)。這種方式與 Java 如何處理方法調(diào)用有關(guān)。對于非靜態(tài)的方法,每次調(diào)用的第一個(隱藏的)參數(shù)是目標(biāo)對象的?this?引用,在計(jì)算堆棧幀中完整參數(shù)集大小時我要考慮到這點(diǎn)。
清單 4. 設(shè)置包裝的調(diào)用
// compute the size of the calling parameters Type[] types = methgen.getArgumentTypes(); int slot = methgen.isStatic() ? 0 : 1; for (int i = 0; i < types.length; i++) {slot += types[i].getSize(); }// save time prior to invocation ilist.append(ifact.createInvoke("java.lang.System","currentTimeMillis", Type.LONG, Type.NO_ARGS, Constants.INVOKESTATIC)); ilist.append(InstructionFactory.createStore(Type.LONG, slot));清單 5 顯示了生成對包裝方法的調(diào)用并保存結(jié)果(如果有的話)的代碼。這段代碼的第一部分再次檢查方法是否是靜態(tài)的。如果方法不是靜態(tài)的,那么就生成將?this?對象引用裝載到堆棧中的代碼,同時設(shè)置方法調(diào)用類型為?virtual?(而不是?static?)。然后?for?循環(huán)生成將所有調(diào)用參數(shù)值拷貝到堆棧中的代碼,?createInvoke()?方法生成對包裝的方法的實(shí)際調(diào)用,最后?if?語句將結(jié)果值保存到位于堆棧幀中的另一個局部變量中(如果結(jié)果類型不是?void?)。
清單 5. 調(diào)用包裝的方法
// call the wrapped method int offset = 0; short invoke = Constants.INVOKESTATIC; if (!methgen.isStatic()) {ilist.append(InstructionFactory.createLoad(Type.OBJECT, 0));offset = 1;invoke = Constants.INVOKEVIRTUAL; } for (int i = 0; i < types.length; i++) {Type type = types[i];ilist.append(InstructionFactory.createLoad(type, offset));offset += type.getSize(); } Type result = methgen.getReturnType(); ilist.append(ifact.createInvoke(cname, iname, result, types, invoke));// store result for return later if (result != Type.VOID) {ilist.append(InstructionFactory.createStore(result, slot+2)); }現(xiàn)在開始包裝。清單 6 生成實(shí)際計(jì)算開始時間后經(jīng)過的毫秒數(shù),并作為編排好格式的消息打印出來的代碼。這一部分看上去很復(fù)雜,但是大多數(shù)操作實(shí)際上只是寫出輸出消息的各個部分。它確實(shí)展示了幾種我在前面的代碼中沒有使用的操作類型,包括字段訪問(到j(luò)ava.lang.System.out?)和幾種不同的指令類型。如果將 JVM 想象為基于堆棧的處理器,則其中大多數(shù)是容易理解的,因此我在這里就不再詳細(xì)說明了。
清單 6. 計(jì)算并打印所使用的時間
// print time required for method call ilist.append(ifact.createFieldAccess("java.lang.System", "out", new ObjectType("java.io.PrintStream"), Constants.GETSTATIC)); ilist.append(InstructionConstants.DUP); ilist.append(InstructionConstants.DUP); String text = "Call to method " + methgen.getName() + " took "; ilist.append(new PUSH(pgen, text)); ilist.append(ifact.createInvoke("java.io.PrintStream", "print",Type.VOID, new Type[] { Type.STRING }, Constants.INVOKEVIRTUAL)); ilist.append(ifact.createInvoke("java.lang.System", "currentTimeMillis", Type.LONG, Type.NO_ARGS, Constants.INVOKESTATIC)); ilist.append(InstructionFactory.createLoad(Type.LONG, slot)); ilist.append(InstructionConstants.LSUB); ilist.append(ifact.createInvoke("java.io.PrintStream", "print", Type.VOID, new Type[] { Type.LONG }, Constants.INVOKEVIRTUAL)); ilist.append(new PUSH(pgen, " ms.")); ilist.append(ifact.createInvoke("java.io.PrintStream", "println", Type.VOID, new Type[] { Type.STRING }, Constants.INVOKEVIRTUAL));生成了計(jì)時消息代碼后,留給清單 7 的就是保存包裝的方法的調(diào)用結(jié)果值(如果有的話),然后結(jié)束構(gòu)建的包裝器方法。最后這部分涉及幾個步驟。調(diào)用?stripAttributes(true)?只是告訴 BCEL 不對構(gòu)建的方法生成調(diào)試信息,而?setMaxStack()?和?setMaxLocals()?調(diào)用計(jì)算并設(shè)置方法的堆棧使用信息。完成了這一步后,就可以實(shí)際生成方法的最終版本,并將它加入到類中。
清單 7. 完成包裝器
// return result from wrapped method call if (result != Type.VOID) {ilist.append(InstructionFactory.createLoad(result, slot+2)); } ilist.append(InstructionFactory.createReturn(result));// finalize the constructed method wrapgen.stripAttributes(true); wrapgen.setMaxStack(); wrapgen.setMaxLocals(); cgen.addMethod(wrapgen.getMethod()); ilist.dispose();完整的代碼
清單 8 顯示了完整的代碼(稍微改變了一下格式以適合顯示寬度),包括以類文件的名字為參數(shù)的?main()?方法和要轉(zhuǎn)換的方法:
清單 8. 完整的轉(zhuǎn)換代碼
public class BCELTiming {private static void addWrapper(ClassGen cgen, Method method) {// set up the construction toolsInstructionFactory ifact = new InstructionFactory(cgen);InstructionList ilist = new InstructionList();ConstantPoolGen pgen = cgen.getConstantPool();String cname = cgen.getClassName();MethodGen wrapgen = new MethodGen(method, cname, pgen);wrapgen.setInstructionList(ilist);// rename a copy of the original methodMethodGen methgen = new MethodGen(method, cname, pgen);cgen.removeMethod(method);String iname = methgen.getName() + "$impl";methgen.setName(iname);cgen.addMethod(methgen.getMethod());Type result = methgen.getReturnType();// compute the size of the calling parametersType[] types = methgen.getArgumentTypes();int slot = methgen.isStatic() ? 0 : 1;for (int i = 0; i < types.length; i++) {slot += types[i].getSize();}// save time prior to invocationilist.append(ifact.createInvoke("java.lang.System","currentTimeMillis", Type.LONG, Type.NO_ARGS, Constants.INVOKESTATIC));ilist.append(InstructionFactory.createStore(Type.LONG, slot));// call the wrapped methodint offset = 0;short invoke = Constants.INVOKESTATIC;if (!methgen.isStatic()) {ilist.append(InstructionFactory.createLoad(Type.OBJECT, 0));offset = 1;invoke = Constants.INVOKEVIRTUAL;}for (int i = 0; i < types.length; i++) {Type type = types[i];ilist.append(InstructionFactory.createLoad(type, offset));offset += type.getSize();}ilist.append(ifact.createInvoke(cname, iname, result, types, invoke));// store result for return laterif (result != Type.VOID) {ilist.append(InstructionFactory.createStore(result, slot+2));}// print time required for method callilist.append(ifact.createFieldAccess("java.lang.System","out", new ObjectType("java.io.PrintStream"),Constants.GETSTATIC));ilist.append(InstructionConstants.DUP);ilist.append(InstructionConstants.DUP);String text = "Call to method " + methgen.getName() +" took ";ilist.append(new PUSH(pgen, text));ilist.append(ifact.createInvoke("java.io.PrintStream","print", Type.VOID, new Type[] { Type.STRING },Constants.INVOKEVIRTUAL));ilist.append(ifact.createInvoke("java.lang.System", "currentTimeMillis", Type.LONG, Type.NO_ARGS, Constants.INVOKESTATIC));ilist.append(InstructionFactory.createLoad(Type.LONG, slot));ilist.append(InstructionConstants.LSUB);ilist.append(ifact.createInvoke("java.io.PrintStream","print", Type.VOID, new Type[] { Type.LONG },Constants.INVOKEVIRTUAL));ilist.append(new PUSH(pgen, " ms."));ilist.append(ifact.createInvoke("java.io.PrintStream","println", Type.VOID, new Type[] { Type.STRING },Constants.INVOKEVIRTUAL));// return result from wrapped method callif (result != Type.VOID) {ilist.append(InstructionFactory.createLoad(result, slot+2));}ilist.append(InstructionFactory.createReturn(result));// finalize the constructed methodwrapgen.stripAttributes(true);wrapgen.setMaxStack();wrapgen.setMaxLocals();cgen.addMethod(wrapgen.getMethod());ilist.dispose();}public static void main(String[] argv) {if (argv.length == 2 && argv[0].endsWith(".class")) {try {JavaClass jclas = new ClassParser(argv[0]).parse();ClassGen cgen = new ClassGen(jclas);Method[] methods = jclas.getMethods();int index;for (index = 0; index < methods.length; index++) {if (methods[index].getName().equals(argv[1])) {break;}}if (index < methods.length) {addWrapper(cgen, methods[index]);FileOutputStream fos =new FileOutputStream(argv[0]);cgen.getJavaClass().dump(fos);fos.close();} else {System.err.println("Method " + argv[1] + " not found in " + argv[0]);}} catch (IOException ex) {ex.printStackTrace(System.err);}} else {System.out.println("Usage: BCELTiming class-file method-name");}} }試一試
清單 9 顯示了以未修改形式第一次運(yùn)行?StringBuilder?程序的結(jié)果,然后運(yùn)行?BCELTiming?程序以加入計(jì)時信息,最后運(yùn)行修改后的StringBuilder?程序。可以看到?StringBuilder?在修改后是如何開始報(bào)告執(zhí)行時間的,以及時間為何比構(gòu)建的字符串長度增加更快,這是由于字符串構(gòu)建代碼的效率不高所致。
清單 9. 運(yùn)行這個程序
[dennis]$ java StringBuilder 1000 2000 4000 8000 16000 Constructed string of length 1000 Constructed string of length 2000 Constructed string of length 4000 Constructed string of length 8000 Constructed string of length 16000 [dennis]$ java -cp bcel.jar:. BCELTiming StringBuilder.class buildString [dennis]$ java StringBuilder 1000 2000 4000 8000 16000 Call to method buildString$impl took 20 ms. Constructed string of length 1000 Call to method buildString$impl took 79 ms. Constructed string of length 2000 Call to method buildString$impl took 250 ms. Constructed string of length 4000 Call to method buildString$impl took 879 ms. Constructed string of length 8000 Call to method buildString$impl took 3875 ms. Constructed string of length 16000回頁首
包裝 BCEL
BCEL 有比我在本文中所介紹的基本類操作支持更多的功能。它還包括完整的驗(yàn)證器實(shí)現(xiàn)以保證二進(jìn)制類對于 JVM 規(guī)范是有效的(參見org.apache.bcel.verifier.VerifierFactory?),一個生成很好地分幀并鏈接的 JVM 級二進(jìn)制類視圖的反匯編程序,甚至一個 BCEL 程序生成器,它輸出源代碼以讓 BCEL 程序編譯所提供的類。(?org.apache.bcel.util.BCELifier?類沒有包括在 Javadocs 中,所以其用法要看源代碼。這個功能很吸引人,但是輸出對大多數(shù)開發(fā)人員來說可能人過于隱晦了)。
我自己使用 BCEL 時,發(fā)現(xiàn) HTML 反匯編程序特別有用。要想試用它,只要執(zhí)行 BCEL JAR 中的?org.apache.bcel.util.Class2HTML?類,用要反匯編的類文件的路徑作為命令行參數(shù)。它會在當(dāng)前目錄中生成 HTML 文件。例如,下面我將反匯編在計(jì)時例子中使用的?StringBuilder類:
[dennis]$ java -cp bcel.jar org.apache.bcel.util.Class2HTML StringBuilder.class Processing StringBuilder.class...Done.圖 1 是反匯編程序生成的分幀輸出的屏幕快照。在這個快照中,右上角的大幀顯示了添加到?StringBuilder?類中的計(jì)時包裝器方法的分解。在下載文件中有完整的 HTML 輸出——如果要實(shí)際觀看它,只需在瀏覽器窗口中打開?StringBuilder.html?文件。
圖 1. 反匯編 StringBuilder
當(dāng)前,BCEL 可能是 Java 類操作使用最多的框架。在 Web 網(wǎng)站上列出了一些使用 BCEL 的其他項(xiàng)目,包括 Xalan XSLT 編譯器、Java 編程語言的 AspectJ 擴(kuò)展和幾個 JDO 實(shí)現(xiàn)。許多其他未列出的項(xiàng)目也使用 BCEL,包括我自己的 JiBX XML 數(shù)據(jù)綁定項(xiàng)目。不過,BCEL 列出的幾個項(xiàng)目已經(jīng)轉(zhuǎn)而使用其他庫,所以不要將這個列表作為 BCEL 大眾化程度的絕對依據(jù)。
BCEL 最大的好處是它的商業(yè)友好的 Apache 許可證及其豐富的 JVM 指令級支持。這些功能結(jié)合其穩(wěn)定性和長壽性,使它成為類操作應(yīng)用程序的非常流行的選擇。不過,BCEL 看來沒有設(shè)計(jì)為具有很好的速度或者容易使用。在大多數(shù)情況下,Javassist 提供了更友好的 API,并有相近的速度(甚至更快),至少在我的簡單測試中是這樣。如果您的項(xiàng)目可以使用 Mozilla Public License (MPL) 或者 GNU Lesser General Public License (LGPL),那么 Javassist 可能是更好的選擇(它在這兩種許可證下都可以用)。
回頁首
下一篇
我已經(jīng)介紹了 Javassist 和 BCEL,本系列的下一篇文章將深入比我們目前已經(jīng)介紹的用途更大的類操作應(yīng)用程序。在?第 2 部分,我展示了方法調(diào)用反射比直接調(diào)用慢得多。在第 8 部分中,我將顯示如何使用 Javassist 和 BCEL,以便用運(yùn)行時動態(tài)生成的代碼替換反射調(diào)用,從而極大地提高性能。下個月請回來看另一篇?Java 編程的動態(tài)性以了解詳情。
原文:http://www.ibm.com/developerworks/cn/java/j-dyn0414/index.html
轉(zhuǎn)載于:https://www.cnblogs.com/davidwang456/p/4035858.html
總結(jié)
以上是生活随笔為你收集整理的Java 编程的动态性,第 7 部分: 用 BCEL 设计字节码--转载的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Java 编程的动态性,第 6 部分:
- 下一篇: java美元兑换,(Java实现) 美元