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

Java通過字節(jié)碼工具ASM實現(xiàn)類的動態(tài)增強

 更新時間:2025年12月12日 08:30:53   作者:@趙士杰  
ASM 是一個輕量級、高性能的 Java 字節(jié)碼操控框架,它基于字節(jié)碼指令集操作,能夠直接讀取、修改和生成 Java 字節(jié)碼文件,下面我們來看看如何通過ASM實現(xiàn)類的動態(tài)增強吧

一、什么是 ASM

ASM 是一個輕量級、高性能的 Java 字節(jié)碼操控框架,它基于字節(jié)碼指令集操作,能夠直接讀取、修改和生成 Java 字節(jié)碼文件(.class文件),是 Java 字節(jié)碼操作領(lǐng)域的核心工具之一。常見的開源框架如Spring、MyBatis等,都在底層使用ASM來實現(xiàn)核心功能(如Spring的AOP動態(tài)代理、MyBatis的Mapper接口動態(tài)實現(xiàn))。

ASM 的核心應(yīng)用場景可概括為:AOP、動態(tài)代理、類增強、代碼混淆與解密、熱部署,以及字節(jié)碼分析與驗證,覆蓋字節(jié)碼操控的核心需求。

二、ASM 依賴引入

使用 ASM 前需引入核心依賴,主要包含兩個模塊:

  • asm:提供基礎(chǔ)字節(jié)碼操作 API,是框架的核心骨架;
  • asm-commons:封裝了通用工具類(如基于訪問者模式的便捷實現(xiàn)),簡化開發(fā)。

Maven 依賴配置如下(以 9.5 版本為例,需與實際使用的 ASM 版本保持一致):

<dependency>
    <groupId>org.ow2.asm</groupId>
    <artifactId>asm</artifactId>
    <version>9.5</version>
</dependency>
<dependency>
    <groupId>org.ow2.asm</groupId>
    <artifactId>asm-commons</artifactId>
    <version>9.5</version>
</dependency>

三、ASM 核心組件

ASM 的核心是「解析 - 處理 - 生成」的流水線,通過一組核心類實現(xiàn),主要分為三大類:ClassReader(字節(jié)碼讀取器)ClassVisitor(字節(jié)碼處理器)、ClassWriter(字節(jié)碼生成器)。三者的協(xié)作流程如下:

讀?。–lassReader)→ 處理(ClassVisitor/MethodVisitor等)→ 生成(ClassWriter)

  • ClassReader 負(fù)責(zé)讀取.class文件,解析出類的元信息(類名、方法、字段等),并通過 “事件回調(diào)” 的方式傳遞給 ClassVisitor;
  • ClassVisitor 作為處理器,接收解析結(jié)果并執(zhí)行修改邏輯(如新增字段、修改方法體),其核心是通過重寫回調(diào)方法實現(xiàn)自定義處理;
  • ClassWriter 最終將處理后的類信息生成新的字節(jié)數(shù)組,新字節(jié)數(shù)組可寫入文件或直接通過類加載器加載,完成類的動態(tài)增強。

3.1 ClassReader 字節(jié)碼讀取器

ClassReader 是字節(jié)碼處理的起點,它能從字節(jié)數(shù)組、輸入流或類名讀取.class文件,并解析出類的基本信息(類名、父類、接口、字段、方法等)。

核心構(gòu)造方法如下:

// 從字節(jié)數(shù)組讀取
public ClassReader(byte[] classFile)
// 從輸入流讀取
 public ClassReader(InputStream is) throws IOException
// 從類名讀?。ㄍㄟ^類加載器)
public ClassReader(String className) throws IOException

解析后,需通過accept(ClassVisitor cv, int flags)方法綁定處理器(ClassVisitor ),例如:

ClassReader classReader = new ClassReader(inputStream);
// 綁定自定義ClassVisitor
classReader.accept(customClassVisitor, ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES);

ClassReader 僅負(fù)責(zé) “讀”,不參與修改,解析出的類結(jié)構(gòu)信息均以“事件回調(diào)”的形式傳遞給 ClassVisitor,后續(xù)會觸發(fā) ClassVisitor 的各類回調(diào)方法,由 ClassVisitor 處理具體邏輯。

3.2 ClassVisitor 字節(jié)碼處理器

ClassVisitor 是一個抽象類,定義了訪問類結(jié)構(gòu)各部分的回調(diào)方法,實際使用時必須繼承它并按需重寫回調(diào)方法,實現(xiàn)對類的修改。其核心回調(diào)方法如下:

// 訪問類頭信息(包含類修飾符、類名、父類名、接口名、簽名等)
void visit(int version, int access, String name, String signature, String superName, String[] interfaces);

// 訪問類的字段信息(包含修飾符、字段名、類型、簽名、初始值等)
FieldVisitor visitField(int access, String name, String descriptor, String signature, Object value);

// 訪問類的方法信息(包含修飾符、方法名、方法描述符、簽名、異常等)
MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions);

// 類信息訪問結(jié)束時觸發(fā)
void visitEnd();

當(dāng) ClassReader 完成字節(jié)碼解析后,會主動調(diào)用上述回調(diào)方法,將類的各部分信息逐一傳遞給 ClassVisitor,由 ClassVisitor 執(zhí)行具體的業(yè)務(wù)邏輯處理。為簡化開發(fā),ASM 提供了多個 ClassVisitor 的現(xiàn)成實現(xiàn)類,適配不同場景需求:

  • ClassWriter:生成字節(jié)碼(前文已介紹)。
  • ClassRemapper:用于重命名類名、字段名、方法名等。
  • AdviceAdapter(asm-commons模塊):簡化方法體的修改,可在方法進(jìn)入、退出時插入邏輯。

visitMethod()

visitMethod()ClassVisitor的核心回調(diào)方法之一,當(dāng)ClassReader解析類文件時,每解析到一個方法(包括普通方法、構(gòu)造器、靜態(tài)初始化塊),就會觸發(fā)一次 visitMethod() 調(diào)用。

在ASM 9.x 版本中,visitMethod() 的默認(rèn)實現(xiàn)如下:

public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
    return this.cv != null ? this.cv.visitMethod(access, name, descriptor, signature, exceptions) : null;
}

visitMethod() 的入?yún)⒈┞读水?dāng)前解析方法的核心元信息,各參數(shù)含義如下:

  • access:方法的訪問修飾符(如public、static、final),取值為 ASM 定義的常量(如 Opcodes.ACC_PUBLIC
  • name:方法名,(構(gòu)造器為,靜態(tài)初始化塊為)
  • desc:方法描述符,用于描述方法參數(shù)和返回值,ASM格式「(參數(shù)類型列表)返回值類型」
  • signature:方法的泛型簽名,描述方法的泛型類型信息
  • exceptions:方法聲明拋出的異常類名數(shù)組

visitMethod() 的返回值用于控制方法內(nèi)部字節(jié)碼的處理邏輯:

  • 若返回 MethodVisitor 實例,可通過該實例操控當(dāng)前方法內(nèi)部的所有字節(jié)碼指令(如方法調(diào)用、變量操作、算術(shù)運算等核心邏輯),實現(xiàn)字節(jié)碼的修改、增強或分析;
  • 如果返回null,表示跳過該方法的字節(jié)碼解析,不干預(yù)方法內(nèi)部邏輯。

visitField()

當(dāng) ClassReader 解析類文件字節(jié)碼時,每識別到一個字段定義(包括成員變量、靜態(tài)變量),都會觸發(fā)一次該方法調(diào)用 —— 其核心作用是暴露字段的完整元信息,并決定是否創(chuàng)建 FieldVisitor 來進(jìn)一步處理字段的注解、自定義屬性等細(xì)節(jié)。

默認(rèn)實現(xiàn)如下:

public FieldVisitor visitField(int access, String name, String descriptor, String signature, Object value) {
    return this.cv != null ? this.cv.visitField(access, name, descriptor, signature, value) : null;
}

方法參數(shù)如下:

  • access:字段的訪問修飾符標(biāo)識
  • name:字段名稱
  • descriptor:字段類型描述符
  • signature:字段的泛型簽名
  • value:字段的初始值

visitField() 若返回 FieldVisitor 則繼續(xù)處理字段 / 方法的細(xì)節(jié)(如注解、字節(jié)碼指令);若返回 null,則跳過當(dāng)前字段 / 方法。

3.3 MethodVisitor 方法字節(jié)碼處理器

當(dāng)ClassVisitor#visitMethod返回MethodVisitor實例時,即可對方法體內(nèi)部的字節(jié)碼指令進(jìn)行處理。MethodVisitor 提供了一系列回調(diào)方法,對應(yīng) JVM 字節(jié)碼指令的解析與生成:

方法名作用說明
visitCode()開始訪問方法體
visitEnd()結(jié)束訪問方法體
visitInsn()處理無操作數(shù)的字節(jié)碼指令(如返回、棧操作)
visitVarInsn()處理局部變量相關(guān)指令(加載 / 存儲)
visitMethodInsn()處理方法調(diào)用指令
visitFieldInsn()處理字段訪問指令(讀 / 寫字段)
visitLdcInsn()加載常量到操作數(shù)棧(如字符串、整數(shù)、Class 對象)
visitTypeInsn()處理類型相關(guān)指令(如新建對象、檢查類型)
visitAnnotation()處理方法上的注解(返回 AnnotationVisitor 進(jìn)一步解析注解內(nèi)容)

所有字節(jié)碼指令的處理必須在回調(diào)方法 visitCode() 之后、visitEnd() 之前觸發(fā)。

3.4 FieldVisitor 類字段處理器

FieldVisitor 是承接 ClassVisitor#visitField 的返回值,專門**處理類字段(成員變量)**的元信息(訪問修飾符、名稱、類型、注解等)。當(dāng) ClassReader 解析到類的字段定義時,會通過 FieldVisitor 的回調(diào)方法觸發(fā) “字段事件”。

FieldVisitor 的方法聚焦于字段的元信息與注解處理,常用方法如下:

方法名作用說明
visitAnnotation()處理字段上的普通注解
visitTypeAnnotation()處理字段的類型注解(泛型字段專屬)
visitAttribute()處理字段的自定義屬性(非標(biāo)準(zhǔn) JVM 屬性)
visitEnd()字段處理結(jié)束的回調(diào)

3.5 ClassWriter 字節(jié)碼生成器

ClassWriter 繼承自 ClassVisitor,內(nèi)部重寫了 ClassVisitor 的所有核心回調(diào)方法。

在典型的 ASM 鏈路(ClassReader → 自定義 ClassVisitor → ClassWriter)中,ClassWriter 是“最終的執(zhí)行者” —— 是字節(jié)碼處理的終點,負(fù)責(zé)將所有修改后的類信息轉(zhuǎn)換為符合 JVM 規(guī)范的字節(jié)數(shù)組。它既可以 “增量修改”(基于現(xiàn)有類解析結(jié)果補充 / 修改字節(jié)碼),也可以 “全新生成”(從零定義類的結(jié)構(gòu)、字段、方法)。

核心構(gòu)造方法如下:

// flags:生成選項,常用COMPUTE_FRAMES(自動計算棧幀,避免手動維護(hù))
public ClassWriter(int flags)
// 高效模式:復(fù)用ClassReader的常量池,減少內(nèi)存占用
public ClassWriter(ClassReader cr, int flags)

通過toByteArray()方法獲取最終的字節(jié)數(shù)組,例如:

byte[] modifiedClassBytes = classWriter.toByteArray(); // 生成可加載的.class字節(jié)碼

3.6 opcode 操作碼

.class文件是由一系列二進(jìn)制字節(jié)碼指令組成的數(shù)據(jù)流,其中的每個字節(jié)碼指令都由一個 1 字節(jié)的 opcode(數(shù)值)+ 可選的操作數(shù)組成,JVM 就是通過識別這些 opcode 數(shù)值來執(zhí)行對應(yīng)的操作(如創(chuàng)建對象、調(diào)用方法等)。

以創(chuàng)建 ArrayList 實例為例:

  • Java 源碼:new ArrayList();
  • 字節(jié)碼指令(助記符):NEW java/util/ArrayList
  • 底層 opcode 數(shù)值:187(十六進(jìn)制 0xBB

這里的 NEW 是 opcode 的助記符(便于人類理解的別名),對應(yīng)的數(shù)值 187(0xBB) 是 JVM 實際識別的指令標(biāo)識 ——JVM 讀取到該數(shù)值后就知道要創(chuàng)建指定類的實例。

ASM 為了讓開發(fā)者不用記憶 opcode 的數(shù)值(比如 187 對應(yīng) NEW),通過org.objectweb.asm.Opcodes接口定義了所有 opcode 的常量,避免開發(fā)者記憶原始數(shù)值。常用常量如下:

常量名數(shù)值含義典型場景
Opcodes.NEW187創(chuàng)建類實例(未調(diào)用構(gòu)造器)檢測是否 new 了目標(biāo)類
Opcodes.INVOKEVIRTUAL182調(diào)用實例方法(非靜態(tài) / 非私有)檢測是否調(diào)用目標(biāo)類的實例方法
Opcodes.INVOKESTATIC184調(diào)用靜態(tài)方法檢測是否調(diào)用目標(biāo)類的靜態(tài)方法
Opcodes.INVOKESPECIAL183調(diào)用構(gòu)造器、私有方法或父類方法檢測是否調(diào)用目標(biāo)類的構(gòu)造器
Opcodes.GETFIELD180讀取成員變量檢測是否訪問目標(biāo)類的靜態(tài)字段
Opcodes.PUTFIELD181寫入成員變量測是否修改目標(biāo)類的靜態(tài)字段
Opcodes.ASM9-ASM 9.x 版本標(biāo)識初始化 Visitor 時指定 API 版本

四、實戰(zhàn)案例:為類動態(tài)添加字段與訪問器

下面通過一個案例實踐 ASM 的核心用法:為User類添加private String address字段,并自動生成getAddress()setAddress()方法。

4.1 原始類定義

假設(shè)原始User類如下(僅含nameage字段):

public class User {
    private String name;
    private int age;

    public User(String name, int age) {
        this.name = name;
        this.age = age;
    }

    // 省略name和age的getter/setter
}

4.2 實現(xiàn)思路

  • 自定義ClassVisitor,在類解析結(jié)束時(visitEnd)添加新字段和方法;
  • 通過ClassReader讀取原始User.class字節(jié)碼;
  • 通過ClassWriter生成修改后的字節(jié)碼,并寫入文件。

4.3 代碼實現(xiàn)

自定義 ClassVisitor 添加字段

// 自定義ClassVisitor:負(fù)責(zé)添加字段和訪問器方法
class AddFieldClassVisitor extends ClassVisitor {
    private String className; // 記錄當(dāng)前類的全限定名(如com/example/User)

    public AddFieldClassVisitor(ClassVisitor cv) {
        super(Opcodes.ASM9, cv);
    }

    // 訪問類頭時記錄類名
    @Override
    public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
        super.visit(version, access, name, signature, superName, interfaces);
        this.className = name; // 保存類名,后續(xù)生成方法時需用到
    }

    // 類解析結(jié)束時,添加新字段和方法
    @Override
    public void visitEnd() {
        // 1. 添加private String address字段
        FieldVisitor addressField = cv.visitField(
                Opcodes.ACC_PRIVATE, // 訪問修飾符:private
                "address",           // 字段名
                "Ljava/lang/String;",// 類型描述符:String對應(yīng)Ljava/lang/String;
                null,                // 泛型簽名(非泛型字段為null)
                null                 // 初始值(無初始值為null)
        );
        addressField.visitEnd(); // 字段定義結(jié)束

        // 2. 添加getAddress()方法:public String getAddress() { return address; }
        MethodVisitor getMethod = cv.visitMethod(
                Opcodes.ACC_PUBLIC,    // 訪問修飾符:public
                "getAddress",          // 方法名
                "()Ljava/lang/String;",// 方法描述符:無參數(shù),返回String
                null,                  // 泛型簽名
                null                   // 拋出的異常(無異常為null)
        );
        getMethod.visitCode(); // 開始生成方法體
        // 加載this(局部變量表索引0)到操作數(shù)棧
        getMethod.visitVarInsn(Opcodes.ALOAD, 0);
        // 讀取this.address字段到操作數(shù)棧
        getMethod.visitFieldInsn(Opcodes.GETFIELD, className, "address", "Ljava/lang/String;");
        // 返回String類型(操作數(shù)棧頂為address的值)
        getMethod.visitInsn(Opcodes.ARETURN);
        // 設(shè)置操作數(shù)棧最大深度和局部變量表大小(自動計算時可省略,此處顯式指定)
        getMethod.visitMaxs(1, 1);
        getMethod.visitEnd(); // 方法定義結(jié)束

        // 3. 添加setAddress()方法:public void setAddress(String address) { this.address = address; }
        MethodVisitor setMethod = cv.visitMethod(
                Opcodes.ACC_PUBLIC,
                "setAddress",
                "(Ljava/lang/String;)V", // 方法描述符:參數(shù)為String,返回void
                null,
                null
        );

        setMethod.visitCode();
        setMethod.visitVarInsn(Opcodes.ALOAD, 0); // 加載this(索引0)
        setMethod.visitVarInsn(Opcodes.ALOAD, 1); // 加載參數(shù)address(索引1)
        // 將參數(shù)值賦值給this.address
        setMethod.visitFieldInsn(Opcodes.PUTFIELD, className, "address", "Ljava/lang/String;");
        setMethod.visitInsn(Opcodes.RETURN); // 無返回值

        // 指定局部變量(參數(shù))的名稱為address
        // 參數(shù)說明:name(變量名)、desc(類型描述符)、signature(泛型簽名)、start(作用域開始)、end(作用域結(jié)束)、index(局部變量表索引)
        setMethod.visitLocalVariable(
                "address",                // 參數(shù)名:address
                "Ljava/lang/String;",     // 類型描述符
                null,
                new Label(),              // 作用域開始(這里簡化用空Label,實際需對應(yīng)方法體的Label)
                new Label(),              // 作用域結(jié)束
                1                         // 參數(shù)在局部變量表的索引(this是0,第一個參數(shù)是1)
        );
        setMethod.visitMaxs(2, 2);
        setMethod.visitEnd();

        super.visitEnd(); // 確保父類邏輯執(zhí)行
    }
}

執(zhí)行字節(jié)碼修改

public class ASMFieldDemo {
    public static void main(String[] args) throws IOException {
        // 1. 讀取原始User類的字節(jié)碼(從類路徑加載)
        ClassReader classReader = new ClassReader("com.shijie.model.User");

        // 2. 創(chuàng)建ClassWriter,復(fù)用原始類的常量池并自動計算棧幀
        ClassWriter classWriter = new ClassWriter(classReader, ClassWriter.COMPUTE_FRAMES);

        // 3. 綁定自定義ClassVisitor(形成處理鏈:ClassReader → AddFieldClassVisitor → ClassWriter)
        AddFieldClassVisitor visitor = new AddFieldClassVisitor(classWriter);

        // 4. 開始解析并修改字節(jié)碼(跳過調(diào)試信息以提高效率)
        classReader.accept(visitor, ClassReader.SKIP_DEBUG);

        // 5. 獲取修改后的字節(jié)數(shù)組
        byte[] modifiedClassBytes = classWriter.toByteArray();

        // 6. 將新字節(jié)碼寫入文件(覆蓋原類或輸出到新路徑)
        File outputFile = new File("target/classes/com/shijie/model/User.class");
        try (FileOutputStream fos = new FileOutputStream(outputFile)) {
            fos.write(modifiedClassBytes);
        }

        System.out.println("字段與訪問器方法添加成功!");
    }
}

4.4 驗證結(jié)果

運行程序后,通過 IDEA 反編譯生成的User.class

以上就是Java通過字節(jié)碼工具ASM實現(xiàn)類的動態(tài)增強的詳細(xì)內(nèi)容,更多關(guān)于Java ASM字節(jié)碼的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • SWT(JFace) 簡易瀏覽器 制作實現(xiàn)代碼

    SWT(JFace) 簡易瀏覽器 制作實現(xiàn)代碼

    SWT(JFace) 簡易瀏覽器 制作實現(xiàn)代碼
    2009-06-06
  • java如何獲取用戶登錄ip、瀏覽器信息、SessionId

    java如何獲取用戶登錄ip、瀏覽器信息、SessionId

    這篇文章主要介紹了java如何獲取用戶登錄ip、瀏覽器信息、SessionId,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-11-11
  • 如何避免在Java?中使用雙括號初始化

    如何避免在Java?中使用雙括號初始化

    這篇文章主要介紹了如何避免在Java中使用雙括號初始化,本文通過實例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2023-07-07
  • Springboot整合freemarker和相應(yīng)的語法詳解

    Springboot整合freemarker和相應(yīng)的語法詳解

    FreeMarker是一款Spring官方推薦使用的模板引擎。接下來通過本文給大家介紹Springboot整合freemarker和相應(yīng)的語法,感興趣的朋友一起看看吧
    2021-09-09
  • java代碼實現(xiàn)空間切割

    java代碼實現(xiàn)空間切割

    大家好,本篇文章主要講的是java代碼實現(xiàn)空間切割,感興趣的同學(xué)趕快來看一看吧,對你有幫助的話記得收藏一下
    2022-01-01
  • SpringMVC日期類型接收空值異常問題解決方法

    SpringMVC日期類型接收空值異常問題解決方法

    這篇文章主要介紹了SpringMVC日期類型接收空值異常問題解決方法,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下
    2019-10-10
  • Lombok在idea中的使用教程

    Lombok在idea中的使用教程

    Lombok是一個可以通過簡單的注解形式,來幫助我們簡化消除一些必須有但顯得很臃腫(如果getter、setter方法)的Java代碼的工具,通過使用對應(yīng)的注解,可以在編譯源碼的時候生成對應(yīng)的方法,這篇文章主要介紹了Lombok在idea中的使用,需要的朋友可以參考下
    2023-03-03
  • SpringBoot對數(shù)據(jù)訪問層進(jìn)行單元測試的方法詳解

    SpringBoot對數(shù)據(jù)訪問層進(jìn)行單元測試的方法詳解

    我們公司作為一個面向銀行、金融機(jī)構(gòu)的TO B類企業(yè),頻繁遇到各個甲方爸爸提出的國產(chǎn)化數(shù)據(jù)庫的改造需求,包括OceanBase, TiDB,geldenDB等等,本文就介紹一種快高效、可復(fù)用的解決方案——對數(shù)據(jù)訪問層做單元測試,需要的朋友可以參考下
    2023-08-08
  • SpringBoot中的RestTemplate使用方法詳解

    SpringBoot中的RestTemplate使用方法詳解

    這篇文章主要介紹了SpringBoot中的RestTemplate使用方法詳解,為了方便使用,這里我封裝成一個工具類來靜態(tài)調(diào)用RestTemplate,基于SpringBoot2.4.2版本,需要的朋友可以參考下
    2024-01-01
  • Java如何從List中刪除元素的正確用法舉例

    Java如何從List中刪除元素的正確用法舉例

    這篇文章主要給大家介紹了關(guān)于Java如何從List中刪除元素的正確用法, 列表List是Java中的一種數(shù)據(jù)結(jié)構(gòu),存放按照添加順序的元素,是個有序的集合,需要的朋友可以參考下
    2023-07-07

最新評論

稻城县| 桃园市| 汕尾市| 无锡市| 六安市| 北票市| 马关县| 唐山市| 北流市| 新宁县| 宝坻区| 彭泽县| 宁南县| 枣强县| 丹江口市| 万安县| 江油市| 红原县| 武功县| 武安市| 泽库县| 大方县| 泾阳县| 宁乡县| 宜章县| 陆丰市| 广饶县| 方城县| 五大连池市| 大冶市| 闸北区| 天镇县| 自治县| 凌源市| 天峨县| 南澳县| 高密市| 思南县| 凤山市| 西丰县| 汝阳县|