比反射更快!使用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

相關文章
相關標籤/搜索