使用ASM为一个类增加属性工具类

2024-06-19 17:28
文章标签 工具 使用 属性 增加 asm

本文主要是介绍使用ASM为一个类增加属性工具类,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

使用ASM可以方便的访问和修改一个类,或者在JVM载入前动态更新一个类也是可以的,如下,

为一个类动态增加一个属性,类Student如下:

public class Student {private int age;private String name;public int getAge() {return age;}public void setAge(int age) {this.age = age;}public String getName() {return name;}public void setName(String name) {this.name = name;}
}

 使用ASM实现一个类写入属性AddFieldAdapter,如下:

public class AddFieldAdapter extends ClassAdapter {private int accessModifier;private String name;private String desc;private boolean isFieldPresent;public AddFieldAdapter(ClassVisitor cv, int accessModifier, String name, String desc) {super(cv);this.accessModifier = accessModifier;this.name = name;this.desc = desc;}@Overridepublic FieldVisitor visitField(int access, String name, String desc,String signature, Object value) {if (name.equals(this.name)) {isFieldPresent = true;}return cv.visitField(access, name, desc, signature, value);}@Overridepublic void visitEnd() {if (!isFieldPresent) {// 若属性不存在则写入FieldVisitor fv = cv.visitField(accessModifier, name, desc, null, null);if (fv != null) {fv.visitEnd();}}cv.visitEnd();}
} 

实现一个类AddField调用上面的类去添加属性,如下:

public class AddField {private Class<?> clazz;private ClassReader cr;private ClassWriter cw;private ClassAdapter ca;private File classFile;private final static String CLASS_FILE_SUFFIX = ".class";public AddField(Class<?> clazz) {this.clazz = clazz;}public void addPublicField(String fieldName, String fieldDesc) {if(cr == null) {try {// com.demo2.Studentcr = new ClassReader(clazz.getCanonicalName());} catch (IOException e) {e.printStackTrace();}cw = new ClassWriter(cr, ClassWriter.COMPUTE_MAXS);}if(ca == null) {ca = new AddFieldAdapter(cw, Opcodes.ACC_PUBLIC, fieldName, fieldDesc);} else {ca = new AddFieldAdapter(ca, Opcodes.ACC_PUBLIC, fieldName, fieldDesc);}}public void writeByteCode() {cr.accept(ca, ClassReader.SKIP_DEBUG);byte[] bys = cw.toByteArray();OutputStream os = null;try {os = new FileOutputStream(getFile());os.write(bys);} catch (IOException e) {e.printStackTrace();} finally {try {os.close();} catch (IOException e) {e.printStackTrace();}}}private File getFile() {if(classFile == null) {StringBuffer sb = new StringBuffer();sb.append(clazz.getResource("/")).append(clazz.getCanonicalName().replace(".", File.separator)).append(CLASS_FILE_SUFFIX);classFile = new File(sb.substring(6));}return classFile;} //测试方法public static void main(String[] args) {// 为 Student 添加字段AddField add = new AddField(Student.class);// 添加一个名为 address,类型为 java.lang.String 的 public 字段add.addPublicField("address", "Ljava/lang/String;");// 再增加一个名为 tel,类型为 int 的 public 方法add.addPublicField("tel", "I");// 重新生成 .class 文件add.writeByteCode();}
}

 使用反编译工具查看生成的CLASS文件,即可看到动态添加的属性了。


ASM操作类属性

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

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

public class Student {public String name = "Jimmy";public String address = "China";
}

 这里,我们将属性定义为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属性,它将被忽略。

​​​​​​​我们现在用两种方式来添加一个名字为age的属性:

第一种方式:

用ClassWriter直接创建

ClassReader cr = new ClassReader(Student.class.getName());ClassWriter cw = new ClassWriter(cr, ClassWriter.COMPUTE_MAXS);ClassVisitor cv = new AddFieldClassAdapter(cw);cr.accept(cv, Opcodes.ASM4);byte[] code = cw.toByteArray();AddField loader = new AddField();Class<?> exampleClass = loader.defineClass(Student.class.getName(), code, 0, code.length);Object obj = exampleClass.newInstance();System.out.println(exampleClass.getField("name").get(obj));System.out.println(exampleClass.getField("age").get(obj));

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

Jimmy
0

 第二种方式,写一个adapter过滤器,重写visitEnd方法

public class AddFieldClassAdapter extends ClassVisitor implements Opcodes{public AddFieldClassAdapter(ClassVisitor cv) {super(Opcodes.ASM4, cv);}@Overridepublic void visitEnd() {cv.visitField(Opcodes.ACC_PUBLIC, "age", Type.getDescriptor(int.class),  null, null);super.visitEnd();}}
public class AddField extends ClassLoader implements Opcodes{public static void main(String args[]) throws IOException, SecurityException, NoSuchFieldException, InstantiationException, IllegalAccessException{ClassReader cr = new ClassReader(Student.class.getName());ClassWriter cw = new ClassWriter(cr, ClassWriter.COMPUTE_MAXS);ClassVisitor cv = new AddFieldClassAdapter(cw);cr.accept(cv, Opcodes.ASM4);byte[] code = cw.toByteArray();AddField loader = new AddField();Class<?> exampleClass = loader.defineClass(Student.class.getName(), code, 0, code.length);Object obj = exampleClass.newInstance();System.out.println(exampleClass.getField("name").get(obj));System.out.println(exampleClass.getField("age").get(obj));}
}

结果如下:

Jimmy

  和第一种方式返回的结果一一致。

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

public class AddFieldClassAdapter extends ClassVisitor implements Opcodes{public AddFieldClassAdapter(ClassVisitor cv) {super(Opcodes.ASM4, cv);}@Overridepublic 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);}}

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

Exception in thread "main" java.lang.ClassFormatError: Duplicate field name&signature in class file field/Student
    at java.lang.ClassLoader.defineClass1(Native Method)
    at java.lang.ClassLoader.defineClassCond(ClassLoader.java:631)
    at java.lang.ClassLoader.defineClass(ClassLoader.java:615)
    at java.lang.ClassLoader.defineClass(ClassLoader.java:465)
    at field.AddField.main(AddField.java:21)

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

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

为了验证visitField是被多次调用, 我那么可以做一个实验,在visitField()方法中加入System.out.println("name:" + name);把每次调用的属性名称都打印出来。

public class AddFieldClassAdapter extends ClassVisitor implements Opcodes{public AddFieldClassAdapter(ClassVisitor cv) {super(Opcodes.ASM4, cv);}@Overridepublic FieldVisitor visitField(int access, String name, String desc,String signature, Object value) {System.out.println("name:" + name);return super.visitField(access, name, desc, signature, value);}}

结果:

name:name
name:address

 把Student所有的属性都打印出来,这证明了visitField()是被多次调用的! 

这篇关于使用ASM为一个类增加属性工具类的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!


原文地址:
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.chinasem.cn/article/1075666

相关文章

MySQL的ALTER TABLE命令的使用解读

《MySQL的ALTERTABLE命令的使用解读》:本文主要介绍MySQL的ALTERTABLE命令的使用,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录1、查看所建表的编China编程码格式2、修改表的编码格式3、修改列队数据类型4、添加列5、修改列的位置5.1、把列

Python使用FFmpeg实现高效音频格式转换工具

《Python使用FFmpeg实现高效音频格式转换工具》在数字音频处理领域,音频格式转换是一项基础但至关重要的功能,本文主要为大家介绍了Python如何使用FFmpeg实现强大功能的图形化音频转换工具... 目录概述功能详解软件效果展示主界面布局转换过程截图完成提示开发步骤详解1. 环境准备2. 项目功能结

SpringBoot使用ffmpeg实现视频压缩

《SpringBoot使用ffmpeg实现视频压缩》FFmpeg是一个开源的跨平台多媒体处理工具集,用于录制,转换,编辑和流式传输音频和视频,本文将使用ffmpeg实现视频压缩功能,有需要的可以参考... 目录核心功能1.格式转换2.编解码3.音视频处理4.流媒体支持5.滤镜(Filter)安装配置linu

Redis中的Lettuce使用详解

《Redis中的Lettuce使用详解》Lettuce是一个高级的、线程安全的Redis客户端,用于与Redis数据库交互,Lettuce是一个功能强大、使用方便的Redis客户端,适用于各种规模的J... 目录简介特点连接池连接池特点连接池管理连接池优势连接池配置参数监控常用监控工具通过JMX监控通过Pr

apache的commons-pool2原理与使用实践记录

《apache的commons-pool2原理与使用实践记录》ApacheCommonsPool2是一个高效的对象池化框架,通过复用昂贵资源(如数据库连接、线程、网络连接)优化系统性能,这篇文章主... 目录一、核心原理与组件二、使用步骤详解(以数据库连接池为例)三、高级配置与优化四、典型应用场景五、注意事

使用Python实现Windows系统垃圾清理

《使用Python实现Windows系统垃圾清理》Windows自带的磁盘清理工具功能有限,无法深度清理各类垃圾文件,所以本文为大家介绍了如何使用Python+PyQt5开发一个Windows系统垃圾... 目录一、开发背景与工具概述1.1 为什么需要专业清理工具1.2 工具设计理念二、工具核心功能解析2.

Linux系统之stress-ng测压工具的使用

《Linux系统之stress-ng测压工具的使用》:本文主要介绍Linux系统之stress-ng测压工具的使用,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、理论1.stress工具简介与安装2.语法及参数3.具体安装二、实验1.运行8 cpu, 4 fo

Maven项目中集成数据库文档生成工具的操作步骤

《Maven项目中集成数据库文档生成工具的操作步骤》在Maven项目中,可以通过集成数据库文档生成工具来自动生成数据库文档,本文为大家整理了使用screw-maven-plugin(推荐)的完... 目录1. 添加插件配置到 pom.XML2. 配置数据库信息3. 执行生成命令4. 高级配置选项5. 注意事

Java使用MethodHandle来替代反射,提高性能问题

《Java使用MethodHandle来替代反射,提高性能问题》:本文主要介绍Java使用MethodHandle来替代反射,提高性能问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑... 目录一、认识MethodHandle1、简介2、使用方式3、与反射的区别二、示例1、基本使用2、(重要)

使用C#删除Excel表格中的重复行数据的代码详解

《使用C#删除Excel表格中的重复行数据的代码详解》重复行是指在Excel表格中完全相同的多行数据,删除这些重复行至关重要,因为它们不仅会干扰数据分析,还可能导致错误的决策和结论,所以本文给大家介绍... 目录简介使用工具C# 删除Excel工作表中的重复行语法工作原理实现代码C# 删除指定Excel单元