1. Java探針技術(shù):Instrumentation

背景:假如我們想打印出某些系統(tǒng)->某些類->某些方法的執(zhí)行耗時,方式有很多召庞,但是想要無侵入的做到這一點破镰,只有Java探針一種方式题画。這也是很多調(diào)用鏈系統(tǒng)依賴的技術(shù)基礎(chǔ)默辨。

什么是Java探針

通俗來講,就是Java提供的一種手段苍息,使我們可以修改并重新加載Class字節(jié)碼缩幸,做到在系統(tǒng)外部來改變類的行為。

Java探針是如何做到的

我們知道只要是java程序竞思,運行的入口就一定是main方法表谊。Java探針技術(shù)相當(dāng)于變相的改變了這個約定,提供了一種外掛盖喷,只要用了這個外掛(即在程序的運行時參數(shù)中加了-javaagent)爆办,就可以先執(zhí)行外掛jar包中的premain方法,然后再執(zhí)行原程序中的main方法课梳。這樣就提供了一個在真正的程序執(zhí)行前距辆,可以修改并重新加載字節(jié)碼的機會。

具體怎么做

我們寫個demo暮刃。

  1. 假如原程序為:

    public class DemoMain {
    
        public static void main(String[] args) {
            System.out.println("\nDemoMain.main開始");
    
            HelloService helloService = new HelloService();
            helloService.sayHello("一拳超人");
    
            System.out.println("DemoMain.main結(jié)束");
        }
    
    }
    
    public class HelloService {
    
        public void sayHello(String name) {
            System.out.println("      HelloService.sayHello開始");
            System.out.println("         Hello " + name + "!");
            System.out.println("      HelloService.sayHello結(jié)束");
        }
    
    }
    
  2. 打jar包跨算,新版的idea打包配置有點小問題,默認(rèn)的MANIFEST的文件夾路徑中椭懊,要刪除/main/java诸蚕,如圖:


    idea打包配置.jpg
  3. 我們想打印HelloService.sayHello的執(zhí)行耗時,使用Java探針來實現(xiàn)氧猬,先寫外掛程序

```java
public class PrintAgent {

    public static void premain(String agentArgs, Instrumentation instrumentation) {
        System.out.println("\nPrintAgent.premain開始");
        System.out.println("   PrintAgent.premain入?yún)ⅲ? + agentArgs);

        instrumentation.addTransformer(new PrintTransformer());

        System.out.println("PrintAgent.premain結(jié)束");
    }

    public static void main(String[] args) {
        //main方法在idea打包時需要背犯,實際上沒什么作用,不會被執(zhí)行盅抚。
    }

}

public class PrintTransformer implements ClassFileTransformer {

    final static String startTime = "\nlong startTime = System.nanoTime();\n";
    final static String endTime = "\nlong endTime = System.nanoTime();\n";

    final static Map<String, List<String>> methodMap = new HashMap<String, List<String>>();

    public PrintTransformer() {
        addMethod("com.wang.hello.HelloService", "sayHello");
    }

    private PrintTransformer addMethod(String className, String methodName) {
        List<String> list = methodMap.computeIfAbsent(className, cn -> new ArrayList<>());
        list.add(methodName);
        return this;
    }

    @Override
    public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
        className = className.replace("/", ".");
        if (methodMap.containsKey(className)) {
            try {
                CtClass ctClass = ClassPool.getDefault().get(className);
                for (String methodName : methodMap.get(className)) {
                    String cost = "\nSystem.out.println(\"   Method:"
                            + methodName
                            + " Cost:\"+(endTime-startTime)+"
                            + "\"ns\");\n";
                    CtMethod ctMethod = ctClass.getDeclaredMethod(methodName);
                    String wrappedMethodName = methodName + "$wrapped";
                    ctMethod.setName(wrappedMethodName);

                    CtMethod newMethod = CtNewMethod.copy(ctMethod, methodName, ctClass, null);

                    StringBuilder newMethodBody = new StringBuilder();
                    newMethodBody.append("{");
                    newMethodBody.append(startTime);
                    newMethodBody.append(wrappedMethodName + "($$);\n");
                    newMethodBody.append(endTime);
                    newMethodBody.append(cost);
                    newMethodBody.append("}");

                    newMethod.setBody(newMethodBody.toString());
                    ctClass.addMethod(newMethod);
                }
                return ctClass.toBytecode();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return null;
    }
}
```
  1. PrintAgent的打包配置如圖:
16191343861365.jpg
  1. 然后需要修改PrintAgent的META-INF/MANIFEST.MF文件媳板,改為(注意結(jié)尾必須加一新行):

    Manifest-Version: 1.0
    Premain-Class: com.wang.agent.PrintAgent
    Can-Redefine-Classes: true
    **我是空白行占位符**
    
  2. 打完包后,我們就可以執(zhí)行測試了泉哈,先看下原始程序打印的日志:

    //執(zhí)行java -jar demo.jar
    DemoMain.main開始
          HelloService.sayHello開始
             Hello 一拳超人!
          HelloService.sayHello結(jié)束
    DemoMain.main結(jié)束
    
  3. 外掛探針后蛉幸,再次執(zhí)行打印的日志破讨,注意其中"Method:sayHello Cost:128835ns"為我們增加的耗時日志:

    //執(zhí)行java -javaagent:instrumentation.jar=wahaha -jar demo.jar
    PrintAgent.premain開始
       PrintAgent.premain入?yún)ⅲ簑ahaha
    PrintAgent.premain結(jié)束
    
    DemoMain.main開始
          HelloService.sayHello開始
             Hello 一拳超人!
          HelloService.sayHello結(jié)束
       Method:sayHello Cost:128835ns
    DemoMain.main結(jié)束
    
  4. 關(guān)鍵的點解決后,再往深了做奕纫,就能慢慢的實現(xiàn)調(diào)用鏈的邏輯了提陶。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市匹层,隨后出現(xiàn)的幾起案子隙笆,更是在濱河造成了極大的恐慌,老刑警劉巖升筏,帶你破解...
    沈念sama閱讀 219,039評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件撑柔,死亡現(xiàn)場離奇詭異,居然都是意外死亡您访,警方通過查閱死者的電腦和手機铅忿,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,426評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來灵汪,“玉大人檀训,你說我怎么就攤上這事∠硌裕” “怎么了峻凫?”我有些...
    開封第一講書人閱讀 165,417評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長览露。 經(jīng)常有香客問我荧琼,道長,這世上最難降的妖魔是什么差牛? 我笑而不...
    開封第一講書人閱讀 58,868評論 1 295
  • 正文 為了忘掉前任铭腕,我火速辦了婚禮,結(jié)果婚禮上多糠,老公的妹妹穿的比我還像新娘累舷。我一直安慰自己,他們只是感情好夹孔,可當(dāng)我...
    茶點故事閱讀 67,892評論 6 392
  • 文/花漫 我一把揭開白布被盈。 她就那樣靜靜地躺著,像睡著了一般搭伤。 火紅的嫁衣襯著肌膚如雪只怎。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,692評論 1 305
  • 那天怜俐,我揣著相機與錄音身堡,去河邊找鬼。 笑死拍鲤,一個胖子當(dāng)著我的面吹牛贴谎,可吹牛的內(nèi)容都是我干的汞扎。 我是一名探鬼主播,決...
    沈念sama閱讀 40,416評論 3 419
  • 文/蒼蘭香墨 我猛地睜開眼擅这,長吁一口氣:“原來是場噩夢啊……” “哼澈魄!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起仲翎,我...
    開封第一講書人閱讀 39,326評論 0 276
  • 序言:老撾萬榮一對情侶失蹤痹扇,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后溯香,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體鲫构,經(jīng)...
    沈念sama閱讀 45,782評論 1 316
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,957評論 3 337
  • 正文 我和宋清朗相戀三年玫坛,在試婚紗的時候發(fā)現(xiàn)自己被綠了结笨。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,102評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡昂秃,死狀恐怖禀梳,靈堂內(nèi)的尸體忽然破棺而出杜窄,到底是詐尸還是另有隱情肠骆,我是刑警寧澤,帶...
    沈念sama閱讀 35,790評論 5 346
  • 正文 年R本政府宣布塞耕,位于F島的核電站蚀腿,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏扫外。R本人自食惡果不足惜莉钙,卻給世界環(huán)境...
    茶點故事閱讀 41,442評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望筛谚。 院中可真熱鬧磁玉,春花似錦、人聲如沸驾讲。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,996評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽吮铭。三九已至时迫,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間谓晌,已是汗流浹背掠拳。 一陣腳步聲響...
    開封第一講書人閱讀 33,113評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留纸肉,地道東北人溺欧。 一個月前我還...
    沈念sama閱讀 48,332評論 3 373
  • 正文 我出身青樓喊熟,卻偏偏與公主長得像,于是被迫代替她去往敵國和親胧奔。 傳聞我的和親對象是個殘疾皇子逊移,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,044評論 2 355

推薦閱讀更多精彩內(nèi)容