欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

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

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

引言

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

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

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

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

  • 架構(gòu)設(shè)計(jì)精巧,使用方便。
  • 更新速度快,支持最新的 Java 版本
  • 速度非???,在動(dòng)態(tài)代理 class 的生成和 class 的轉(zhuǎn)換時(shí),盡可能確保運(yùn)行中的應(yīng)用不會(huì)被 ASM 拖慢
  • 非??煽俊⒕媒?jīng)考驗(yàn),已經(jīng)有很多著名的開源框架都在使用,例如 cglib,、mybatis、fastjson
    其它字節(jié)碼操作框架在操作字節(jié)碼的過(guò)程中生成大量的中間類和對(duì)象,耗費(fèi)大量的內(nèi)存且運(yùn)行緩慢,ASM 使用了訪問(wèn)者(Visitor)設(shè)計(jì)模式,避免了創(chuàng)建和消耗大量的中間變量。

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

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

DOM 解析方式則會(huì)將整個(gè) XML 作為類似樹結(jié)構(gòu)的方式讀入內(nèi)存中以便操作及解析,ASM 的 Tree API 類似于這種方式。

以下面的 XML 文件為例:

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

對(duì)應(yīng)的 SAX 和 DOM 解析方式的如下圖所示:

ASM 核心類介紹

ClassReader

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

ClassVisitor

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

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

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

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

ClassWriter

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

這三個(gè)核心類的關(guān)系如下圖

一個(gè)最簡(jiǎn)單的用法如下面的代碼所示:

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 負(fù)責(zé)讀取類文件字節(jié)數(shù)組,accept 調(diào)用之后 ClassReader 會(huì)把解析字節(jié)碼過(guò)程的事件源源不斷的通知給 ClassVisitor 對(duì)象調(diào)用不同的 visit 方法,ClassVisitor 可以在這些 visit 方法中對(duì)字節(jié)碼進(jìn)行修改,ClassWriter 可以生成最終修改過(guò)的自己字節(jié)碼。

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

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

訪問(wèn)類的方法和字段

ASM 的 visitor 設(shè)計(jì)模式可以很方便的用來(lái)訪問(wèn)類文件中我們感興趣的部分,比如類文件的字段和方法列表,有下面的類:

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

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

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);

輸出結(jié)果:

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

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

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

使用 Tree Api 的方式也可以實(shí)現(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();

新增一個(gè)字段

在實(shí)際字節(jié)碼轉(zhuǎn)換中,經(jīng)常會(huì)需要給類新增一個(gè)字段存儲(chǔ)額外的信息,在 ASM 中給類新增一個(gè)字段非常簡(jiǎn)單,以下面的 MyMain 類為例,使用 javac 編譯為 class 文件。

public class MyMain {
}

那么問(wèn)題來(lái)了,在 ClassVisitor 的哪個(gè)方法里面進(jìn)行添加字段的操作呢?由前面介紹的調(diào)用順序可知,visitField 調(diào)用時(shí)機(jī)只能在 visitInnerClass、visitField、visitMethod、visitEnd 這四種方法中選擇,又因?yàn)?visitInnerClass、visitField 不一定都會(huì)被調(diào)用到,且它們可能被調(diào)用多次,因此放在 visitEnd 方法中進(jìn)行處理比較恰當(dāng)。

使用下面的代碼可以給 MyMain 新增一個(gè) 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é)碼,可以看到已經(jīng)多了一個(gè)類型為String 的 xyz 變量了。

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

新增方法

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

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

新增方法需要調(diào)用 visitMethod 方法,根據(jù)前面的調(diào)用順序來(lái)看,同 visitField 一樣,visitMethod 調(diào)用時(shí)機(jī)只能在 visitInnerClass、visitField、visitMethod、visitEnd 這四種方法中選擇,這里選擇 visitEnd 方法。

根據(jù)第一章的內(nèi)容可以知道 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 類,確認(rèn) xyz 方法已經(jīng)生成:

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

移除方法和字段

前面介紹了利用 ASM 給 class 文件新增方法和字段,接下來(lái)介紹如何刪掉方法和字段,假設(shè)有 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;
    }
}

如果如果仔細(xì)觀察 ClassVisitor 類的 visit 方法,會(huì)發(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 方法已經(jīng)被移除,只剩下 def 字段和 foo 方法了。

小結(jié)

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

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

以上就是Java字節(jié)碼操縱框架ASM圖文實(shí)例詳解的詳細(xì)內(nèi)容,更多關(guān)于Java 字節(jié)碼操縱框架ASM的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • 關(guān)于java獲取新浪天氣示例

    關(guān)于java獲取新浪天氣示例

    這篇文章主要介紹了關(guān)于java獲取新浪天氣示例,新浪的接口可以直接通過(guò)城市名字查詢天氣所以這里使用新浪API,,需要的朋友可以參考下
    2023-04-04
  • java不使用第三變量交換兩個(gè)變量的值

    java不使用第三變量交換兩個(gè)變量的值

    這篇文章主要介紹了java不使用第三變量交換兩個(gè)變量的值問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2024-04-04
  • Java實(shí)現(xiàn)圖形化界面的日歷

    Java實(shí)現(xiàn)圖形化界面的日歷

    這篇文章主要介紹了Java實(shí)現(xiàn)圖形化界面的日歷,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2022-06-06
  • MyBatis查詢數(shù)據(jù),賦值給List集合時(shí),數(shù)據(jù)缺少的問(wèn)題及解決

    MyBatis查詢數(shù)據(jù),賦值給List集合時(shí),數(shù)據(jù)缺少的問(wèn)題及解決

    這篇文章主要介紹了MyBatis查詢數(shù)據(jù),賦值給List集合時(shí),數(shù)據(jù)缺少的問(wèn)題及解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-01-01
  • Java 如何安全的發(fā)布對(duì)象

    Java 如何安全的發(fā)布對(duì)象

    這篇文章主要介紹了Java 如何安全的發(fā)布對(duì)象,幫助大家更好的理解和使用Java,感興趣的朋友可以了解下
    2021-01-01
  • MybatisPlus查詢數(shù)據(jù)日期格式化問(wèn)題解決方法

    MybatisPlus查詢數(shù)據(jù)日期格式化問(wèn)題解決方法

    MyBatisPlus是MyBatis的增強(qiáng)工具,支持常規(guī)的CRUD操作以及復(fù)雜的聯(lián)表查詢等功能,這篇文章主要給大家介紹了關(guān)于MybatisPlus查詢數(shù)據(jù)日期格式化問(wèn)題的解決方法,需要的朋友可以參考下
    2023-10-10
  • Spring Boot項(xiàng)目集成UidGenerato的方法步驟

    Spring Boot項(xiàng)目集成UidGenerato的方法步驟

    這篇文章主要介紹了Spring Boot項(xiàng)目集成UidGenerato的方法步驟,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2020-12-12
  • SpringBoot利用filter實(shí)現(xiàn)xss防御功能

    SpringBoot利用filter實(shí)現(xiàn)xss防御功能

    Cross-Site?Scripting(跨站腳本攻擊)簡(jiǎn)稱?XSS,是一種代碼注入攻擊,攻擊者通過(guò)在目標(biāo)網(wǎng)站上注入惡意腳本,使之在用戶的瀏覽器上運(yùn)行,利用這些惡意腳本,攻擊者可獲取用戶的敏感信息,本文給大家介紹了SpringBoot利用filter實(shí)現(xiàn)xss防御功能,需要的朋友可以參考下
    2024-09-09
  • 基于JWT實(shí)現(xiàn)SSO單點(diǎn)登錄流程圖解

    基于JWT實(shí)現(xiàn)SSO單點(diǎn)登錄流程圖解

    這篇文章主要介紹了基于JWT實(shí)現(xiàn)SSO單點(diǎn)登錄流程圖解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2020-07-07
  • JNDI,JTA和JMS簡(jiǎn)介

    JNDI,JTA和JMS簡(jiǎn)介

    這篇文章主要介紹了JNDI,JTA和JMS的相關(guān)內(nèi)容,包括中文釋義,概念解釋等,需要的朋友可以了解下。
    2017-09-09

最新評(píng)論