ASM簡(jiǎn)單入門筆記

1. 前言

前幾天,在Q群里有個(gè)大佬,展示了下 Android 做無痕埋點(diǎn),覺得挺厲害的
問了下使用的是 AspectJ, 網(wǎng)上搜了下資料 ASM 比 AspectJ 更靈活,更輕量
剛好趁著五一假期系統(tǒng)的學(xué)習(xí)下

2. 介紹

ASM 是一款輕量級(jí)的Java字節(jié)碼操作倉(cāng)庫(kù)

3. 前期準(zhǔn)備

3.1 簡(jiǎn)單的asm 方面的知識(shí)

ASM 主要有幾個(gè)類需要了解 而且需要對(duì) Java字節(jié)碼 比較熟悉

ClassReader
    字節(jié)碼的讀取與分析引擎扛点。它采用類似SAX的事件讀取機(jī)制雕沉,每當(dāng)有事件發(fā)生時(shí),調(diào)用注冊(cè)的ClassVisitor奢人、AnnotationVisitor攻走、FieldVisitor、MethodVisitor做相應(yīng)的處理此再。

ClassVisitor
    定義在讀取Class字節(jié)碼時(shí)會(huì)觸發(fā)的事件昔搂,如類頭解析完成、注解解析输拇、字段解析摘符、方法解析等

AnnotationVisitor
    定義在解析注解時(shí)會(huì)觸發(fā)的事件,如解析到一個(gè)基本值類型的注解策吠、enum值類型的注解逛裤、Array值類型的注解、注解值類型的注解等

FieldVisitor
    定義在解析字段時(shí)觸發(fā)的事件猴抹,如解析到字段上的注解带族、解析到字段相關(guān)的屬性等
    
MethodVisitor
    定義在解析方法時(shí)觸發(fā)的事件,如方法上的注解蟀给、屬性蝙砌、代碼等。

ClassWriter
    它實(shí)現(xiàn)了ClassVisitor接口跋理,用于拼接字節(jié)碼择克。

3.2 開發(fā)工具準(zhǔn)備

idea / Android studio 
ASM Bytecode Viewer(對(duì) Java字節(jié)碼 不熟悉的話必備)

4 實(shí)戰(zhàn)

4.1 要實(shí)現(xiàn)的效果

class User {
    public static void main(String[] args) {
        show();
    }

    public static void show(){
        System.out.println("Hello World");
    }
}
上圖為一個(gè) User類,要對(duì) show() 方法的耗時(shí)進(jìn)行計(jì)算并打印

4.2 編寫 ASM 邏輯

4.2.1 編寫 ClassVisitor

解析類的監(jiān)聽器,解析Class字節(jié)碼時(shí)會(huì)觸發(fā)內(nèi)部的方法
public class TestClassVisitor extends ClassVisitor {
    public TestClassVisitor(final ClassVisitor cv) {
        super(Opcodes.ASM5, cv);
    }

    @Override
    public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
        if (cv != null) {
            cv.visit(version, access, name, signature, superName, interfaces);
        }
    }

    @Override
    public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
        //如果methodName是show,則返回我們自定義的TestMethodVisitor
        if ("show".equals(name)) {
            MethodVisitor mv = cv.visitMethod(access, name, desc, signature, exceptions);
            return new TestMethodVisitor(mv);
        }
        if (cv != null) {
            return cv.visitMethod(access, name, desc, signature, exceptions);
        }
        return null;
    }
}

4.2.2 編寫 MethodVisitor

解析方法的監(jiān)聽器,解析Method時(shí)會(huì)觸發(fā)內(nèi)部的方法
編寫前若對(duì) Java字節(jié)碼 不熟悉的話 
    建議安裝 ASM Bytecode Viewer 插件!!!
    建議安裝 ASM Bytecode Viewer 插件!!!
    建議安裝 ASM Bytecode Viewer 插件!!!

先新建一個(gè)類 編寫要注入的代碼,然后用插件查看
image.png
public class TestMethodVisitor extends MethodVisitor implements Opcodes {
    public TestMethodVisitor(MethodVisitor mv) {
        super(Opcodes.ASM5, mv);
    }

    @Override
    public void visitCode() {
        //方法體內(nèi)開始時(shí)調(diào)用
        mv.visitMethodInsn(INVOKESTATIC, "java/lang/System", "currentTimeMillis", "()J", false);
        mv.visitVarInsn(LSTORE, 0);
        super.visitCode();
    }
    @Override
    public void visitInsn(int opcode) {
        //每執(zhí)行一個(gè)指令都會(huì)調(diào)用
        if (opcode == Opcodes.RETURN) {
            mv.visitMethodInsn(INVOKESTATIC, "java/lang/System", "currentTimeMillis", "()J", false);
            mv.visitVarInsn(LLOAD, 0);
            mv.visitInsn(LSUB);
            mv.visitVarInsn(LSTORE, 2);
            Label l3 = new Label();
            mv.visitLabel(l3);
            mv.visitLineNumber(11, l3);
            mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
            mv.visitTypeInsn(NEW, "java/lang/StringBuilder");
            mv.visitInsn(DUP);
            mv.visitMethodInsn(INVOKESPECIAL, "java/lang/StringBuilder", "<init>", "()V", false);
            mv.visitLdcInsn("== method cost time = ");
            mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false);
            mv.visitVarInsn(LLOAD, 2);
            mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(J)Ljava/lang/StringBuilder;", false);
            mv.visitLdcInsn(" ==");
            mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false);
            mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "toString", "()Ljava/lang/String;", false);
            mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);

        }
        super.visitInsn(opcode);
    }
}

4.3 測(cè)試效果

編寫測(cè)試類 運(yùn)行

public class Demo {
    public static void main(String[] args) throws IOException {
        ClassReader cr = new ClassReader(User.class.getName());
        ClassWriter cw = new ClassWriter(cr, ClassWriter.COMPUTE_MAXS);
        ClassVisitor cv = new TestClassVisitor(cw);
        cr.accept(cv, Opcodes.ASM5);
        // 獲取生成的class文件對(duì)應(yīng)的二進(jìn)制流
        byte[] code = cw.toByteArray();
        //將二進(jìn)制流寫到out/下
        FileOutputStream fos = new FileOutputStream("out/User.class");
        fos.write(code);
        fos.close();
    }
}

原User 類生成的 .class 文件,以及輸出效果

image.png
image.png

字節(jié)碼修改后的 User 類的 .class 文件以及輸出效果

image.png
image.png

用途

可以用于無痕埋點(diǎn),打印日志,以及性能監(jiān)控等

TIPS:

Github地址

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末前普,一起剝皮案震驚了整個(gè)濱河市肚邢,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌拭卿,老刑警劉巖骡湖,帶你破解...
    沈念sama閱讀 207,248評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異峻厚,居然都是意外死亡勺鸦,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,681評(píng)論 2 381
  • 文/潘曉璐 我一進(jìn)店門目木,熙熙樓的掌柜王于貴愁眉苦臉地迎上來换途,“玉大人懊渡,你說我怎么就攤上這事【猓” “怎么了剃执?”我有些...
    開封第一講書人閱讀 153,443評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)懈息。 經(jīng)常有香客問我肾档,道長(zhǎng),這世上最難降的妖魔是什么辫继? 我笑而不...
    開封第一講書人閱讀 55,475評(píng)論 1 279
  • 正文 為了忘掉前任怒见,我火速辦了婚禮,結(jié)果婚禮上姑宽,老公的妹妹穿的比我還像新娘遣耍。我一直安慰自己,他們只是感情好炮车,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,458評(píng)論 5 374
  • 文/花漫 我一把揭開白布舵变。 她就那樣靜靜地躺著,像睡著了一般瘦穆。 火紅的嫁衣襯著肌膚如雪纪隙。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,185評(píng)論 1 284
  • 那天扛或,我揣著相機(jī)與錄音绵咱,去河邊找鬼。 笑死熙兔,一個(gè)胖子當(dāng)著我的面吹牛麸拄,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播黔姜,決...
    沈念sama閱讀 38,451評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼拢切,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了秆吵?” 一聲冷哼從身側(cè)響起淮椰,我...
    開封第一講書人閱讀 37,112評(píng)論 0 261
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎纳寂,沒想到半個(gè)月后主穗,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,609評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡毙芜,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,083評(píng)論 2 325
  • 正文 我和宋清朗相戀三年忽媒,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片腋粥。...
    茶點(diǎn)故事閱讀 38,163評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡晦雨,死狀恐怖架曹,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情闹瞧,我是刑警寧澤绑雄,帶...
    沈念sama閱讀 33,803評(píng)論 4 323
  • 正文 年R本政府宣布,位于F島的核電站奥邮,受9級(jí)特大地震影響万牺,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜洽腺,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,357評(píng)論 3 307
  • 文/蒙蒙 一脚粟、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧蘸朋,春花似錦核无、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,357評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽画舌。三九已至堕担,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間曲聂,已是汗流浹背霹购。 一陣腳步聲響...
    開封第一講書人閱讀 31,590評(píng)論 1 261
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留朋腋,地道東北人齐疙。 一個(gè)月前我還...
    沈念sama閱讀 45,636評(píng)論 2 355
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像旭咽,于是被迫代替她去往敵國(guó)和親贞奋。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,925評(píng)論 2 344

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