ASM系列之四:操作类属性

    在上一篇文章中,我们看到了ASM中的Core API中使用的是XXXVisitor操作类中的对应部分。本文将展示如何使用ASM中的Core API对类的属性的操作。

首先,我们定义一个原类Person,如下:

public class Person {
	public String name = "zhangzhuo";
	public String address = "xxxxx" ;
}

 这里,我们将属性定义为public类型,目的是为了我们使用反射去调用这个属性,接下来我们要为这个类添加一个int类型的属性,名字叫age。

    第一个问题,ASM的Core API允许我们在那些方法中来添加属性?

    在ASM的Core API中你要为类添加属性就必须要自己去实现ClassVisitor这个接口,这个接口中的visitInnerClass、visitField、visitMethod和visitEnd方法允许我们进行添加一个类属性操作,其余的方法是不允许的。这里我们依然使用Core API中的ClassAdapter类,我们继承这个类,定义一个去添加属性的类,ClassAdapter实现了ClassVisitor。

    第二个问题,我们要在这些方法中写什么样的代码才能添加一个属性?

    在使用ASM的Core API添加一个属性时只需要调用一句语句就可以,如下:

classVisitor.visitField(Opcodes.ACC_PUBLIC, "age", Type.getDescriptor(int.class),
				null, null);

 第一个参数指定的是这个属性的操作权限,第二个参数是属性名,第三个参数是类型描述,第四个参数是泛型类型,第五个参数是初始化的值,这里需要注意一下的是第五个参数,这个参数只有属性为static时才有效,也就是数只有为static时,这个值才真正会赋值给我们添加的属性上,对于非static属性,它将被忽略。

好了,让我们看一段代码,在visitEnd去添加一个名字为age的属性:

public class Transform extends ClassAdapter {

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

	@Override
	public void visitEnd() {
		cv.visitField(Opcodes.ACC_PUBLIC, "age", Type.getDescriptor(int.class),
				null, null);
	}

}

 非常简单吧,只要一句话就可以添加一个属性到我们的类中,看一下我们的测试类:

public class TransformTest {
	@Test
	public void addAge() throws Exception {
		ClassReader classReader = new ClassReader(
				"org.victorzhzh.core.field.Person");
		ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS);
		ClassAdapter classAdapter = new Transform(classWriter);

		classReader.accept(classAdapter, ClassReader.SKIP_DEBUG);

		byte[] classFile = classWriter.toByteArray();

		GeneratorClassLoader classLoader = new GeneratorClassLoader();
		Class clazz = classLoader.defineClassFromClassFile(
				"org.victorzhzh.core.field.Person", classFile);
		Object obj = clazz.newInstance();

		System.out.println(clazz.getDeclaredField("name").get(obj));//----(1)
		System.out.println(clazz.getDeclaredField("age").get(obj));//----(2)
	}
}

 在这里,如果我们的age没有被添加进去那么(2)这个地方将会报错,看一下结果:

zhangzhuo
0

 int类型在没有被初始化时,默认值为0,而第二行输出0,说明我们添加了一个属性age

 

接下来,我们在visitField方法中在次添加age属性,如下:

public class Transform extends ClassAdapter {

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

	@Override
	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);
		return super.visitField(access, name, desc, signature, value);
	}

}

 这时,我们再次运行测试类,结果如下:

java.lang.ClassFormatError: Duplicate field name&signature in class file org/victorzhzh/core/field/Person
	at java.lang.ClassLoader.defineClass1(Native Method)
	at java.lang.ClassLoader.defineClassCond(ClassLoader.java:632)
	at java.lang.ClassLoader.defineClass(ClassLoader.java:616)
	at java.lang.ClassLoader.defineClass(ClassLoader.java:466)
	at org.victorzhzh.common.GeneratorClassLoader.defineClassFromClassFile(GeneratorClassLoader.java:14)
	at org.victorzhzh.core.field.TransformTest.addAge(TransformTest.java:22)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
	at java.lang.reflect.Method.invoke(Method.java:597)
	at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:44)
	at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:15)
	at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:41)
	at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:20)
	at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:76)
	at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:50)
	at org.junit.runners.ParentRunner$3.run(ParentRunner.java:193)
	at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:52)
	at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:191)
	at org.junit.runners.ParentRunner.access$000(ParentRunner.java:42)
	at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:184)
	at org.junit.runners.ParentRunner.run(ParentRunner.java:236)
	at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:49)
	at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:467)
	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683)
	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390)
	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197)

 很奇怪,怎么会属性名重复,我们看一下原类,

public String name = "zhangzhuo";
public String address = "xxxxx" ;

 没有重复的名字,而我们添加的是age也不重复,为什么会报重复属性名错误呢?

原因是,在我们的Transform类中的visitField方法,这个方法会在每次属性被访问时调用,而ASM在对这个类操作时会遍历到每个属性,也就是说有一个属性就会调用一次visitField方法,有两个属性就会调用两次visitField方法,所以当我们原类中有两个属性时visitField方法被调用了两次,因此创建了两个同名的age属性。

 

从这个例子中我们可以将visitInnerClass、visitField、visitMethod和visitEnd这些方法分成两组,一组是visitInnerClass、visitField、visitMethod,这些方法有可能会被多次调用,因此在这些方法中创建属性时要注意会重复创建;另一组是visitEnd,这个方法只有在最后才会被调用且只调用一次,所以在这个方法中添加属性是唯一的,因此一般添加属性选择在这个方法里编码。

    当然这里只给出了如何创建一个属性,其实修改,删除也都一样,根据上述知识大家可以参考ASM的源码即可掌握修改删除等操作。

 

附GeneratorClassLoader类代码

public class GeneratorClassLoader extends ClassLoader {

	@SuppressWarnings("rawtypes")
	public Class defineClassFromClassFile(String className, byte[] classFile)
			throws ClassFormatError {
		return defineClass(className, classFile, 0, classFile.length);
	}
}
 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值