比反射更快!使用ASM获取class信息(ClassReader)

比反射更快!使用ASM获取class信息(ClassReader)

一般咱们想要在java运行时获取class的信息时,一般使用反射的方式来获取其中的属性,方法,注解等信息。一般是这样的:html

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中的信息。java

从官网抄的介绍:

官网:https://asm.ow2.io/web

ASM是一个通用的Java字节码操做和分析框架。它能够用于修改现有类或直接以二进制形式动态生成类。ASM提供了一些常见的字节码转换和分析算法,能够从中构建自定义复杂转换和代码分析工具。ASM提供与其余Java字节码框架相似的功能,但专一于 性能。由于它的设计和实现尽量小并且快,因此它很是适合在动态系统中使用(但固然也能够以静态方式使用,例如在编译器中)。算法

嗯~api

看起来很不错,怎么用呢?

添加依赖

<dependency>
    <groupId>org.ow2.asm</groupId>
    <artifactId>asm</artifactId>
    <version>7.1</version>
</dependency>

读取class须要的对象

如今的asm版本是7.1,因此这一内容都以7.1的版本为主。框架

由于咱们要作的是获取class中的各类信息,因此咱们须要用到下面一些对象:函数

  1. ClassReader :按照Java虚拟机规范中定义的方式来解析class文件中的内容,在遇到合适的字段时调用ClassVisitor中相对应的方法。
  2. ClassVisitor:java中的访问者,提供一系列方法由ClassReader调用。是一个抽象类,咱们在使用的时候须要继承此类。使用此对象的时候须要指定asm api的版本。
  3. ModuleVisitor:Java中模块的访问者,做为ClassVisitor.visitModule方法的返回值,要是不关心模块的使用状况,能够返回一个null。使用此对象的时候须要指定asm api的版本。
  4. AnnotationVisitor:Java中注解的访问者,做为ClassVisitovisitTypeAnnotationvisitTypeAnnotation的返回值,要是不关心注解的使用状况,能够返回一个null。使用此对象的时候须要指定asm api的版本。
  5. FieldVisitor:Java中字段的访问者,做为ClassVisito.visitField的返回值,要是不关心字段的使用状况,能够返回一个null。使用此对象的时候须要指定asm api的版本。
  6. 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
。。。省略。。。

访问标示有这么几种:性能

名称
ACC_ABSTRACT 1024
ACC_ANNOTATION 8192
ACC_BRIDGE 64
ACC_DEPRECATED 131072
ACC_ENUM 16384
ACC_FINAL 16
ACC_INTERFACE 512
ACC_MANDATED 32768
ACC_MODULE 32768
ACC_NATIVE 256
ACC_OPEN 32
ACC_PRIVATE 2
ACC_PROTECTED 4
ACC_PUBLIC 1
ACC_STATIC 8
ACC_STATIC_PHASE 64
ACC_STRICT 2048
ACC_SUPER 32
ACC_SYNCHRONIZED 32
ACC_SYNTHETIC 4096
ACC_TRANSIENT 128
ACC_TRANSITIVE 32
ACC_VARARGS 128
ACC_VOLATILE 64

其中方法的返回值是上面几个表格中几个参数相加的结果。好比若是结果为33,那么就是ACC_PUBLIC与ACC_SUPER相加的结果,表明是一个public修饰的类。ssr

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(对于intfloatlong 或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:描述

3:其余的对象

先写到这里吧~~ 有时间了补上。

没了~

原文地址: http://www.javashuo.com/article/p-cxyljjex-cn.html

相关文章
相关标签/搜索