動態

詳情 返回 返回

聊聊為什麼java會有這麼多的字節碼改寫方式(jdk/cglib/asm/javasist)? - 動態 詳情

字節碼改寫方式的多樣性主要源於不同的使用場景、需求和設計哲學。

以下兩個方面是比較重要的原因:

https://www.zhihu.com/question/7501915796/answer/61918375697

  • 學習和使用成本
  • 精細化控制能力與性能需求

首先説一下jdk代理,它是在2000年5月發佈的jdk 1.3中引入的,這裏對jdk代理就不做過於詳細的介紹了,它的出現主要是為了提供一種靈活的方法來實現面向切面編程(AOP)遠程方法調用(RMI)、事務管理和其他需要在運行時動態增強對象功能的應用場景,説到AOP大家應該耳熟能詳了吧,接下來是一個經典的八股問題——為什麼有了jdk代理還要有cglib代理?

公佈下面試標準答案: 因為JDK動態代理不能直接代理類,而只能代理接口,這限制了其在一些情況下的使用靈活性,尤其是當需要代理沒有接口的第三方類時。

但你想過為什麼jdk動態代理為什麼必須要基於接口嗎?感興趣的小夥伴可以移步我這篇回答

https://www.zhihu.com/question/634937642/answer/3329082357

有點跑題,我們接着聊一下cglib代理, 它的出現就是為了解決jdk代理的一些問題比如無法代理沒有接口/final的類, 這使得它變得無比強大,對要代理的類沒有任何限制這一條是它成功的主要因素,而這都是依賴於asm,asm自2002年(https://asm.ow2.io/versions.html)正式誕生,伴隨了java在世界上的蓬勃發展,是字節碼改寫的經典之作(https://gitlab.ow2.org/asm/asm)。

在jvm世界中大約有200條左右的指令。這些指令包括各種操作,用於信息加載、存儲、算術計算、類型轉換、對象創建、調用方法、控制流管理和異常處理等,是整個java世界的基石,但理解它們需要對JVM工作機制以及java語言特性有深入瞭解,這對於絕大多數人來説太困難了。

asm庫提供了一套API,使得開發者可以以更高效和結構化的方式構建、修改和分析java字節碼,避免開發者直接處理字節碼的複雜性。從某種角度來看可以説asm是對jvm指令的一種抽象,在字節碼的世界裏asm幾乎無往不利,但強大的代價就是它還是太難了——它的學習和使用成本還是太過高昂,使用者還是要去了解jvm的指令集學習自己需要的指令。這對於入門人員甚至普通開發者來説簡直就是一場噩夢,而javasist(https://github.com/jboss-javassist/javassist)無疑是在降低使用門檻這條路上走的最遠的,它使開發者能夠以接近java源碼的方式來操作字節碼。它本是為jboss的aop功能而開源的,但逐漸成為了很多對字節碼指令沒那麼熟悉的人做字節碼改寫時的首選,除了mybatis,還有阿里著名的transmittable-thread-localdubbo都是使用此種方式,如果你想做簡單的字節碼改寫它會是一個不錯的選擇。

以下為transmittable-thread-local節選代碼

private boolean updateBeforeAndAfterExecuteMethodOfExecutorSubclass(@NonNull final CtClass clazz) throws NotFoundException, CannotCompileException {
        final CtClass runnableClass = clazz.getClassPool().get(RUNNABLE_CLASS_NAME);
        final CtClass threadClass = clazz.getClassPool().get("java.lang.Thread");
        final CtClass throwableClass = clazz.getClassPool().get("java.lang.Throwable");
        boolean modified = false;

        try {
            final CtMethod beforeExecute = clazz.getDeclaredMethod("beforeExecute", new CtClass[]{threadClass, runnableClass});
            // unwrap runnable if IsAutoWrapper
            String code = "$2 = com.alibaba.ttl.threadpool.agent.internal.transformlet.impl.Utils.doUnwrapIfIsAutoWrapper($2);";
            logger.info("insert code before method " + signatureOfMethod(beforeExecute) + " of class " +
                beforeExecute.getDeclaringClass().getName() + ": " + code);
            beforeExecute.insertBefore(code);
            modified = true;
        } catch (NotFoundException e) {
            // clazz does not override beforeExecute method, do nothing.
        }

        try {
            final CtMethod afterExecute = clazz.getDeclaredMethod("afterExecute", new CtClass[]{runnableClass, throwableClass});
            // unwrap runnable if IsAutoWrapper
            String code = "$1 = com.alibaba.ttl.threadpool.agent.internal.transformlet.impl.Utils.doUnwrapIfIsAutoWrapper($1);";
            logger.info("insert code before method " + signatureOfMethod(afterExecute) + " of class " +
                afterExecute.getDeclaringClass().getName() + ": " + code);
            afterExecute.insertBefore(code);
            modified = true;
        } catch (NotFoundException e) {
            // clazz does not override afterExecute method, do nothing.
        }

        return modified;
    }

但軟件行業有個定律:抽象在軟件開發中的確涉及信息選擇和隱藏的過程,而這種信息的隱藏可能會被認為是對底層細節描述能力的某種“喪失”

同樣的javasist作為一個更高級的抽象層,這意味着會有一些性能開銷,這在需要進行大量字節碼操作的場合可能成為瓶頸,同時對於一些非常細緻的字節碼操作或者需要很細粒度控制的場景來説,它不夠靈活,因此它更適合於簡單或常見的字節碼操作。

而除了上述提到的一些,還有很多非常優秀的字節碼改寫框架:

  • bytebuddy(https://bytebuddy.net/#/),鏈路追蹤系統中非常出名的skywalking就是基於它去實現的,此外Jackson、Hibernate、Mockito等知名框架也都使用了bytebuddy
  • jvm-sandbox(https://github.com/alibaba/jvm-sandbox) ,arthas的前身greys)的作者基於greys沉澱出來的作品,底層是基於asm的(大神對於asm的理解和使用功力非常深),它出現是為了做jvm上的spring aop,強烈建議如果你要做一個aop類型(不需要對行間代碼進行修改僅在方法進入退出時執行某些操作)的agent可以使用此框架
  • bytekit(https://github.com/alibaba/bytekit),是arthas的開發人員基於arthas抽象而來,底層也是asm

字節碼改寫的方式有很多,不同的場景也有不同的選擇,javasist開箱即用,asm可以鍛鍊你的java內功,趕進度時你所熟悉的方式則是最合適的。

ps:在java世界裏,字節碼改寫 + 反射可以讓你變成“上帝”,你可以完成任何你想做的事情!

原文地址:https://pebble-skateboard-d46.notion.site/java-jdk-cglib-asm-javasist-16534675225b802e897cc4f7c3438b78

user avatar xuxueli 頭像 u_17021563 頭像 motianlun_5d0766992e67a 頭像 lu_lu 頭像 tuhooo 頭像 witt7 頭像 lindsay_bubble 頭像 guaiguaidedoujiang_cmg5bc 頭像 zhao_59106344e870e 頭像
點贊 9 用戶, 點贊了這篇動態!
點贊

Add a new 評論

Some HTML is okay.