程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> 關於JAVA >> 用BCEL設計字節碼 - 直接在方法的調用處添加方法

用BCEL設計字節碼 - 直接在方法的調用處添加方法

編輯:關於JAVA

這個最接近之前提出的API轉換問題

這個需要掃描源代碼找到此類的方法調用處,然後在方法調用處的前後添加指令

即將代碼轉換成如下形式:

public class StringBuilder
{
     public 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++) {

             long start = System.currentTimeMillis();
             String result = inst.buildString(Integer.parseInt(argv[i]));
             System.out.println("Call to buildString$impl took " +
                     (System.currentTimeMillis()-start) + " ms.");

             System.out.println("Constructed string of length " +
                 result.length());
         }
     }
}

轉換代碼如下:

先掃描這個類中的各個方法

我准備改變一下生成策略,不自己寫指令了,這個自己寫指令比較麻煩,但要加的代碼比較多時比較容易出錯,當所加的代碼有if,跳轉語句,自己根本就不會如何寫添哪些指令,這種寫類似匯編指令總是很麻煩的。

先要把加的代碼自己在某個類中事先用方法寫好,實例方法也好,靜態方法也好,方法帶有參數或者有返回值,

這些參數和返回值供調用方法中的局部變量來替換和使用。

再要添加代碼的方法的前後調用這些已經寫好的方法的字節碼的指令序列生成InstructionList,然後再添加到使用這個方法的指令序列中

如下

public class ToolUtil {

     public static long printStart() {
         System.out.println("start start start start");
         long start = System.currentTimeMillis();
         return start;
     }

     public static void printEnd(String methodname,long start){

         System.out.println("Call to "+methodname+" took " +
                 (System.currentTimeMillis()-start) + " ms.");
         System.out.println("end end end end end end");
     }
}

原始的方法

public class StringBuilder {

     /**//*
      * 要在調用了此方法的方法的代碼的前後添加代碼
      */
     public String buildString(int length) {
         String result = "";
         for (int i = 0; i < length; i++) {
             result += (char) (i % 26 + 'a');
         }
         System.out.println(result);
         return result;
     }
     /**//*
      * 調用了buildString方法
      */
     private String testInvokeMethod(){

         String temp = null;

         temp = buildString(10);
         return temp;
     }

     /**//*
      * 調用了buildString方法
      */
     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());
         }
     }
}

生成的方法為

public class StringBuilder {

     /**//*
      * 要在調用了此方法的方法的代碼的前後添加代碼
      */
     public String buildString(int length) {
         String result = "";
         for (int i = 0; i < length; i++) {
             result += (char) (i % 26 + 'a');
         }
         System.out.println(result);
         return result;
     }
     /**//*
      * 調用了buildString方法
      */
     private String testInvokeMethod(){

         String temp = null;
         //調用事先寫好的方法
         long startTime = ToolUtil.printStart();

         temp = buildString(10);
         //調用事先寫好的方法
         ToolUtil.printEnd("buildString", startTime);

         System.out.println("我是測試方法,我是測試方法,我是測試方法,我是測試方法");
         return temp;
     }

     /**//*
      * 調用了buildString方法
      */
     public static void main(String[] argv) {
         StringBuilder inst = new StringBuilder();
         for (int i = 0; i < argv.length; i++) {

             //調用事先寫好的方法
             long startTime = ToolUtil.printStart();
             String result = inst.buildString(Integer.parseInt(argv[i]));
             //調用事先寫好的方法
             ToolUtil.printEnd("buildString", startTime);

             System.out.println("Constructed string of length "
                     + result.length());
         }
     }
}

main這個調用的代碼或者使用INVOKEXXXX指令實現,或者把這些調用方法的字節碼指令序列加入到main的指令序列當中來實現,直接加的話可能自己事先寫的方法的局部變量與main方法的局部變量會相同,而且涉及到所加的指令序列使用局部變量的問題,比如加的iload_0,istore_2就會引用現在方法的局部變量,但是現在的局部變量根本就不是這種指令所對應的類型

。所以還是使用方法調用比較方便

import java.io.FileOutputStream;
import org.apache.bcel.Constants;
import org.apache.bcel.classfile.ClassParser;
import org.apache.bcel.classfile.ConstantClass;
import org.apache.bcel.classfile.ConstantMethodref;
import org.apache.bcel.classfile.ConstantNameAndType;
import org.apache.bcel.classfile.ConstantPool;
import org.apache.bcel.classfile.JavaClass;
import org.apache.bcel.classfile.Method;
import org.apache.bcel.generic.ClassGen;
import org.apache.bcel.generic.ConstantPoolGen;
import org.apache.bcel.generic.INVOKEVIRTUAL;
import org.apache.bcel.generic.Instruction;
import org.apache.bcel.generic.InstructionFactory;
import org.apache.bcel.generic.InstructionHandle;
import org.apache.bcel.generic.InstructionList;
import org.apache.bcel.generic.LocalVariableGen;
import org.apache.bcel.generic.MethodGen;
import org.apache.bcel.generic.Type;

public class BCELTiming3 {

     /**//*
      * 掃描StringBuilder的各個方法的指令序列,在其中找Invokexxxx指令,
      * 看此指令在常量池中引用的方法引用是否是要其前後加代碼的方法
      * 方法所在的類要一致,方法的名稱要一致,方法的簽名要一致
      *
      */

     /** *//**
      *
      * @param cgen 要被解析的類class文件
      * @param classname 要被修改的方法所在的類名稱,若有包名,則為java/lang/Object類似的
      * @param methodname 要被修改的方法的名稱
      * @param methodSignature 要被修改的方法的簽名
      */
     private static void modifyWrapper(ClassGen cgen,String classname,String methodname,String methodSignature){

         InstructionFactory ifact = new InstructionFactory(cgen);
         ConstantPoolGen pgen = cgen.getConstantPool();
         ConstantPool pool = pgen.getConstantPool();

         String cname = cgen.getClassName();

         Method[] methods = cgen.getMethods();
         for(int i=0;i<methods.length;i++){
             Method tempMethod = methods[i];

             MethodGen tempMethodGen = new MethodGen(tempMethod,cname,pgen);

             InstructionList tempList = tempMethodGen.getInstructionList();
             tempList.iterator();
             Instruction[] tempInstructions = tempList.getInstructions();
             InstructionHandle[] tempInHandles = tempList.getInstructionHandles();
             for(int j=0;j<tempInHandles.length;j++){
                 InstructionHandle ihandle = tempInHandles[j];
                 Instruction nowInstruction = ihandle.getInstruction();
                 if(nowInstruction.getOpcode()==Constants.INVOKEVIRTUAL){
                     INVOKEVIRTUAL invokeVirtual = (INVOKEVIRTUAL)nowInstruction;
                     ConstantMethodref cmr = (ConstantMethodref)pgen.getConstant(invokeVirtual.getIndex());

                     ConstantClass cc = (ConstantClass)pgen.getConstant(cmr.getClassIndex());
                     String nowClassName = cc.getBytes(pgen.getConstantPool());
                     ConstantNameAndType cnt = (ConstantNameAndType)pgen.getConstant(cmr.getNameAndTypeIndex());
                     String nowMethodName = cnt.getName(pgen.getConstantPool());
                     String nowMethodSignature = cnt.getSignature(pgen.getConstantPool());

                     //判斷此方法的所屬的類,方法的名稱,方法的簽名是否與所要加的一致(I)Ljava/lang/String;
                     if(nowClassName.equals(classname) && nowMethodName.equals(methodname) && nowMethodSignature.equals(methodSignature)){

                         cgen.removeMethod(tempMethodGen.getMethod());

                         //找到此方法在list的位置
                         InstructionList beforeList = new InstructionList();
                         //先加一個局部變量保存starttime的值
                         LocalVariableGen lvg = tempMethodGen.addLocalVariable("starttime", Type.LONG, null, null);
                         beforeList.append(ifact.createInvoke("ToolUtil", "printStart", Type.LONG, Type.NO_ARGS, Constants.INVOKESTATIC));
                         beforeList.append(InstructionFactory.createStore(Type.LONG, lvg.getIndex()));
                         tempList.insert(ihandle.getPrev().getPrev(), beforeList);

                         //加方法後的代碼
                         InstructionList afterList = new InstructionList();
                         afterList.append(ifact.createConstant(methodname));
                         afterList.append(ifact.createLoad(Type.LONG, lvg.getIndex()));
                         afterList.append(ifact.createInvoke("ToolUtil", "printEnd", Type.VOID, new Type[]{Type.STRING,Type.LONG}, Constants.INVOKESTATIC));
                         tempList.insert(ihandle.getNext().getNext(), afterList);

                         //finalize the construted method
                         tempMethodGen.stripAttributes(false);
                         tempMethodGen.setMaxStack();
                         tempMethodGen.setMaxLocals();
                         cgen.addMethod(tempMethodGen.getMethod());

                         System.out.println(tempMethodGen.getInstructionList());
                         System.out.println();
                         System.out.println();

                     }
                 }
             }

//            tempList.setPositions();
//            tempList.findHandle(pos);

         }

     }
     /** *//**
      * @param args
      */
     public static void main(String[] args) {

         args[0]="D:\\java to eclipse\\javaeclipsestudy\\workspace\\BCELTest\\bin\\StringBuilder.class";

         if(args.length==1 && args[0].endsWith(".class")){
             try{
                 JavaClass jclas = new ClassParser(args[0]).parse();

                 ClassGen cgen = new ClassGen(jclas);

                 modifyWrapper(cgen,"StringBuilder","buildString","(I)Ljava/lang/String;");

                 cgen.getJavaClass().dump(args[0]);
             }catch(Exception e){
                 e.printStackTrace();
             }
         }else{
             System.out.println("usage: class-file");
         }
     }
}

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved