ASM 使用的一个范例。

 

现在开始研究Junit,试图通过Junit来改善自己的编程,看了一两天,觉得自己懂了,开始上马。

 

同事写了一个byte 数组转 json的组件,我的目标是通过撰写 Juit的测试用例来测试这些组件是否OK。

 

一上来就遇到了一个不小的麻烦,我的测试主要是来比较两个“对象”是否相等,给定了一个byte数组,组件返回给我了一个

 

json对象,我也自己模拟了一个json对象,我需要用JSONObject.equals来判断两个对象是否相等,很遗憾,同时用的是

 

org.json,但不是json-lib中的 net.sf.json中的JSONObject,json-lib中的JSONObject是有equals方法的,可以递归地

 

比较到最底层。我用的是jar包,我修改不了org.json.JSONObject的源代码,我后来想用 toString来比较,但是org.json.

 

JSONObject反编译出来的源码是:

 

 

    public JSONObject()
    {
        map = new HashMap();
    }

 

 

 

 

  private Map map;

 

 

 它里面的数据结构是hashmap, 所以就可能和我要求的不一样,

 

 例如 加入我要求 {"aa":"11","bb":"22"},

 

  它 toString 给我的结果可能是 {"bb":"22","aa":"11"}

 

所以,这个toString方法是不能间接证明它们 not equlas的。现在如果想想,如果能让

 

 

   public JSONObject()
    {
        map = new LinkedHashMap();
    }
那该多好啊,转化组件是有配置的,配置是有顺序的,所以我自己知道顺序,那么这样toString就可以做到比较两者之间
是否相等。
																				
	

   该 如何让这个jar里面的类的hashmap-->linkedhashmap呢,我在网上找到了ASM。

 

 

  废话不多说了,就是看看如何做吧。我们那这个问题当模型的话,那么我们只需要修改类的构造方法就是了。

 

但是,我需要提一点的是,网上很多教程没有说,类的值域好像是无法修改的,只能先删除,后添加一个新的,比如如果类

 

里面有个 private String name-->你希望name是int型,那么先删掉name,后价格 string 型的name.至于有没有修改

 

的方法,我也不确定。我以网上这个例子来当范本吧。

 

http://victorzhzh.iteye.com/blog/882699

 

 

import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;

public class Person {
	public int name ;
	public Map address ;

}

 

 

我们的目标是

 

 

import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;

public class Person {
	public String name ;
	public Map address ;
	
	public Person()
	{
		address=new LinkedHashMap();
	}
}

 

 

 

怎么做呢?

 

import java.util.LinkedHashMap;
import java.util.Map;

import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.FieldVisitor;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.util.CheckClassAdapter;



public class Transform extends CheckClassAdapter {

	public Transform(ClassVisitor cv) {
		super(cv);
	}

	@Override
	public void visitEnd() {  //这个方法只会执行一次,在这里我们可以构造我们需要的东西。
		
		cv.visitField(Opcodes.ACC_PUBLIC, "name", Type.getDescriptor(int.class),
				null, null);
        //新增加一个方法
        MethodVisitor mv= cv.visitMethod(Opcodes.ACC_PUBLIC,
                "<init>",
                "()V",
                null,
                null);
        
        
        
        
        mv.visitVarInsn(Opcodes.ALOAD, 0);
        mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Objec", "<init>", "()V");
        
        mv.visitVarInsn(Opcodes.ALOAD, 0);
        mv.visitInsn(Opcodes.ICONST_2);
        mv.visitFieldInsn(Opcodes.PUTFIELD, "Person", "name", "I");
        
        mv.visitVarInsn(Opcodes.ALOAD, 0);
        mv.visitTypeInsn(Opcodes.NEW, "java/util/LinkedHashMap");  
        mv.visitInsn(Opcodes.DUP); 
        mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/util/LinkedHashMap", "<init>", "()V");
        mv.visitFieldInsn(Opcodes.PUTFIELD, "Person", "address", " java/util/Map"); 
//        mv.visitVarInsn(Opcodes.ASTORE, 0);

        mv.visitInsn(Opcodes.RETURN);

       // mv.visitMaxs(0, 0);
        mv.visitEnd();
        
	}
	
    public FieldVisitor visitField(int access, String name, String desc,  
            String signature, Object value) {  
    	
       // cv.visitField(Opcodes.ACC_PUBLIC, "age", Type.getDescriptor(int.class),  
       //         null, null);  
    	if("name".equals(name)) //值域变量名称为name的时候
    		return null;          //为null表示去掉
        return super.visitField(access, name, desc, signature, value); //不为null,保留 
    }  
    
    @Override
    public MethodVisitor visitMethod(
        int access,
        String name,
        String desc,
        String signature,
        String[] exceptions)
    {
        if ( "<init>".equals(name)) { //init为初始化方法
            return null; //为空表示删除
           }
 
         //其他的方法保留
           return super.visitMethod(access, name, desc, signature, exceptions);
        

    }
	

}
 

 

 

 

 

public class GeneratorClassLoader extends ClassLoader {

	@SuppressWarnings("rawtypes")
	public Class defineClassFromClassFile(String className, byte[] classFile)
			throws ClassFormatError {
		return defineClass(className, classFile, 0, classFile.length);
	}
}

 

 

 

 

import java.io.FileOutputStream;

import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.util.CheckClassAdapter;

public class TransformTest {
	
	public void addAge() throws Exception {
//   输入需要改造的类,Person
		ClassReader classReader = new ClassReader(
				"Person");
//       写改造好的类,怎么改,谁改,classAdapter
		ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS);
		CheckClassAdapter classAdapter = new Transform(classWriter);

		classReader.accept(classAdapter, ClassReader.SKIP_DEBUG);
              //生成的,改造好的类的二级制文件。
		byte[] classFile = classWriter.toByteArray();

         //直接把这些二进制文件,通过类加载器,加载到虚拟机中去,为了检验,我们需要存到文件中去
//		GeneratorClassLoader classLoader = new GeneratorClassLoader();
//		Class clazz = classLoader.defineClassFromClassFile(
//				"Person", classFile);
//		Object obj = clazz.newInstance();

		//System.out.println(clazz.getDeclaredField("name").get(obj));//----(1)
		//System.out.println(clazz.getDeclaredField("age").get(obj));//----(2)
		//存到e盘log目录下的Example.class文件中,我们可以用反编译工具来查看,我们修改的
              是否正确。
	       FileOutputStream fos = new FileOutputStream("e:\\logs\\Example.class");
	       fos.write(classFile);
	       fos.close();
	}
	
	  public static void main(String[] args) throws Exception
	  {
		  new TransformTest().addAge();
	  }
}

 

 

我们重点考察的是Transform的visitEnd方法,这个方法完成了我们队类的改写。

 

  public FieldVisitor visitField(int access, String name, String desc,  

            String signature, Object value)

 

和     public MethodVisitor visitMethod(

        int access,

        String name,

        String desc,

        String signature,

        String[] exceptions)

 

这两个方法,分别表示构建值域和函数,如果为空,就表示没有这个值域或者函数了。这个应该会遍历那个最原始的类。

 

比方说,我们最初的Person类里面有2个值域,5个方法,那么就会执行两次visitField,5次visitMethod方法。

 

所以我们遇到想去掉的方法,或者值域就Ok了。

 

visitEnd这个方法只会执行一次,所以,我们在这里加上我们想要的东西。

 

		cv.visitField(Opcodes.ACC_PUBLIC, "name", Type.getDescriptor(int.class),
				null, null); //name改变为int 型
        //新增加一个方法,构造方法
        MethodVisitor mv= cv.visitMethod(Opcodes.ACC_PUBLIC,
                "<init>",
                "()V",
                null,
                null);
        
        
       
        //为什么加这段,以后再说
        mv.visitVarInsn(Opcodes.ALOAD, 0);
        mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Objec", "<init>", "()V");
        
       //让int型的name=2
        mv.visitVarInsn(Opcodes.ALOAD, 0);
        mv.visitInsn(Opcodes.ICONST_2);
        mv.visitFieldInsn(Opcodes.PUTFIELD, "Person", "name", "I");
        
      //让 address=new LinkedHashMap();
        mv.visitVarInsn(Opcodes.ALOAD, 0);
        mv.visitTypeInsn(Opcodes.NEW, "java/util/LinkedHashMap");  
        mv.visitInsn(Opcodes.DUP); 
        mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/util/LinkedHashMap", "<init>", "()V");
        mv.visitFieldInsn(Opcodes.PUTFIELD, "Person", "address", " java/util/Map"); 
//        mv.visitVarInsn(Opcodes.ASTORE, 0);

        mv.visitInsn(Opcodes.RETURN);

       // mv.visitMaxs(0, 0);
        mv.visitEnd();
 

 

这样,我们就完成了改造的任务,为什么这样改,怎样改。

 

首先我们需要分析一下这个过程。我们需要工具,一个是asm的eclipse插件,这个插件负责把类的二进制结构显示出来,

 

名字叫 

bytecode-outline

 

在这里 2.4.0版本的 http://forge.ow2.org/project/download.php?group_id=23&file_id=17193

 

还有一个windows xp上的反编译工具。

 

我们先用eclipse写出,我们需要的类,然后用 bytecode-outline观察,然后再用asm api构造出这个想要的类,然后保存到文件中,然后用 jd-gui反编译,看看是不是我们想要的类。

 

例如,我们需要这样,这是我们的目标代码

 

public class Person {
	public int name ;
	public String  address ;
//	private Map map;
//	
	public Person()
	{
		name=7;
		address="福田";
	}
}
 

bytecode-outline看到的是:

 

// class version 50.0 (50)
// access flags 0x21
public class Person {

  // compiled from: Person.java

  // access flags 0x1
  public I name

  // access flags 0x1
  public Ljava/lang/String; address

  // access flags 0x1
  public <init>()V
   L0
    LINENUMBER 10 L0
    ALOAD 0
    INVOKESPECIAL java/lang/Object.<init>()V
   L1
    LINENUMBER 12 L1
    ALOAD 0
    BIPUSH 7
    PUTFIELD Person.name : I
   L2
    LINENUMBER 13 L2
    ALOAD 0
    LDC "\u798f\u7530"
    PUTFIELD Person.address : Ljava/lang/String;
   L3
    LINENUMBER 14 L3
    RETURN
   L4
    LOCALVARIABLE this LPerson; L0 L4 0
    MAXSTACK = 2
    MAXLOCALS = 1
}

 

那么我们的构建代码将是这样:

 

	public void visitEnd() {
		
		cv.visitField(Opcodes.ACC_PUBLIC, "name", Type.getDescriptor(int.class),
				null, null);
        //新增加一个方法
        MethodVisitor mv= cv.visitMethod(Opcodes.ACC_PUBLIC,
                "<init>",
                "()V",
                null,
                null);
        
        
        
        
        mv.visitVarInsn(Opcodes.ALOAD, 0);
        mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Objec", "<init>", "()V");
        
        mv.visitVarInsn(Opcodes.ALOAD, 0);
        mv.visitVarInsn(Opcodes.BIPUSH, 7);
        //mv.visitInsn(Opcodes.ICONST_2);
        mv.visitFieldInsn(Opcodes.PUTFIELD, "Person", "name", "I");
        
       mv.visitVarInsn(Opcodes.ALOAD, 0);
       mv.visitLdcInsn("\u798f\u7530");
       mv.visitFieldInsn(Opcodes.PUTFIELD, "Person", "address", "Ljava/lang/String"); 
        
        
//        mv.visitVarInsn(Opcodes.ALOAD, 0);
//        mv.visitTypeInsn(Opcodes.NEW, "java/util/LinkedHashMap");  
//        mv.visitInsn(Opcodes.DUP); 
//        mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/util/LinkedHashMap", "<init>", "()V");
//        mv.visitFieldInsn(Opcodes.PUTFIELD, "Person", "address", " java/util/Map"); 
//        mv.visitVarInsn(Opcodes.ASTORE, 0);

        mv.visitInsn(Opcodes.RETURN);

       // mv.visitMaxs(0, 0);
        mv.visitEnd();
        
	}

 

蓝色对蓝色,红色对红色。呵呵,然后我们用 jd-gui反编译一下example.class会发现 

public class Person
{
  public String address;
  public int name = 7;

  public Person()
  {
    super();
    this.address = "福田";
  }
}

 

 

这样,我们就找到了一种比较好的途径来构建我们需要的类,因为java类的字节码还是比较难认的,这种方式就能照葫芦画瓢,

 

以比较小的代价完成我们的目标,如果你要想深入,那么就需要精通虚拟机的类的结构了。

 

掌握了这个方法,我们就可以在没有源代码的情况下,通过反编译工具来构建我们需要的类了,快试试吧。其实我也不怎么懂这个ASM框架的,就是调试调试,基本了解了其流程,利用“工具”吧,逆向工程来学习。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值