现在的位置: 首页 > 综合 > 正文

使用ASM对JAVA class file进行修改的技术 — 添加类方法

2012年03月28日 ⁄ 综合 ⁄ 共 6337字 ⁄ 字号 评论关闭

使用ASM包进行Class File修改真是很方便,不过可惜的是ASM不提供现成的方法修改工具,那我们就利用它提供的强大的字节码操作能力,自己来做一个吧:

基本思路如下:假设操作类为A, 假设要加的方法为MA,我们为了操作上方便,构造类B,将类B的MA方法加到A上就可以了,有了这个思路即可快速实现如下代码:

---------------------------------------------------------------------------------------------------------------

首先构造Visitor用于添加方法MA:

public class BytecodeClassMethodAdder extends ClassAdapter {
 
 // method nodes for appending operation
 private final List<MethodNode> methodNodesToAppend;
 
 /**
  * Construction for bytecode class method adder operation
  *
  * @param cv
  * @param methodNodes
  */
 public BytecodeClassMethodAdder(final ClassVisitor cv, List<MethodNode> methodNodes) {
  super(cv);
  
  // all method nodes needed to append for current class
  this.methodNodesToAppend = methodNodes;
 }
 
 /**
  * visit end of this adapter for current class
  *
  */
 @SuppressWarnings("unchecked")
 public void visitEnd() {
  for (MethodNode mn : this.methodNodesToAppend) {
   List exceptions = mn.exceptions;
   String [] earray = null;
   if (exceptions.size() > 0){
    earray = new String[exceptions.size()];
    for (int i=0; i<exceptions.size(); i++){
     String exception = (String)exceptions.get(i);
     earray[i] = exception;
    }
   }
   mn.accept(cv);    //add this method node to the visitor operation
  }
  
  // overload the visiting operation
  super.visitEnd();
 }
 
}

-----------------------------------------------------------------------------------------------------------

然后构造从另外一个类B中抽取方法的操作类:

public class BytecodeClassFilterUtil implements IBytecodeContainer{
 
 private ClassNode classNode = null;
 
 /**
  * bytecode class filter utility construction
  *
  * @param classFile
  * @param op
  * @throws IOException
  */
 public BytecodeClassFilterUtil(final String classFile) throws IOException {
  FileInputStream fis = new FileInputStream(classFile);
  ClassReader cr = new ClassReader(fis);
  BytecodeClassFilter ca = new BytecodeClassFilter(null);
  cr.accept(ca, ClassReader.EXPAND_FRAMES);
  if (fis != null) {
   fis.close();
  }
 }
 
 /**
  * bytecode class filter utility construction
  *
  * @param classFile
  * @param op
  * @throws IOException
  */
 public BytecodeClassFilterUtil(File classFile) throws IOException {
  FileInputStream fis = new FileInputStream(classFile);
  ClassReader cr = new ClassReader(fis);
  BytecodeClassFilter ca = new BytecodeClassFilter(null);
  cr.accept(ca, ClassReader.EXPAND_FRAMES);
  if (fis != null) {
   fis.close();
  }
 }
 
 /**
  * get a specified class node instance for current bytecode class filter utility
  *
  * @return
  */
 public ClassNode getClassNode() {
  return this.classNode;
 }
 
 /**
  * get a specified field node by a specified name pattern and description pattern
  *
  * @param name
  * @return
  */
 @SuppressWarnings("unchecked")
 public List<FieldNode> getFieldNode(String namePattern, String descPattern) {
  List<FieldNode> returnNodes = new ArrayList<FieldNode>();
  List fields = this.classNode.fields;
  if (fields != null) {
   for (Object ofield : fields) {
    FieldNode field = (FieldNode) ofield;
    boolean blnNameMatch = true;
    boolean blnDescMatch = true;
    if (namePattern != null) {
     blnNameMatch = Pattern.matches(namePattern, field.name);
    }
    if (descPattern != null) {
     blnDescMatch = Pattern.matches(descPattern, field.desc);
    }
    if (blnNameMatch && blnDescMatch) {
     returnNodes.add(field);
    }
   }
  }
  return returnNodes;
 }
 
 /**
  * get a specified method name or a list of them.
  *
  * @param name
  * @param description
  * @return
  */
 @SuppressWarnings("unchecked")
 public List<MethodNode> getMethodNode(String namePattern, String descPattern) {
  List<MethodNode> returnNodes = new ArrayList<MethodNode>();
  List methods = this.classNode.methods;
  if (methods != null) {
   for (Object omethod : methods) {
    MethodNode method = (MethodNode) omethod;
    boolean blnNameMatch = true;
    boolean blnDescMatch = true;
    if (namePattern != null) {
     blnNameMatch = Pattern.matches(namePattern, method.name);
    }
    if (descPattern != null) {
     blnDescMatch = Pattern.matches(descPattern, method.desc);
    }
    if (blnNameMatch && blnDescMatch) {
     returnNodes.add(method);
    }
   }
  }
  return returnNodes;
 }

 /**
  * get all of the field descriptions for a specified class
  *
  * @return
  */
 @SuppressWarnings("unchecked")
 public List<String> getFieldDescription() {
  List<String> descList = new ArrayList<String>();
  List fields = this.classNode.fields;
  if (fields != null) {
   for (Object ofield : fields) {
    FieldNode field = (FieldNode) ofield;
    StringBuilder sb = new StringBuilder();
    sb.append(field.name).append(":").append(field.desc);
    descList.add(sb.toString());
   }
  }
  return descList;
 }
 
 /**
  * get all of the method list for a specified class
  *
  * @return
  */
 @SuppressWarnings("unchecked")
 public List<String> getMethodDescription() {
  List<String> descList = new ArrayList<String>();
  List methods = this.classNode.methods;
  if (methods != null) {
   for (Object omethod : methods) {
    MethodNode method = (MethodNode) omethod;
    StringBuilder sb = new StringBuilder();
    sb.append(method.name).append(":").append(method.desc);
    descList.add(sb.toString());
   }
  }
  return descList;
 }
 
 /**
  * bytecode class filter extend from class adpater class.
  *
  */
 class BytecodeClassFilter extends ClassAdapter {

  // construction call for current class
  public BytecodeClassFilter(final ClassVisitor cv) {
   super(new ClassNode() {
    public void visitEnd() {
     if (cv != null) {
      accept(cv);
     }
    }
   });
  }

  // execute the next operation after this visit ending
  public void visitEnd() {
   classNode = (ClassNode) cv;
  }

 }

}

------------------------------------------------------------------------------------------------

构造调用函数,实现方法“转移”功能:

public void addMethodToClass(String src, String des, String combine, String nameFilter, String descFilter)
   throws IOException {
  BytecodeClassFilterUtil util = new BytecodeClassFilterUtil(src);
  List<MethodNode> methods = util.getMethodNode(nameFilter, descFilter);
  
  // visitor current class
  if (methods.size() == 0) {
   System.out.println("ERROR: No method is chosen out by the filter.");
  } else {
   ClassWriter cw = new ClassWriter(0);
   BytecodeClassMethodAdder adder = new BytecodeClassMethodAdder(cw, methods);
   FileInputStream fis = new FileInputStream(des);
   ClassReader cr = new ClassReader(fis);
   cr.accept(adder, ClassReader.EXPAND_FRAMES); // need to expand frames for current end user
   if (fis != null) {
    fis.close();
   }
   
   // convert the specified method into current class
   byte[] bytearray = cw.toByteArray();
   FileOutputStream fos = new FileOutputStream(combine);
   fos.write(bytearray);
   fos.flush();
   fos.close();
  }
 }

------------------------------------------------------------------------------------

最后挂接操作界面:

addMethodToClass(sourceFile, targetFile, destFile, nameFilter, descFilter);

需要注意的是: sourceFile = B.class;

                     targetFile = A.class;

                     destFile = 合成后的A.class

nameFilter,descFilter 支持正则表达式,并且可以为NULL,都为NULL时表示添加所有Methods;

 

呵呵,大功告成了,大家可以测试一下,你会发现原来代码“迁移”如此容易.

抱歉!评论已关闭.