最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Java字節(jié)碼操縱框架ASM圖文實例詳解

 更新時間:2023年07月09日 09:23:32   作者:悅  
這篇文章主要為大家介紹了Java字節(jié)碼操縱框架ASM圖文實例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪

引言

今天我們將介紹字節(jié)碼相關的應用場景,首先要介紹的是如何對字節(jié)碼做解析和修改,本文將會詳細給大家介紹一個工業(yè)級字節(jié)碼操作框架 ASM。

當我們需要對一個 class 文件做修改時,我們可以選擇自己解析這個class 文件,在符合 Java 字節(jié)碼規(guī)范的前提下進行字節(jié)碼改造。如果你寫過 class 文件的解析程序,會發(fā)現(xiàn)這個過程極其繁瑣,更別說進行增加方法等操作了。

ASM 最開始是 2000 年 Eric Bruneton 在 INRIA(法國國立計算機及自動化研究院)讀博士期間完成的一個作品。那個時候包含 java.lang.reflect.Proxy 包的 JDK 1.3 還沒發(fā)布,ASM 被作為代碼生成器,用來生成動態(tài)代理的代理類。經過多年的發(fā)展,ASM 在諸多框架中已經遍地開花,成為字節(jié)碼操作領域事實上的標準。

簡單的 API 背后 ASM 自動幫我們做了很多事情,比如維護常量池的索引,計算最大棧大小 max_stack,局部變量表大小 max_locals 等,除此之外還有下面這些優(yōu)點:

  • 架構設計精巧,使用方便。
  • 更新速度快,支持最新的 Java 版本
  • 速度非??欤趧討B(tài)代理 class 的生成和 class 的轉換時,盡可能確保運行中的應用不會被 ASM 拖慢
  • 非常可靠、久經考驗,已經有很多著名的開源框架都在使用,例如 cglib,、mybatis、fastjson
    其它字節(jié)碼操作框架在操作字節(jié)碼的過程中生成大量的中間類和對象,耗費大量的內存且運行緩慢,ASM 使用了訪問者(Visitor)設計模式,避免了創(chuàng)建和消耗大量的中間變量。

ASM 提供了兩種生成和轉換類的方法: 基于事件觸發(fā)的 core API 和基于對象的 Tree API,這兩種方式可以用 XML 解析的 SAX 和 DOM 方式來對照。

SAX 解析 XML 文件采用的是事件驅動,它不需要解析完整個文檔,而是一邊按內容順序解析文檔,如果解析時符合特定的事件則回調一些函數(shù)來處理事件。SAX運行時是單向的、流式的,解析過的部分無法在不重新開始的情況下再次讀取,ASM 的 Core API 類似于這種方式。

DOM 解析方式則會將整個 XML 作為類似樹結構的方式讀入內存中以便操作及解析,ASM 的 Tree API 類似于這種方式。

以下面的 XML 文件為例:

<Order>
  <Customer>Arthur</Customer>
  <Product>
      <Name>Birdsong Clock</Name>
      <Quantity>12</Quantity>
      <Price currency="USD">21.95</Price >
  </Product>
</Order>

對應的 SAX 和 DOM 解析方式的如下圖所示:

ASM 核心類介紹

ClassReader

它是字節(jié)碼讀取和分析引擎,幫我們做了最苦最累的解析二進制的 class 文件字節(jié)碼的活。采用類似于 SAX 的事件讀取機制,每當有事件發(fā)生時,觸發(fā)相應的 ClassVisitor、MethodVisitor 等做相應的處理。

ClassVisitor

它是一個抽象類,ClassReader 對象創(chuàng)建之后,調用 ClassReader.accept() 方法,傳入一個 ClassVisitor 對象。ClassVisitor 在解析字節(jié)碼的過程中遇到不同的節(jié)點時會調用不同的 visit() 方法,比如 visitSource, visitOuterClass, visitAnnotation, visitAttribute, visitInnerClass, visitField, visitMethod 和 visitEnd方法。 在上述 visit 的過程中還會產生一些子過程,比如 visitAnnotation 會觸發(fā) AnnotationVisitor 的調用、visitMethod 會觸發(fā) MethodVisitor 的調用。 正是在這些 visit 的過程中,我們得以有機會去修改各個子節(jié)點的字節(jié)碼。

ClassVisitor 類中的 visit 方法必須按照以下的順序被調用執(zhí)行:

visit
[visitSource]
[visitOuterClass] 
(visitAnnotation | visitAttribute)*
(visitInnerClass | visitField | visitMethod)* 
visitEnd

visit 方法最先被調用,接著調用零次或一次 visitSource 方法,接著調用零次或一次 visitOuterClass 方法,再接下來按任意順序調用任意多次 visitAnnotation 和 visitAttribute 方法,再接下來按任意順序調用任意多次 visitInnerClass、visitField、visitMethod 方法,visitEnd 最后被調用。

ClassWriter

這個類是 ClassVisitor 抽象類的一個實現(xiàn)類,其之前的每個 ClassVisitor 都可能對原始的字節(jié)碼做修改,ClassWriter 的 toByteArray 方法則把最終修改的字節(jié)碼以 byte 數(shù)組的形式返回

這三個核心類的關系如下圖

一個最簡單的用法如下面的代碼所示:

public class FooClassVisitor extends ClassVisitor {
    ...
    // visitXXX() 函數(shù)
    ...
}
ClassReader cr = new ClassReader(bytes);
ClassWriter cw = new ClassWriter(cr,
        ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES);
ClassVisitor cv = new FooClassVisitor(cw);
cr.accept(cv, 0);

上面的代碼中,ClassReader 負責讀取類文件字節(jié)數(shù)組,accept 調用之后 ClassReader 會把解析字節(jié)碼過程的事件源源不斷的通知給 ClassVisitor 對象調用不同的 visit 方法,ClassVisitor 可以在這些 visit 方法中對字節(jié)碼進行修改,ClassWriter 可以生成最終修改過的自己字節(jié)碼。

ASM 操作字節(jié)碼案例

接下面我們用幾個簡單的例子來演示 ASM 各個核心類操作字節(jié)碼的案例。

訪問類的方法和字段

ASM 的 visitor 設計模式可以很方便的用來訪問類文件中我們感興趣的部分,比如類文件的字段和方法列表,有下面的類:

public class MyMain {
    public int a = 0;
    public int b = 1;
    public void test01() {
    }
    public void test02() {
    }
}

使用 javac 編譯為 class 文件,可以用下面的 ASM 代碼來輸出類的方法和字段列表:

byte[] bytes  = getBytes(); // MyMain.class 文件的字節(jié)數(shù)組
ClassReader cr = new ClassReader(bytes);
ClassWriter cw = new ClassWriter(0);
ClassVisitor cv = new ClassVisitor(ASM5, cw) {
    @Override
    public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) {
        System.out.println("field: " + name);
        return super.visitField(access, name, desc, signature, value);
    }
    @Override
    public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
        System.out.println("method: " + name);
        return super.visitMethod(access, name, desc, signature, exceptions);
    }
};
cr.accept(cv, ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG);

輸出結果:

field: a
field: b
method: <init>
method: test01
method: test02

值得注意的是 ClassReader 類 accept 方法的第二個參數(shù) flags,這個參數(shù)是一個比特掩碼(bit-mask),可以選擇組合的值如下:

  • SKIP_DEBUG:跳過類文件中的調試信息,比如行號信息(LineNumberTable)等
  • SKIP_CODE:跳過方法體中的 Code 屬性(方法字節(jié)碼、異常表等)
  • EXPAND_FRAMES:展開 StackMapTable 屬性,
  • SKIP_FRAMES:跳過 StackMapTable 屬性
    前面有提到 ClassVisitor 是一個抽象類,我們可以選擇關心的事件進行處理,比如例子中的覆寫了 visitField 和 visitMethod 方法,僅對字段和方法進行處理,對于不感興趣的事件可以選擇不覆寫或者返回 null 值,這樣 ASM 就知道可以跳過對應的解析事件了。

使用 Tree Api 的方式也可以實現(xiàn)同樣的效果

byte[] bytes = getBytes();
ClassReader cr = new ClassReader(bytes);
ClassNode cn = new ClassNode();
cr.accept(cn, ClassReader.SKIP_DEBUG | ClassReader.SKIP_CODE);
List<FieldNode> fields = cn.fields;
for (int i = 0; i < fields.size(); i++) {
    FieldNode fieldNode = fields.get(i);
    System.out.println("field: " + fieldNode.name);
}
List<MethodNode> methods = cn.methods;
for (int i = 0; i < methods.size(); ++i) {
    MethodNode method = methods.get(i);
    System.out.println("method: " + method.name);
}
ClassWriter cw = new ClassWriter(0);
cr.accept(cn, 0);
byte[] bytesModified = cw.toByteArray();

新增一個字段

在實際字節(jié)碼轉換中,經常會需要給類新增一個字段存儲額外的信息,在 ASM 中給類新增一個字段非常簡單,以下面的 MyMain 類為例,使用 javac 編譯為 class 文件。

public class MyMain {
}

那么問題來了,在 ClassVisitor 的哪個方法里面進行添加字段的操作呢?由前面介紹的調用順序可知,visitField 調用時機只能在 visitInnerClass、visitField、visitMethod、visitEnd 這四種方法中選擇,又因為 visitInnerClass、visitField 不一定都會被調用到,且它們可能被調用多次,因此放在 visitEnd 方法中進行處理比較恰當。

使用下面的代碼可以給 MyMain 新增一個 String 類型的 xyz 字段。

byte[] bytes = FileUtils.readFileToByteArray(new File("./MyMain.class"));
ClassReader cr = new ClassReader(bytes);
ClassWriter cw = new ClassWriter(0);
ClassVisitor cv = new ClassVisitor(ASM5, cw) {
    @Override
    public void visitEnd() {
        super.visitEnd();
        FieldVisitor fv = cv.visitField(Opcodes.ACC_PUBLIC, "xyz", "Ljava/lang/String;", null, null);
        if (fv != null) fv.visitEnd();
    }
};
cr.accept(cv, ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG);
byte[] bytesModified = cw.toByteArray();
FileUtils.writeByteArrayToFile(new File("./MyMain2.class"), bytesModified);

使用 javap 查看 MyMain2 的字節(jié)碼,可以看到已經多了一個類型為String 的 xyz 變量了。

...
public java.lang.String xyz;
descriptor: Ljava/lang/String;
flags: ACC_PUBLIC
...

新增方法

在這個例子中,同樣使用 MyMain 類為例,給這個類新增一個 xyz 方法。

public void xyz(int a, String b) {
}

新增方法需要調用 visitMethod 方法,根據前面的調用順序來看,同 visitField 一樣,visitMethod 調用時機只能在 visitInnerClass、visitField、visitMethod、visitEnd 這四種方法中選擇,這里選擇 visitEnd 方法。

根據第一章的內容可以知道 xyz 方法的簽名為 (ILjava/lang/String;)V

byte[] bytes = FileUtils.readFileToByteArray(new File("./MyMain.class"));
ClassReader cr = new ClassReader(bytes);
ClassWriter cw = new ClassWriter(0);
ClassVisitor cv = new ClassVisitor(ASM5, cw) {
    @Override
    public void visitEnd() {
        super.visitEnd();
        MethodVisitor mv = cv.visitMethod(Opcodes.ACC_PUBLIC, "xyz", "(ILjava/lang/String;)V", null, null);
        if (mv != null) mv.visitEnd();
    }
};
cr.accept(cv, ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG);
byte[] bytesModified = cw.toByteArray();
FileUtils.writeByteArrayToFile(new File("./MyMain2.class"), bytesModified);

使用 javap 查看生成的 MyMain2 類,確認 xyz 方法已經生成:

...
public void xyz(int, java.lang.String);
descriptor: (ILjava/lang/String;)V
flags: ACC_PUBLIC
...

移除方法和字段

前面介紹了利用 ASM 給 class 文件新增方法和字段,接下來介紹如何刪掉方法和字段,假設有 MyMain 類代碼如下,下面介紹如何刪掉 abc 字段和 xyz 方法。

public class MyMain {
    private int abc = 0;
    private int def = 0;
    public void foo() {
    }
    public int xyz(int a, String b) {
        return 0;
    }
}

如果如果仔細觀察 ClassVisitor 類的 visit 方法,會發(fā)現(xiàn)visitField、visitMethod 等方法是有返回值的,如果這些方法直接返回 null,效果是這些字段、方法從類中被移除。

byte[] bytes = FileUtils.readFileToByteArray(new File("./MyMain.class"));
ClassReader cr = new ClassReader(bytes);
ClassWriter cw = new ClassWriter(0);
ClassVisitor cv = new ClassVisitor(ASM5, cw) {
    @Override
    public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) {
        if ("abc".equals(name)) {
            return null;
        }
        return super.visitField(access, name, desc, signature, value);
    }
    @Override
    public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
        if ("xyz".equals(name)) {
            return null;
        }
        return super.visitMethod(access, name, desc, signature, exceptions);
    }
};
cr.accept(cv, ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG);
byte[] bytesModified = cw.toByteArray();
FileUtils.writeByteArrayToFile(new File("./MyMain2.class"), bytesModified);

同樣使用 javap 查看 MyMain2 的字節(jié)碼,可以看到 abc 字段和 xyz 方法已經被移除,只剩下 def 字段和 foo 方法了。

小結

這篇文章我們主要講解了 ASM 字節(jié)碼操作框架,一起來回顧一下要點:

  • 第一,ASM 是一個久經考驗的工業(yè)級字節(jié)碼操作框架。
  • 第二,ASM 的三個核心類 ClassReader、ClassVisitor、ClassWriter。ClassReader 對象創(chuàng)建之后,調用 ClassReader.accept() 方法,傳入一個 ClassVisitor 對象。ClassVisitor 在解析字節(jié)碼的過程中遇到不同的節(jié)點時會調用不同的 visit() 方法。ClassWriter 負責把最終修改的字節(jié)碼以 byte 數(shù)組的形式返回。

以上就是Java字節(jié)碼操縱框架ASM圖文實例詳解的詳細內容,更多關于Java 字節(jié)碼操縱框架ASM的資料請關注腳本之家其它相關文章!

相關文章

  • Java將RTF轉換為PDF格式的實現(xiàn)

    Java將RTF轉換為PDF格式的實現(xiàn)

    本文主要介紹了Java將RTF轉換為PDF格式的實現(xiàn),文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2022-07-07
  • Mybatis執(zhí)行SQL命令的流程分析

    Mybatis執(zhí)行SQL命令的流程分析

    這篇文章主要介紹了Mybatis執(zhí)行SQL命令的流程分析,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2023-04-04
  • Java實現(xiàn)excel動態(tài)列導出的示例代碼

    Java實現(xiàn)excel動態(tài)列導出的示例代碼

    這篇文章主要為大家詳細介紹了如何使用Java實現(xiàn)excel動態(tài)列導出,文中的示例代碼講解詳細,具有一定的借鑒價值,感興趣的小伙伴可以跟隨小編一起學習一下
    2024-03-03
  • Java 中的 @SneakyThrows 注解的使用方法(簡化異常處理的利與弊)

    Java 中的 @SneakyThrows 注解的使用方法(簡化異常處理的利與弊)

    @SneakyThrows是Lombok提供的一個注解,用于簡化Java方法中的異常處理,特別是對于檢查型異常,它允許方法拋出異常而不必顯式聲明或捕獲這些異常,本文介紹Java 中的 @SneakyThrows 注解的使用方法,感興趣的朋友一起看看吧
    2025-03-03
  • 詳解在Spring中如何使用AspectJ來實現(xiàn)AOP

    詳解在Spring中如何使用AspectJ來實現(xiàn)AOP

    這篇文章主要介紹了詳解在Spring中如何使用AspectJ來實現(xiàn)AOP,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2018-06-06
  • java中XML的使用全過程

    java中XML的使用全過程

    這篇文章主要介紹了java中XML的使用全過程,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2025-05-05
  • Java 得到集合中所有子集

    Java 得到集合中所有子集

    本文主要介紹了Java 得到集合中所有子集的方法。具有很好的參考價值,下面跟著小編一起來看下吧
    2017-02-02
  • Java數(shù)據結構之散列表詳解

    Java數(shù)據結構之散列表詳解

    散列表(Hash table,也叫哈希表),是根據關鍵碼值(Key value)而直接進行訪問的數(shù)據結構。本文將為大家具體介紹一下散列表的原理及其代碼實現(xiàn)
    2022-01-01
  • 詳解springSecurity之java配置篇

    詳解springSecurity之java配置篇

    這篇文章主要介紹了詳解springSecurity之java配置篇,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2020-07-07
  • SpringBoot配置mybatis駝峰命名規(guī)則自動轉換的實現(xiàn)

    SpringBoot配置mybatis駝峰命名規(guī)則自動轉換的實現(xiàn)

    這篇文章主要介紹了SpringBoot配置mybatis駝峰命名規(guī)則自動轉換的實現(xiàn),文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2020-09-09

最新評論

陆川县| 汉沽区| 保亭| 无极县| 阿坝| 平和县| 公安县| 游戏| 香格里拉县| 凤山市| 合山市| 贺兰县| 宣威市| 吴桥县| 北辰区| 台山市| 镇平县| 炉霍县| 兴城市| 台北县| 南城县| 武隆县| 辰溪县| 子长县| 永定县| 武夷山市| 南皮县| 霞浦县| 元朗区| 承德市| 赣榆县| 皮山县| 祁门县| 宽甸| 富源县| 永德县| 呼图壁县| 大化| 邯郸县| 兖州市| 南平市|