JVM Class字节码之三-使用BCEL改变类属性
使用BCEL動(dòng)態(tài)改變Class內(nèi)容
之前對(duì)Class文件中的常量池,Method的字節(jié)碼指令進(jìn)行了說明。
JVM Class詳解之一
JVM Class詳解之二 Method字節(jié)碼指令
現(xiàn)在我們開始實(shí)際動(dòng)手,使用BCEL改變字節(jié)碼指令,對(duì)Class文件進(jìn)行功能擴(kuò)充。
先介紹下BCEL全程Apache Byte Code Engineering Library,BCEL 每項(xiàng)內(nèi)容操作在JVM匯編語言的級(jí)別
HelloWorld搞起
這個(gè)case我們需要給Programmer類做功能擴(kuò)展,Programmer 職責(zé)進(jìn)行了變化,除了要Coding以外,在每次Coding之前需要先做Plan,所以需要在do Coding信息輸出之前輸出 "doBcelPlan..." 信息。
Demo
期望效果
@Overridepublic void doCoding() {doPlan();System.out.println("do Coding...");}private void doPlan() {System.out.println("do Plan...");}需要做什么
針對(duì)我們的期望結(jié)果我們需要做以下三點(diǎn)
工程先引入BCEL的依賴Pom中追加即可
<dependency><groupId>asm</groupId><artifactId>asm</artifactId><version>3.1</version></dependency><dependency><groupId>asm</groupId><artifactId>asm-tree</artifactId><version>3.1</version></dependency>1. 先使用BCEL 加載需要編輯的Class
JavaClass clazz = Repository.lookupClass(Programmer.class);ClassGen classGen = new ClassGen(clazz);ConstantPoolGen cPoolGen = classGen.getConstantPool(); // 常量池信息2. 在常量池中增加一個(gè)MethodRef doBcelPlan
int methodIndex = cPoolGen.addMethodref("byteCode.decorator.Programmer", "doBcelPlan", "()V"); // 在常量池中增加一個(gè)方法的聲明返回methodIndex為聲明在常量池中的位置索引 第一個(gè)參數(shù)的去路徑類名
第二個(gè)參數(shù)是方法名稱
第三個(gè)方法返回類型 ()V 是void類型
方法返回類型描述參考
3. 在常量池中增加一個(gè)String類型的Filed
因?yàn)橛蠸ystem.out.println("doBcelPlan")語句?
doBcelPlan中的System.out 變量和println方法再doCoding中已經(jīng)使用所有已經(jīng)在常量池中了
注意這里需要記錄追加方法和Filed的index后面需要使用。
4. 然后創(chuàng)建doBcelPlan方法的實(shí)體的字節(jié)碼指令
調(diào)用System.out變量和println方法 具體的字節(jié)碼指令參數(shù) 上一節(jié)內(nèi)容有說明 參考上一節(jié)文檔?JVM Class詳解之二 Method字節(jié)碼指令
InstructionList instructionDoPlan = new InstructionList(); // 字節(jié)碼指令信息 instructionDoPlan.append(new GETSTATIC(17)); // 獲取System.out常量 instructionDoPlan.append(new LDC(stringIndex)); // 獲取String Field信息 instructionDoPlan.append(new INVOKEVIRTUAL(25)); // 調(diào)用Println方法 instructionDoPlan.append(new RETURN()); // return 結(jié)果
其中17,25都是常量池的引用參見下圖,將原先的Programmer類編譯后使用javap -versobse XXX.class 可以查看常量池信息。
stringIndex 是引用第三步追加常量池String Field soBcelPlan
5. 生成doBcelPlan方法
MethodGen doPlanMethodGen = new MethodGen(1, Type.VOID, Type.NO_ARGS, null, "doBcelPlan", classGen.getClassName(), instructionDoPlan, cPoolGen); classGen.addMethod(doPlanMethodGen.getMethod()); 方法的聲明并追加到classGen中。
這樣doBcelPlan方法就追加成功了。接下來我們需要找到doCoding方法,在方法中追加doBcelPlan的調(diào)用。
6. 找到并修正doCoding方法
Method[] methods = classGen.getMethods();for (Method method : methods) {String methodName = method.getName();if ("doCoding".equals(methodName)) {MethodGen methodGen = new MethodGen(method, clazz.getClassName(), cPoolGen);InstructionList instructionList = methodGen.getInstructionList();InstructionHandle[] handles = instructionList.getInstructionHandles();InstructionHandle from = handles[0];InstructionHandle aload = instructionList.append(from, new ALOAD(0));instructionList.append(aload, new INVOKESPECIAL(methodIndex));classGen.replaceMethod(method, methodGen.getMethod());}}InstructionList 是當(dāng)前方法中的字節(jié)碼指令,我們append了兩個(gè)指令A(yù)LOAD和INVOKESPECIAL。實(shí)現(xiàn)doBcelPlan的調(diào)用。
7. 將編輯后的Class輸出
JavaClass target = classGen.getJavaClass();target.dump("D:\\AliDrive\\bytecode\\bcel\\Programmer.class"); 將修改后的字節(jié)碼輸出來看下,使用JD打開OK
可以看到經(jīng)過編輯后的Class文件輸出結(jié)果同我們預(yù)期的是一樣的
Done!
from:?https://yq.aliyun.com/articles/7243?spm=5176.100239.blogcont7241.37.db8GKF
總結(jié)
以上是生活随笔為你收集整理的JVM Class字节码之三-使用BCEL改变类属性的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: JVM Class详解之一
- 下一篇: 语言堆栈入门——堆和栈的区别