欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

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

程序员文章站 2022-03-20 13:10:38
比反射更快!使用ASM获取class信息(ClassReader) 通常我们想要在java运行时获取class的信息时,通常使用反射的方式来获取其中的属性,方法,注解等信息。通常是这样的: 虽然用起来也是很好用,api也不复杂,但是由于使用反射对性能的开销比较大,性能不是很好。我们可以通过asm来获 ......

比反射更快!使用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中的信息。

从官网抄的介绍:

官网:

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:

应该是没有不支持的方法。

解释一下

这里只是介绍一些我感觉常用的一些方法,要看详细内容的话,请看官方的文档:

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

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

没了~

原文地址: