使用 Javassist 在執行時重新載入類「替換原方法輸出不一樣的結果」| 位元組碼程式設計 · Javassist 03

小傅哥發表於2020-05-12


作者:小傅哥
部落格:https://bugstack.cn

沉澱、分享、成長,讓自己和他人都能有所收穫!

一、前言

通過前面兩篇 javassist 的基本內容,大體介紹了;類池(ClassPool)、類(CtClass)、屬性(CtField)、方法(CtMethod),的使用方式,並通過建立不同型別的入參出參方法,基本可以掌握如何使用這樣的程式碼結構進行位元組碼程式設計。

那麼,今天我們嘗試使用 javassist 去修改一個正在執行中的類裡面的方法內容。也就是在執行時重新載入類資訊

可能在你平時的 CRUD 開發中並沒有想到過這樣的 燒操作,但它卻有很多的應用場景在使用,例如;

  1. 熱部署常用在生產環境中,主要由於這樣的系統不能頻繁啟停且啟動耗時較長的應用。
  2. 另外一些元件化風控模型包,給外部使用。當模型包進行升級時並不需要外部重新部署,甚至不需要讓你知道升級了。
  3. 再者會用於開發、除錯中,可以非常有效的提升編碼效率,解放碼農的右手左手

人的大腦很難創造未知的事物,所以需要學習。請多看小傅哥的碼文,少搞CRUD

關於位元組程式設計中所有涉及的程式碼,都可以通過關注公眾號bugstack蟲洞棧,回覆:原始碼,進行獲取。

二、開發環境

  1. JDK 1.8.0
  2. jdk1.8.0_161libtools.jar - 需要使用到 jdi
  3. javassist 3.12.1.GA

三、案例目標

為了讓案例目標更具色彩,我們模擬一個謝飛機老婆,通過系統查詢自己男朋友前女友數量危機 方法,需要緊急處理的過程。

為了保障家庭的和諧化解危機,我們通過動態重新載入類,將謝飛機前女友數量修改為0並返回。依次安定家庭和諧。最終謝飛機會給我錢,當做報酬

德萊聯盟,王牌工程師,申請出棧

讓謝飛機很慌的方法

public class ApiTest {

    public String queryGirlfriendCount(String boyfriendName) {
        return boyfriendName + "的前女友數量:" + (new Random().nextInt(10) + 1) + " 個";
    }

}

可預見的結果

你到底幾個前女友!!!
謝飛機的前女友數量:3 個
謝飛機的前女友數量:5 個
謝飛機的前女友數量:8 個

四、技術實現

1. HotSwapper 操作類熱載入

德萊聯盟,王牌工程師,申請出

德萊聯盟王牌工程師

/**
 * 公眾號:bugstack蟲洞棧
 * 部落格棧:https://bugstack.cn - 沉澱、分享、成長,讓自己和他人都能有所收穫!
 * 本專欄是小傅哥多年從事一線網際網路Java開發的學習歷程技術彙總,旨在為大家提供一個清晰詳細的學習教程,側重點更傾向編寫Java核心內容。如果能為您提供幫助,請給予支援(關注、點贊、分享)!
 */
public class GenerateClazzMethod {

    public static void main(String[] args) throws Exception {

        ApiTest apiTest = new ApiTest();
        System.out.println("你到底幾個前女友!!!");

              // 模擬謝飛機老婆一頓查詢
        new Thread(() -> {
            while (true){
                System.out.println(apiTest.queryGirlfriendCount("謝飛機"));
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();

        
        // 監聽 8000 埠,在啟動引數裡設定
        // java -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=8000
        HotSwapper hs = new HotSwapper(8000);

        ClassPool pool = ClassPool.getDefault();
        CtClass ctClass = pool.get(ApiTest.class.getName());

        // 獲取方法
        CtMethod ctMethod = ctClass.getDeclaredMethod("queryGirlfriendCount");
        // 重寫方法
        ctMethod.setBody("{ return $1 + \"的前女友數量:\" + (0L) + \" 個\"; }");

        // 載入新的類
        System.out.println(":: 執行HotSwapper熱插拔,修改謝飛機前女友數量為0個!");
        hs.reload(ApiTest.class.getName(), ctClass.toBytecode());

    }

}

2. 知識點講解

  1. 多執行緒模擬迴圈呼叫,這個方法會一直執行查詢。在後續修改類之後輸出的結果資訊會有不同。
  2. javassist.tools.HotSwapper,是 javassist 的包中提供的熱載入替換類操作。在執行時需要啟用 JPDA(Java平臺偵錯程式體系結構)。
  3. ctMethod.setBody,重寫方法的內容在上面兩個章節已經很清楚的描述了。$1 是獲取方法中的第一個入參,大括號{}裡是具體執行替換的方法體。
  4. 最後使用 hs.reload 執行熱載入替換操作,這裡的 ctClass.toBytecode() 獲取的是處理後類的位元組碼。

五、測試結果

1. 引入tools.jar

Open Module Settings,引入tools.jar

2. 配置-agentlib

-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=8000

VM options,配置-agentlib

3. 執行測試

Listening for transport dt_socket at address: 8000
你到底幾個前女友!!!
謝飛機的前女友數量:3 個
謝飛機的前女友數量:5 個
謝飛機的前女友數量:8 個
:: 執行HotSwapper熱插拔,修改謝飛機前女友數量為0個!
謝飛機的前女友數量:4 個
謝飛機的前女友數量:5 個
謝飛機的前女友數量:0 個
謝飛機的前女友數量:0 個
謝飛機的前女友數量:0 個
謝飛機的前女友數量:0 個
謝飛機的前女友數量:0 個
謝飛機的前女友數量:0 個
謝飛機的前女友數量:0 個
謝飛機的前女友數量:0 個
謝飛機的前女友數量:0 個
謝飛機的前女友數量:0 個
謝飛機的前女友數量:0 個
謝飛機的前女友數量:0 個
謝飛機的前女友數量:0 個
謝飛機的前女友數量:0 個
謝飛機的前女友數量:0 個
謝飛機的前女友數量:0 個
謝飛機的前女友數量:0 個
謝飛機的前女友數量:0 個
謝飛機的前女友數量:0 個
謝飛機的前女友數量:0 個
謝飛機的前女友數量:0 個
謝飛機的前女友數量:0 個
...

Process finished with exit code -1

看到前女友數量為 0 時,謝飛機露出了羞澀的微笑,並兌現了承諾,將4毛錢給了王牌工程師小傅哥

來自謝飛機的code4毛錢/code收入

4. 效果演示

熱載入救火,成功拿到4毛錢

六、總結

  1. 沒得辦法,即使再好的技術不加點段子也沒人看。只能坑我兄弟飛機了!德萊聯盟,王牌工程師,申請出
  2. 關於熱載入修改類的操作,在實際場景中還是蠻多的,但一般都是比較苛刻的場景訴求。在平時開發中還是比較少遇到的,並且CRUD開發不會遇到。
  3. JavassistASM 這樣的位元組碼操作封裝起來提供的API確實很好操作,在一些場景下也不需要考慮 JVM 中區域性變數和運算元棧。但如果需要更高的效能,可以考慮使用 ASM

相關文章