在上一篇文章中,我们看到了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);
}
}
分享到:
相关推荐
- **Core API**:基于事件的模型,将Java类表示为一系列事件,每个事件对应类的一个元素,如类头、字段、方法等。这种模式类似于XML的SAX解析。 - **Tree API**:基于对象的模型,将Java类视为一个对象树,每个...
字节码由一系列操作码(opcode)和操作数组成,每个操作码对应一个简单的操作,如加载、存储、算术运算等。字节码文件包含了类信息、字段信息、方法信息以及常量池等,这些信息被JVM用于解释执行。 3. **Java...
例如,类的定义包括类名、属性和操作,消息传递可以有同步和异步两种方式,操作可以有输入参数和返回值等。 5. **模拟与分析**:ASM4支持对模型进行动态模拟,这有助于理解系统在不同条件下的行为。此外,它还提供...
4. **字节码操作**:ASM提供了大量的OpCode类,对应于Java字节码指令。通过这些类,开发者可以直接生成或修改字节码,实现低级别的程序逻辑。 5. **动态代理**:ASM可以用来创建动态代理类,实现接口或扩展特定类。...
3. **属性处理**:ASM-Attrs.jar 提供了对类、字段、方法等属性的高级操作,包括访问、修改和添加自定义属性。在处理如注解、运行时可见性等元数据时,这一特性尤为重要。 4. **API设计**:ASM的API设计简洁而强大...
在`FieldVisitor`中定义了一系列的`visitXxx()`方法,如`visitAnnotation()`, `visitTypeAnnotation()`, `visitAttribute()`等,用于处理字段的各种元数据,例如注解、类型注解和自定义属性。然而,在实际使用中,...
3. **字节码修改**:在解析类文件后,ASM允许开发者修改这些对象模型,比如添加、删除或修改方法体、字段和类属性。这种能力对于AOP(面向切面编程)和代码增强等场景非常有用。 4. **字节码生成器**:ASM提供了...
ASM库主要用于在运行时解析和生成Java类的字节码,它提供了低级别的访问方式来操作类的结构,包括字段、方法、指令和类的加载。 描述中提到的"asm,all,3.3.1,sources,jar.zip包下载,依赖包"进一步强调了ASM库的关键...
以及一系列的`Visitor`接口,它们定义了遍历和操作类结构的方法。在asm-analysis-2.2.jar中,包含了一些专门用于分析的`Visitor`实现,如`Frame`类,它可以分析和计算方法的局部变量表和操作数栈。 另一个文件,asm...
"asm-attrs"部分可能指的是ASM库中处理Java类属性的部分,比如注解(annotations)和其他元数据。在Java字节码中,属性是与类、字段、方法或代码块关联的额外信息。ASM库提供了对这些属性的解析和生成能力,使得...
四、ASM Tree的应用场景 1. 动态代理:ASM Tree常用于动态生成代理类,实现AOP(面向切面编程)功能,如Spring AOP的实现就部分依赖于ASM。 2. 代码混淆:在Android开发中,ProGuard和R8等混淆工具使用ASM Tree来...
总之,ASM-attrs-1.5.3.jar作为ASM库的一个版本,为Java开发者提供了强大的字节码操作能力,特别是对于处理类属性方面,使得我们可以创建更复杂、更灵活的程序。同时,了解和遵循相关的许可协议,是使用开源库的基本...
"asm-attrs-2.1.jar"可能是一个包含了ASM库对Java类属性处理功能的特定版本。在Java中,类属性包括如源文件名、方法参数的名字、注解等元数据。ASM库提供了访问和修改这些属性的工具,使得开发者可以更深入地控制...
【标签】"工具"表明c32asm是一个实用程序,可能是为了开发人员或系统管理员设计的,他们需要对dill文件进行高级操作,比如分析、调试或者逆向工程。在Python开发环境中,这类工具是十分宝贵的,因为它们可以帮助...
ASM是一个强大的Java字节码操控和分析框架,它可以直接生成和修改Java类的字节码。在提供的代码示例中,我们看到如何使用ASM库来动态生成一个名为`HelloWorld`的类。以下是对该过程的详细解释: 1. **创建Class...
而ASM则是汇编语言的缩写,是一种低级编程语言,直接对应于机器指令,对计算机硬件的操作非常直接。 描述中提到的“内含几个已经写好的类”,这意味着在压缩包里可能包含了一些用汇编语言实现的面向对象结构。在...
ASM-Attrs-1.5.2.jar.zip文件是ASM库的一个特定版本,包含了对Java类和属性进行深度操作的特性。ASM库以其高度灵活性和低级访问权限,为开发者提供了深入到Java虚拟机(JVM)内部的可能性,从而实现诸如代码混淆、...
ASM库主要用于生成和分析Java字节码,从而提供对Java类和方法的低级别操作。 描述中提到的"asm, attrs, 2.0, jar.zip包下载, 依赖包"表明这个压缩文件与ASM库的Attributes模块有关,该模块可能处理Java类文件中的...
ASM Tree库,作为ASM框架的一个重要组成部分,是Java字节码操作的核心工具之一。本文将深入探讨ASM Tree库3.3.1版本的特性、功能以及在实际开发中的应用。 首先,ASM是一个Java字节码操纵和分析框架,它可以直接...
4. **实例方法**:一旦状态机被定义,类实例就会获得一系列与之相关的实例方法,如`activate!`、`deactivate!`等,用于触发事件并执行状态转换。这些方法会自动处理当前状态的检查和转换。 三、高级特性 1. **条件...