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


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

    通常我们想要在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中的各种信息,所以我们需要用到下面一些对象:

    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修饰的类。

    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:其他的对象

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

    没了~

    原文地址: https://www.cnblogs.com/hebaibai/p/11011004.html

  • 相关阅读:
    Mysql学习总结(19)——Mysql无法创建外键的原因
    Java Web学习总结(21)——http协议响应状态码大全以及常用状态码
    Java Web学习总结(21)——http协议响应状态码大全以及常用状态码
    Tomcat学习总结(5)——Tomcat容器管理安全的几种验证方式
    Android学习总结(1)——好的 Android 开发习惯
    phabricator
    linux-kernel 学习计划
    【华为云技术分享】Linux内核编程环境 (1)
    7 分钟全面了解位运算
    【华为云技术分享】鲲鹏弹性云服务器GCC交叉编译环境搭建指南
  • 原文地址:https://www.cnblogs.com/hebaibai/p/11011004.html
Copyright © 2020-2023  润新知