方案設計:基於IDEA插件開發和位元組碼插樁技術,實現研發交付質量自動分析


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

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

一、前言

如何保證代碼質量?

業務提需求,產品定方案,研發做實現,測試驗流程。四種角色的相互配合是確保一個需求上線的必備條件。在整個需求的交付質量級別劃分中,研發與測試是非常重的一環,如果研發提測的代碼質量不高,就會出現不同級別的修BUG、返工甚至重做的風險。

那麼,怎麼來提高代碼質量呢?一般我們都會要求研發在開發代碼的過程中編寫單元測試,驗證自己的代碼邏輯。如果最終單元測試覆蓋度不足,可以由測試拒絕研發提測。

但是,整個需求實現的代碼是在全部開發完成後提測的,也就是臨近上線的最後一環,大家才知道某個研發的某個功能域的實現是否具備提測條件。如果這個時候代碼質量不高,那麼接下來就是項目風險的時候。壓測試時間調上線時間,總之有病拖着最後成大病了!

當然,你可以在項目開發期間定期排查代碼,或者在日會進度反饋等等手段。可這樣需要耗費大量時間1拖1的開發排查方式很難滿足複雜流程的較大型項目開發,而且對於項目風險把控也是不可預估的。

所以,我們希望採集研發在開發過程中的執行動作,把風險判斷提前。實際操作舉例就是,當你開發完成一個接口,開始測試運行時,我們的插件就可以採集到這個接口的全部信息,包括:接口名稱、入參類型和內容、出參類型和內容、異常信息、調用關係鏈等。而再把這些信息匯總提交到服務端,生成本次需求代碼分支下的全部接口動作,以及各系統間的關係鏈路,並附帶隨時生成最新的接口文檔和一鍵測試驗證功能。後期測試人員介入時就可以參考研發在編碼過程中的全部測試用例,也可以查看整個功能的覆蓋程度,此外測試人員測試過程中的數據也會被保留下。現在擁有這些數據信息以後,就可以完整的生成一套研發測試質量交付全覽圖,讓整個工程開發交付質量評估透明化。

接下來我們就按照以上的描述性內容,實踐開發一個案例體會下。走起!

二、技術實現準備

  1. 位元組碼插樁,因為我們需要採集到接口執行信息,那麼就需要使用位元組碼插樁組件給接口方法增強。這個實現有點類似谷歌的Dapper,大規模分佈式架構的非入侵監控。只不過我們需要採集的描述性信息更多。關於位元組碼插樁,可以了解ASM、Javassist、Byte-Buddy,它們都可以做此項工作。
  2. IDEA 插件開發,因為我們需要在研發人員開發過程中進行採集,也不破壞研發的操作習慣。那麼最好的方式就是嵌入到啟動運行中,只要在開發過程中有運行代碼的動作,就採集相應的接口信息。
  3. 最後就是數據的傳輸和處理,傳輸可以使用MQ或者直接用Netty。而處理數據的過程會相對比較複雜,在這個過程需要分析出有價值的數據,同類的數據,合併一條執行鏈路的數據,以及生成相關的接口文檔和工程服務地圖。

三、對位元組碼插樁

這裡我們使用的位元組碼插樁組件是 Byte-buddy,它是一個代碼生成和操作庫,用於在 Java 應用程序運行時創建和修改 Java 類,而無需編譯器的幫助。除了 Java 類庫附帶的代碼生成實用程序外,Byte Buddy 還允許創建任意類,並且不限於實現用於創建運行時代理的接口。此外,Byte Buddy 提供了一種方便的 API,可以使用 Java 代理或在構建過程中手動更改類。

  • 無需理解位元組碼指令,即可使用簡單的 API 就能很容易操作位元組碼,控制類和方法。
  • 已支持Java 11,庫輕量,僅取決於Java位元組代碼解析器庫ASM的訪問者API,它本身不需要任何其他依賴項。
  • 比起JDK動態代理、cglib、Javassist,Byte Buddy在性能上具有一定的優勢。

1. 方法入口

public static void premain(String agentArgs, Instrumentation inst) {
    AgentBuilder.Transformer transformer = (builder, typeDescription, classLoader, javaModule) -> {
        return builder
                .method(ElementMatchers.any()) // 攔截任意方法
                .intercept(MethodDelegation.to(MonitorMethod.class));
    };
    new AgentBuilder
            .Default()
            .type(ElementMatchers.nameStartsWith(agentArgs)) 
            .transform(transformer)
            .installOn(inst);
}

如果你接觸過 Javaagent 開發,那麼對於 premain 會比較熟悉。如果不清楚你可以把它理解為,它是程序啟動的時的方法入口,你可以從這個入口中攔截到你需要的方法,之後對它進行位元組碼增強。其實也就是動態寫代碼,在方法中添加你的代碼,來收集方法信息。

2. 採集信息

@RuntimeType
public static Object intercept(@Origin Method method, @SuperCall Callable<?> callable, @AllArguments Object[] args) throws Exception {
    long start = System.currentTimeMillis();
    Object resObj = null;
    try {
        resObj = callable.call();
        return resObj;
    } finally {
        System.out.println("方法名稱:" + method.getName());
        System.out.println("入參個數:" + method.getParameterCount());
        for (int i = 0; i < method.getParameterCount(); i++) {
            System.out.println("入參 Idx:" + (i + 1) + " 類型:" + method.getParameterTypes()[i].getTypeName() + " 內容:" + args[i]);
        }
        System.out.println("出參類型:" + method.getReturnType().getName());
        System.out.println("出參結果:" + resObj);
        System.out.println("方法耗時:" + (System.currentTimeMillis() - start) + "ms");
    }
}

這個就是使用 Byte-Buddy 可以採集的信息,你可以通過註解入參,獲取到一個方法的全部信息。方法名稱、入參個數、入參類型和內容、出參類型和結果以及還能計算方法執行耗時。

四、IDEA 插件開發

關於 IDEA 插件開發的知識內容較多,可以從GitHub搜索一些資料和查閱官方文檔://plugins.jetbrains.com/docs/intellij/gradle-build-system.html?from=jetbrains.org

此處演示案例關於插件開發的內容比較簡單,主要是繼承 com.intellij.execution.impl.DefaultJavaProgramRunner,Override doExecute 方法,添加自己需要的內容即可。

這部分添加的內容核心就是在程序啟動時添加我們的位元組碼插樁程序,如下:

@Override
protected RunContentDescriptor doExecute(@NotNull RunProfileState state, @NotNull ExecutionEnvironment env) throws ExecutionException {
    JavaParameters parameters = ((JavaCommandLine) state).getJavaParameters();
    // 信息獲取
    PsiFile psiFile = env.getDataContext().getData(LangDataKeys.PSI_FILE);
    String packageName = ((PsiJavaFileImpl) psiFile).getPackageName();
    // 添加位元組碼插裝
    ParametersList parametersList = parameters.getVMParametersList();
    parametersList.add("-javaagent:" + this.getClass().getResource("/").getPath().substring(1) + "ProjectProbe.jar=" + packageName);
    return super.doExecute(state, env);
}

此處最核心的就是 -javaagentProjectProbe.jar 工程探針程序的Jar包加載進去。其他的就是一些關於 PsiFile API 的使用,感興趣可以閱讀官方文檔中的介紹。

五、效果演示

安裝插件

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-z3Smwntr-1614045501972)(//bugstack.cn/assets/images/2020/all-23-3.png)]

  • 安裝插件就和我們正常安裝一樣,不過目前這個插件在開發階段,所以需要本地安裝。

運行效果

  • 上圖就是運行效果的案例演示,我們把運行時接口的信息完整的輸出到控制台。
  • 在實際使用的過程中,會把這部分信息傳回服務端,由服務端分析處理後,展示在頁面上。

六、總結

  • 基於IDEA插件和位元組碼插樁技術,能做的功能實現還有很多。本文僅僅是其中一種研發到測試痛點的解決方案,如果感興趣可以一起深入研究。
  • 當你看到這樣的案例以後,希望能給你的是並不一定所有的技術點都是為了面試造火箭對話的。當你真的把它落地以後,才會懂的自己需要很多知識。
  • 本文沒有太過多的介紹插件開發和位元組碼技術,如果對位元組碼編程感興趣,可以在公眾號:bugstack蟲洞棧,回復位元組碼編程。全書11萬7千字,20個章節涵蓋三個位元組碼框架(ASM、Javassist、Byte-budy)和JavaAgent使用並附帶整套案例源碼!

七、系列推薦