现在开始研究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插件,这个插件负责把类的二进制结构显示出来,
名字叫
在这里 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框架的,就是调试调试,基本了解了其流程,利用“工具”吧,逆向工程来学习。