字節碼編程,Javassist篇三《使用Javassist在運行時重新加載類「替換原方法輸出不一樣的結果」》


作者:小傅哥
博客:https://bugstack.cn

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

一、前言

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

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

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

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

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

關於字節編程中所有涉及的代碼,都可以通過關注公衆號bugstack蟲洞棧,回覆:源碼,進行獲取。

二、開發環境

  1. JDK 1.8.0
  2. jdk1.8.0_161\lib\tools.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毛錢給了王牌工程師小傅哥

來自謝飛機的收入

4. 效果演示

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

六、總結

  1. 沒得辦法,即使再好的技術不加點段子也沒人看。只能坑我兄弟飛機了!德萊聯盟,王牌工程師,申請出
  2. 關於熱加載修改類的操作,在實際場景中還是蠻多的,但一般都是比較苛刻的場景訴求。在平時開發中還是比較少遇到的,並且CRUD開發不會遇到。
  3. JavassistASM 這樣的字節碼操作封裝起來提供的API確實很好操作,在一些場景下也不需要考慮 JVM 中局部變量和操作數棧。但如果需要更高的性能,可以考慮使用 ASM
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章