使用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

相关文章

Linux使用fdisk进行磁盘的相关操作

《Linux使用fdisk进行磁盘的相关操作》fdisk命令是Linux中用于管理磁盘分区的强大文本实用程序,这篇文章主要为大家详细介绍了如何使用fdisk进行磁盘的相关操作,需要的可以了解下... 目录简介基本语法示例用法列出所有分区查看指定磁盘的区分管理指定的磁盘进入交互式模式创建一个新的分区删除一个存

C#使用HttpClient进行Post请求出现超时问题的解决及优化

《C#使用HttpClient进行Post请求出现超时问题的解决及优化》最近我的控制台程序发现有时候总是出现请求超时等问题,通常好几分钟最多只有3-4个请求,在使用apipost发现并发10个5分钟也... 目录优化结论单例HttpClient连接池耗尽和并发并发异步最终优化后优化结论我直接上优化结论吧,

SpringBoot使用Apache Tika检测敏感信息

《SpringBoot使用ApacheTika检测敏感信息》ApacheTika是一个功能强大的内容分析工具,它能够从多种文件格式中提取文本、元数据以及其他结构化信息,下面我们来看看如何使用Ap... 目录Tika 主要特性1. 多格式支持2. 自动文件类型检测3. 文本和元数据提取4. 支持 OCR(光学

JAVA系统中Spring Boot应用程序的配置文件application.yml使用详解

《JAVA系统中SpringBoot应用程序的配置文件application.yml使用详解》:本文主要介绍JAVA系统中SpringBoot应用程序的配置文件application.yml的... 目录文件路径文件内容解释1. Server 配置2. Spring 配置3. Logging 配置4. Ma

Linux使用dd命令来复制和转换数据的操作方法

《Linux使用dd命令来复制和转换数据的操作方法》Linux中的dd命令是一个功能强大的数据复制和转换实用程序,它以较低级别运行,通常用于创建可启动的USB驱动器、克隆磁盘和生成随机数据等任务,本文... 目录简介功能和能力语法常用选项示例用法基础用法创建可启动www.chinasem.cn的 USB 驱动

基于Python开发电脑定时关机工具

《基于Python开发电脑定时关机工具》这篇文章主要为大家详细介绍了如何基于Python开发一个电脑定时关机工具,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录1. 简介2. 运行效果3. 相关源码1. 简介这个程序就像一个“忠实的管家”,帮你按时关掉电脑,而且全程不需要你多做

C#使用yield关键字实现提升迭代性能与效率

《C#使用yield关键字实现提升迭代性能与效率》yield关键字在C#中简化了数据迭代的方式,实现了按需生成数据,自动维护迭代状态,本文主要来聊聊如何使用yield关键字实现提升迭代性能与效率,感兴... 目录前言传统迭代和yield迭代方式对比yield延迟加载按需获取数据yield break显式示迭

使用SQL语言查询多个Excel表格的操作方法

《使用SQL语言查询多个Excel表格的操作方法》本文介绍了如何使用SQL语言查询多个Excel表格,通过将所有Excel表格放入一个.xlsx文件中,并使用pandas和pandasql库进行读取和... 目录如何用SQL语言查询多个Excel表格如何使用sql查询excel内容1. 简介2. 实现思路3

java脚本使用不同版本jdk的说明介绍

《java脚本使用不同版本jdk的说明介绍》本文介绍了在Java中执行JavaScript脚本的几种方式,包括使用ScriptEngine、Nashorn和GraalVM,ScriptEngine适用... 目录Java脚本使用不同版本jdk的说明1.使用ScriptEngine执行javascript2.

c# checked和unchecked关键字的使用

《c#checked和unchecked关键字的使用》C#中的checked关键字用于启用整数运算的溢出检查,可以捕获并抛出System.OverflowException异常,而unchecked... 目录在 C# 中,checked 关键字用于启用整数运算的溢出检查。默认情况下,C# 的整数运算不会自