天天看點

方案設計:基于IDEA插件開發和位元組碼插樁技術,實作研發傳遞品質自動分析

方案設計:基于IDEA插件開發和位元組碼插樁技術,實作研發傳遞品質自動分析

作者:小傅哥

部落格:https://bugstack.cn

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

一、前言

如何保證代碼品質?

方案設計:基于IDEA插件開發和位元組碼插樁技術,實作研發傳遞品質自動分析

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

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

但是,整個需求實作的代碼是在全部開發完成後提測的,也就是臨近上線的最後一環,大家才知道某個研發的某個功能域的實作是否具備提測條件。如果這個時候代碼品質不高,那麼接下來就是項目風險的時候。

壓測試時間

調上線時間

,總之有病拖着最後成大病了!

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

方案設計:基于IDEA插件開發和位元組碼插樁技術,實作研發傳遞品質自動分析

是以,我們希望采集研發在開發過程中的執行動作,把風險判斷提前。實際操作舉例就是,

當你開發完成一個接口,開始測試運作時

,我們的插件就可以采集到這個接口的全部資訊,包括:接口名稱、入參類型和内容、出參類型和内容、異常資訊、調用關系鍊等。而再把這些資訊彙總送出到服務端,生成本次需求代碼分支下的全部接口動作,以及各系統間的關系鍊路,并附帶随時生成最新的接口文檔和一鍵測試驗證功能。後期測試人員介入時就可以參考研發在編碼過程中的全部測試用例,也可以檢視整個功能的覆寫程度,此外測試人員測試過程中的資料也會被保留下。現在擁有這些資料資訊以後,就可以完整的生成一套研發測試品質傳遞全覽圖,讓整個工程開發傳遞品質評估透明化。

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

二、技術實作準備

  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搜尋一些資料和查閱官方文檔:https://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);
}
           

此處最核心的就是

-javaagent

ProjectProbe.jar

工程探針程式的Jar包加載進去。其他的就是一些關于

PsiFile

API 的使用,感興趣可以閱讀官方文檔中的介紹。

五、效果示範

安裝插件

方案設計:基于IDEA插件開發和位元組碼插樁技術,實作研發傳遞品質自動分析
  • 安裝插件就和我們正常安裝一樣,不過目前這個插件在開發階段,是以需要本地安裝。

運作效果

方案設計:基于IDEA插件開發和位元組碼插樁技術,實作研發傳遞品質自動分析
  • 上圖就是運作效果的案例示範,我們把運作時接口的資訊完整的輸出到控制台。
  • 在實際使用的過程中,會把這部分資訊傳回服務端,由服務端分析處理後,展示在頁面上。

六、總結

  • 基于IDEA插件和位元組碼插樁技術,能做的功能實作還有很多。本文僅僅是其中一種研發到測試痛點的解決方案,如果感興趣可以一起深入研究。
  • 當你看到這樣的案例以後,希望能給你的是并不一定所有的技術點都是為了面試造火箭對話的。當你真的把它落地以後,才會懂的自己需要很多知識。
  • 本文沒有太過多的介紹插件開發和位元組碼技術,如果對位元組碼程式設計感興趣,可以在公衆号:

    bugstack蟲洞棧

    ,回複

    位元組碼程式設計

    。全書11萬7千字,20個章節涵蓋三個位元組碼架構(ASM、Javassist、Byte-budy)和JavaAgent使用并附帶整套案例源碼!

七、系列推薦

  • 數學,離一個程式員有多近?
  • 一次代碼評審,差點過不了試用期!
  • 《Java 面經手冊》PDF,全書5章29節,417頁11.5萬字,完稿&發版!
  • 工作兩年履歷寫成這樣,誰要你呀!
  • 工作3年,看啥資料能月薪30K?

公衆号:bugstack蟲洞棧 | 作者小傅哥多年從事一線網際網路 Java 開發的學習曆程技術彙總,旨在為大家提供一個清晰詳細的學習教程,側重點更傾向編寫Java核心内容。如果能為您提供幫助,請給予支援(關注、點贊、分享)!