比反射更快!使用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中的各種資訊,所以我們需要用到下面一些物件:
- 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
。。。省略。。。
訪問標示有這麼幾種:
名稱 | 值 |
---|---|
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
(對於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:描述
3:其他的物件
先寫到這裡吧~~ 有時間了補上。
沒了~
原文地址: https://www.cnblogs.com/hebaibai/p/11011004.h