现在开始研究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框架的,就是调试调试,基本了解了其流程,利用“工具”吧,逆向工程来学习。