网站首页 > 技术文章 正文
通常我们想要在java运行时获取class的信息时,通常使用反射的方式来获取其中的属性,方法,注解等信息。通常是这样的:
Class<Aoo> aooClass = Aoo.class; //获取declaredMethod for (Method declaredMethod : aooClass.getDeclaredMethods()) { System.out.println("declaredMethod.getName() : " + declaredMethod.getName()); System.out.println("declaredMethod.getReturnType(): " + declaredMethod.getReturnType().getName()); } //获取DeclaredField for (Field field : aooClass.getDeclaredFields()) { System.out.println("field.getName() : " + field.getName()); System.out.println("field.getType() : " + field.getType().getName()); } //获取Annotation for (Annotation annotation : aooClass.getAnnotations()) { System.out.println("annotation.annotationType() : " + annotation.annotationType().getName()); } ... 获取其他的一些信息
虽然用起来也是很好用,api也不复杂,但是由于使用反射对性能的开销比较大,性能不是很好。我们可以通过asm来获取class中的信息。
从官网抄的介绍:
官网:https://asm.ow2.io/
ASM是一个通用的Java字节码操作和分析框架。它可以用于修改现有类或直接以二进制形式动态生成类。ASM提供了一些常见的字节码转换和分析算法,可以从中构建自定义复杂转换和代码分析工具。ASM提供与其他Java字节码框架类似的功能,但专注于 性能。因为它的设计和实现尽可能小而且快,所以它非常适合在动态系统中使用(但当然也可以以静态方式使用,例如在编译器中)。
嗯~
看起来很不错,怎么用呢?
添加依赖
<dependency> <groupId>org.ow2.asm</groupId> <artifactId>asm</artifactId> <version>7.1</version> </dependency>
读取class需要的对象
现在的asm版本是7.1,所以这一内容都以7.1的版本为主。
因为我们要做的是获取class中的各种信息,所以我们需要用到下面一些对象:
- ClassReader :按照Java虚拟机规范中定义的方式来解析class文件中的内容,在遇到合适的字段时调用ClassVisitor中相对应的方法。
- ClassVisitor:java中类的访问者,提供一系列方法由ClassReader调用。是一个抽象类,我们在使用的时候需要继承此类。使用此对象的时候需要指定asm api的版本。
- ModuleVisitor:Java中模块的访问者,作为ClassVisitor.visitModule方法的返回值,要是不关心模块的使用情况,可以返回一个null。使用此对象的时候需要指定asm api的版本。
- AnnotationVisitor:Java中注解的访问者,作为ClassVisito中visitTypeAnnotation和visitTypeAnnotation的返回值,要是不关心注解的使用情况,可以返回一个null。使用此对象的时候需要指定asm api的版本。
- FieldVisitor:Java中字段的访问者,作为ClassVisito.visitField的返回值,要是不关心字段的使用情况,可以返回一个null。使用此对象的时候需要指定asm api的版本。
- MethodVisitor:Java中方法的访问者,作为ClassVisito.visitMethod的返回值,要是不关心方法的使用情况,可以返回一个null。使用此对象的时候需要指定asm api的版本。
一些需要的说明
class的访问标示:
可以使用如下命令:
//命令 javap -v Aoo.class //结果 。。。省略。。。 public class com.hebaibai.example.demo.Demo minor version: 0 major version: 52 //这里是访问标示 flags: ACC_PUBLIC, ACC_SUPER Constant pool: #1 = Methodref #22.#42 // java/lang/Object."<init>":()V #2 = Methodref #43.#44 // java/lang/System.currentTimeMillis:()J #3 = Class #45 // org/objectweb/asm/ClassReader 。。。省略。。。
访问标示有这么几种:
其中方法的返回值是上面几个表格中几个参数相加的结果。比如如果结果为33,那么就是ACC_PUBLIC与ACC_SUPER相加的结果,代表是一个public修饰的类。
asm api 版本说明
api版本不同支持的功能也不同,通过查看代码,大致上有以下区别,可能有遗漏或者错误。高版本不支持的功能,低版本同样不支持。
Opcodes.ASM4:
不支持:
//方法 ClassVisitor.visitTypeAnnotation FieldVisitor.visitTypeAnnotation MethodVisitor.visitTypeAnnotation MethodVisitor.visitParameter MethodVisitor.visitMethodInsn MethodVisitor.visitInvokeDynamicInsn MethodVisitor.visitLdcInsn MethodVisitor.visitInsnAnnotation MethodVisitor.visitTryCatchAnnotation MethodVisitor.visitLocalVariableAnnotation
Opcodes.ASM5:
不支持:
//方法 ClassVisitor.visitModule //对象 ModuleVisitor
Opcodes.ASM6:
不支持
//方法 ClassVisitor.visitNestHost ClassVisitor.visitNestMember MethodVisitor.visitLdcInsn
Opcodes.ASM7:
应该是没有不支持的方法。
解释一下
这里只是介绍一些我感觉常用的一些方法,要看详细内容的话,请看官方的文档:https://asm.ow2.io/javadoc/overview-summary.html
1: ClassReader
构造函数:
//使用class的名称 ClassReader classReader = new ClassReader(Aoo.class.getName()); //使用InputStream File classFile = new File("/home/hjx/demo/target/classes/com/hebaibai/example/demo/Aoo.class"); ClassReader classReader = new ClassReader(new FileInputStream(classFile)); //使用byte[] File classFile = new File("/home/hjx/demo/target/classes/com/hebaibai/example/demo/Aoo.class"); FileInputStream inputStream = new FileInputStream(classFile); byte[] classBytes = new byte[inputStream.available()]; inputStream.read(classBytes); ClassReader classReader = new ClassReader(classBytes);
方法:
1:accept(ClassVisitor classVisitor, int parsingOptions)
使用给定的ClassVisitor来传递解析后得到的class中的信息。 parsingOptions参数代表用于解析class的选项,有几个取值范围:
ClassReader.SKIP_CODE:
跳过代码属性的标志(个人感觉就是没有方法会被特地跳过)
ClassReader.SKIP_FRAMES:
跳过StackMap和StackMapTable属性的标志。跳过MethodVisitor.visitFrame方法。
ClassReader.SKIP_DEBUG:
跳过SourceFile,SourceDebugExtension,LocalVariableTable,LocalVariableTypeTable和LineNumberTable属性的标志。跳过ClassVisitor.visitSource, MethodVisitor.visitLocalVariable, MethodVisitor.visitLineNumber方法。
ClassReader.EXPAND_FRAMES:
用于展开堆栈映射帧的标志。这会大大降低性能。(文档上写的,感觉上用不到)
2:getAccess()
返回class的访问标志,是一个int类型的参数。
3:getClassName()
获取类的名称,没什么说的。
4:getSuperName()
获取超类的名称,也没啥说的。
5:getInterfaces()
获取接口名称,同样没啥说的。
6:其他的方法
看起来太高级了,看不懂,不知道干啥用,不写了。
使用例子
ClassReader classReader = new ClassReader(Aoo.class.getName()); //这里使用的匿名内部类,需要获取class信息需要继承重写超类的一些方法,下面会说 classReader.accept(new ClassVisitor(Opcodes.ASM7) { { System.out.println("init ClassVisitor"); } }, ClassReader.SKIP_DEBUG); System.out.println(classReader.getClassName()); System.out.println(Arrays.toString(classReader.getInterfaces())); System.out.println(classReader.getSuperName()); System.out.println(classReader.getAccess()); //结果 init ClassVisitor com/hebaibai/example/demo/Aoo [java/io/Serializable] java/lang/Object 33
2:ClassVisitor
这个类是我们获取class信息主要用到的对象,因为是一个抽象类,我们在使用的时候需要自己写一个类来继承它。需要得到哪些信息就重写哪些方法。
这个类方法比较多,写几个常用到的。
构造函数:
public ClassVisitor(int api) public ClassVisitor(int api, ClassVisitor classVisitor)
api参数指asm api版本。
classVisitor参数为委派方法的调用对象。
方法:
1:void visit(int version, int access, String name, String signature, String superName, String[] interfaces)
访问class的头信息
version:class版本(编译级别)
access: 访问标示
name:类名称
signature:class的签名,可能是null
superName:超类名称
interfaces:接口的名称
2:void visitAnnotation(String descriptor, boolean visible)
访问class的注解信息
descriptor:描述信息
visible:是否运行时可见
3:FieldVisitor visitField(int access, String name,String descriptor, String signature,Object value)
访问class中字段的信息,返回一个FieldVisitor用于获取字段中更加详细的信息。
name:字段个的名称
descriptor:字段的描述
value:该字段的初始值,文档上面说:
该参数,其可以是零,如果字段不具有初始值,必须是一个Integer,一Float,一Long,一个Double或一个String(对于int,float,long 或String分别字段)。此参数仅用于静态字段。对于非静态字段,它的值被忽略,非静态字段必须通过构造函数或方法中的字节码指令进行初始化(但是不管我怎么试,结果都是null)。
4:MethodVisitor visitMethod(int access,String name,String descriptor,String signature, String[] exceptions)
访问class中方法的信息,返回一个MethodVisitor用于获取方法中更加详细的信息。
name:方法的名称
descriptor:方法的描述
signature:方法的签名
exceptions:方法的异常名称
5:visitInnerClass(String name, String outerName, String innerName, int access)
访问class中内部类的信息。这个内部类不一定是被访问类的成员(这里的意思是可能是一段方法中的匿名内部类,或者声明在一个方法中的类等等)。
name:内部类的名称。例子com/hebaibai/example/demo/Aoo$1XX
outerName:内部类所在类的名称
innerName:内部类的名称
6:visitOuterClass(String owner, String name, String descriptor)
访问该类的封闭类。仅当类具有封闭类时,才必须调用此方法。
我自己试了一下,如果在一个方法中定义了一个class,或者定义个一个匿名内部类,这时通过visitInnerClass方法能够得到例如com/hebaibai/example/demo/Aoo$1或者com/hebaibai/example/demo/Aoo$1XX的类名称。这时通过使用
ClassReader classReader = new ClassReader("com/hebaibai/example/demo/Aoo$1"); classReader.accept(new DemoClassVisitor(Opcodes.ASM7), ClassReader.SKIP_CODE);
可以得到持有内部类的类信息。
owner:拥有该类的class名称
name:包含该类的方法的名称,如果该类未包含在其封闭类的方法中,则返回null
descriptor:描述
猜你喜欢
- 2024-09-21 Java并发编程:LongAdder | LongAccumulator 对比测试
- 2024-09-21 「Java技巧」优雅的统计程序的执行时间,别再用System.cur
- 2024-09-21 Flink SQL 知其所以然(九)| SQL 的时间语义
- 2024-09-21 ArrayList插入1000w条数据之后,我怀疑了jvm...
- 2024-09-21 《Java实战之内存模型》详解篇(java内存模型happens before)
- 2024-09-21 了解Java线程优先级,更要知道对应操作系统的优先级,不然会踩坑
- 2024-09-21 让大学生写的一个计算时间的方法,有人看得出来是在做什么吗?这
- 2024-09-21 Java基础——Java多线程(Lock接口详解)
- 2024-09-21 JVM性能调优监控工具jps、jstack、jmap、jhat、jstat使用详解
- 2024-09-21 最近学到的限流知识(限流的含义)
- 1514℃桌面软件开发新体验!用 Blazor Hybrid 打造简洁高效的视频处理工具
- 569℃Dify工具使用全场景:dify-sandbox沙盒的原理(源码篇·第2期)
- 510℃MySQL service启动脚本浅析(r12笔记第59天)
- 486℃服务器异常重启,导致mysql启动失败,问题解决过程记录
- 485℃启用MySQL查询缓存(mysql8.0查询缓存)
- 467℃「赵强老师」MySQL的闪回(赵强iso是哪个大学毕业的)
- 446℃mysql服务怎么启动和关闭?(mysql服务怎么启动和关闭)
- 444℃MySQL server PID file could not be found!失败
- 最近发表
- 标签列表
-
- c++中::是什么意思 (83)
- 标签用于 (65)
- 主键只能有一个吗 (66)
- c#console.writeline不显示 (75)
- pythoncase语句 (81)
- es6includes (73)
- windowsscripthost (67)
- apt-getinstall-y (86)
- node_modules怎么生成 (76)
- c++int转char (75)
- static函数和普通函数 (76)
- el-date-picker开始日期早于结束日期 (70)
- js判断是否是json字符串 (67)
- checkout-b (67)
- c语言min函数头文件 (68)
- asynccallback (71)
- localstorage.removeitem (74)
- vector线程安全吗 (70)
- & (66)
- java (73)
- js数组插入 (83)
- mac安装java (72)
- eacces (67)
- 查看mysql是否启动 (70)
- 无效的列索引 (74)