ASM

今天介紹下ASM3.0,開始之前先思考幾個問題:

1.ASM是什么募强?
2.ASM 跟傳說中的AOP三劍客APT崇摄、aspectJ、Javassit有什么關(guān)系鸠儿?
3.ASM是怎樣修改class文件的厕氨?

帶著問題開始今天的分享:

  • 1.ASM是什么命斧?

ASM 是一個 Java 字節(jié)碼操控框架。它能被用來動態(tài)生成類或者增強既有類的功能国葬。ASM 可以直接產(chǎn)生二進制 class 文件遭京,也可以在類被加載入 Java 虛擬機之前動態(tài)改變類行為。Java class 被存儲在嚴格格式定義的 .class 文件里鲫趁,這些類文件擁有足夠的元數(shù)據(jù)來解析類中的所有元素:類名稱、方法堡僻、屬性以及 Java 字節(jié)碼(指令)疫剃。ASM 從類文件中讀入信息后,能夠改變類行為巢价,分析類信息,甚至能夠根據(jù)用戶要求生成新類城菊。說白了asm是直接通過字節(jié)碼來修改class文件碉克。

  • 2.ASM 跟傳說中的AOP三劍客APT、aspectJ客税、Javassit有什么關(guān)系撕贞?

分別解釋下這幾個名詞

APT:APT(Annotation Processing Tool)即注解處理器,是一種處理注解的工具酥夭,確切的說它是javac的一個工具脊奋,它用來在編譯時掃描和處理注解疙描。注解處理器以Java代碼(或者編譯過的字節(jié)碼)作為輸入,生成.java文件作為輸出久又。簡單來說就是在編譯期,通過注解生成.java文件

aspectJ:AspectJ是一個面向切面的框架炉峰,它擴展了Java語言脉执。AspectJ定義了AOP語法,所以它有一個專門的[編譯器]用來生成遵守Java字節(jié)編碼規(guī)范的Class文件婆廊。適合在某一個方法前后插入部分代碼巫橄,處理某些邏輯:比如方法運行時間、插入動態(tài)權(quán)限檢查等宾舅。問題會造成很多的冗余代碼枚尼,產(chǎn)生很多代理類。簡單來說就是在生成class時動態(tài)織入代碼

Javassit: Javassist是一個開源的分析崎溃、編輯和創(chuàng)建Java字節(jié)碼的類庫盯质。是由東京工業(yè)大學(xué)的數(shù)學(xué)和計算機科學(xué)系的 Shigeru Chiba(千葉滋)所創(chuàng)建的呼巷。簡單來說就是源碼級別的api去修改字節(jié)碼

各種方式作用時機
  • 3.ASM是怎樣修改class文件的?

開始這個問題之前我們先學(xué)習(xí)幾個東西破镰。

  • 字節(jié)碼

這里的字節(jié)碼主要說的是Java字節(jié)碼压储,看之前的一篇文章java bytecode

  • 訪問者模式

一個稱為元素(Element),另一個稱為訪問者(Visitor)孕似。元素有一個accept方法刮刑,該方法接收訪問者作為參數(shù)养渴;accept()方法調(diào)用訪問者的visit()方法理卑,并且將元素自身作為參數(shù)傳遞給訪問者蔽氨。由元素本身決定是否訪問

在ASM中元素(被訪問者)ClassReader、MethodNode等等中捆,訪問者接口包含ClassVisitor坊饶、AnnotationVisitor、FieldVisitor匿级、MethodVisitor

下面我們先簡單實現(xiàn)一個插樁操作
有如下代碼:

public class Main2Activity extends AppCompatActivity {

    private static int MESSAGE_KEY = 0x2019;
    @SuppressLint("HandlerLeak")
    private static Handler sHandler =new Handler() {
        @Override
        public void handleMessage(Message msg) {
            if (msg.what ==MESSAGE_KEY) {
                if (msg.obj!=null) {
                    Log.i("xmq", String.valueOf(msg.obj));
                }
            }
        }
    };
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        sendMessage(getClass().getSimpleName());
    }
    
    private void sendMessage(String string) {
        Message message = new Message();
        message.what =MESSAGE_KEY;
        sHandler.sendMessage(message);
    }
}

我們要在sendMessage方法中添加一行代碼變?yōu)橄铝?/p>

private void sendMessage(String string) {
        Message message = new Message();
        message.what =MESSAGE_KEY;
        message.obj = string;
        sHandler.sendMessage(message);
}
  • 1.我們這里使用一個Android studio的plugin (ASM ByteCode Outline)查看Main2Activity的ASM代碼,看主要的sendMessage部分
{
            mv = cw.visitMethod(ACC_PRIVATE, "sendMessage", "(Ljava/lang/String;)V", null, null);
            mv.visitCode();
            Label l0 = new Label();
            mv.visitLabel(l0);
            mv.visitLineNumber(33, l0);
            mv.visitTypeInsn(NEW, "android/os/Message");
            mv.visitInsn(DUP);
            mv.visitMethodInsn(INVOKESPECIAL, "android/os/Message", "<init>", "()V", false);
            mv.visitVarInsn(ASTORE, 2);
            Label l1 = new Label();
            mv.visitLabel(l1);
            mv.visitLineNumber(34, l1);
            mv.visitVarInsn(ALOAD, 2);
            mv.visitFieldInsn(GETSTATIC, "com/lucky/lib/studyapp/Main2Activity", "MESSAGE_KEY", "I");
            mv.visitFieldInsn(PUTFIELD, "android/os/Message", "what", "I");
            Label l2 = new Label();
            mv.visitLabel(l2);
            mv.visitLineNumber(36, l2);
            mv.visitFieldInsn(GETSTATIC, "com/lucky/lib/studyapp/Main2Activity", "sHandler", "Landroid/os/Handler;");
            mv.visitVarInsn(ALOAD, 2);
            mv.visitMethodInsn(INVOKEVIRTUAL, "android/os/Handler", "sendMessage", "(Landroid/os/Message;)Z", false);
            mv.visitInsn(POP);
            Label l3 = new Label();
            mv.visitLabel(l3);
            mv.visitLineNumber(37, l3);
            mv.visitInsn(RETURN);
            Label l4 = new Label();
            mv.visitLabel(l4);
            mv.visitLocalVariable("this", "Lcom/lucky/lib/studyapp/Main2Activity;", null, l0, l4, 0);
            mv.visitLocalVariable("string", "Ljava/lang/String;", null, l0, l4, 1);
            mv.visitLocalVariable("message", "Landroid/os/Message;", null, l1, l4, 2);
            mv.visitMaxs(2, 3);
            mv.visitEnd();
        }

看起來很懵逼,其實這里只不過是ASM幫助我們調(diào)用java bytecode罷了孤页。

    1. 修改你想要的代碼,同樣使用ASM ByteCode Outlineplugin對比差異代碼
***
mv.visitFieldInsn(GETSTATIC, "com/lucky/lib/studyapp/Main2Activity", "MESSAGE_KEY", "I");
mv.visitFieldInsn(PUTFIELD, "android/os/Message", "what", "I");
Label l2 = new Label();
mv.visitLabel(l2);
mv.visitLineNumber(35, l2);
mv.visitVarInsn(ALOAD, 2);
mv.visitVarInsn(ALOAD, 1);
mv.visitFieldInsn(PUTFIELD, "android/os/Message", "obj", "Ljava/lang/Object;");
Label l3 = new Label();
mv.visitLabel(l3);
mv.visitLineNumber(36, l3);
***

看的出我們在mv.visitFieldInsn(PUTFIELD, "android/os/Message", "what", "I");后邊插入了三行代碼(其中的Label以及行數(shù)設(shè)置可以不用理)允坚,那么這三行代碼什么意思呢稠项?這里就用到了上邊提到的java bytecode知識鲜结,意思是:將變量2,1分別入棧精刷,并將2變量賦值給message的obj贬养。

好了那么我們開始寫ASM琴庵,這里我們使用android transform api作為前置條件掃描文件仰美。(有關(guān)于transform api后期分享)

開始之前先解釋幾個類:

1.Opcodes接口定義了一些常量儿礼,尤其是版本號,訪問標(biāo)示符诉字,字節(jié)碼等信息知纷;
2.ClassReader用于讀取Class文件,主要用于Class文件的分析伍绳,可接受一個ClassVisitor;ClassReader會將解析過程中產(chǎn)生的類的部分信息乍桂,比如訪問標(biāo)識符,字段权谁,方法逐個送入ClassVisitor,后者在接收到對應(yīng)的信息后憋沿,進行各自的處理;
3.ClassVisitor的子類ClassWriter: 負責(zé)進行Class文件的輸出和生成甥绿。ClassVisitor在進行字段和方法處理的時候则披,會委托給FieldVistor和MethodVisitor進行處理;在類的處理過程中图谷,會創(chuàng)建對應(yīng)的FieldVisitor和MethodVisitor對象阱洪;FieldVisitor和MethodVisitor類也各自有1個重要的子類,F(xiàn)ieldWriter和MethodWriter承璃;當(dāng)ClassWriter進行字段和方法的處理時蚌本,也是依賴這兩個類進行的;
4.ClassVisitor,FieldVisitor,MethodVisitor都可以使用委托的方式隘梨,將實際的處理工作交給內(nèi)部的委托類進行舷嗡;它們內(nèi)部有一些列的visitXXX方法,這些方法就是ASM 的實際方法code。

創(chuàng)建元素跟訪問者

private static byte [] scanClass(InputStream inputStream) {
        //被訪問者(元素)
        ClassReader cr = new ClassReader(inputStream)
        //訪問者
        ClassWriter cw = new ClassWriter(cr, 0)
        ScanClassVisitor cv = new ScanClassVisitor(Opcodes.ASM5,cw) //ClassWriter 的代理類
        cr.accept(cv, ClassReader.EXPAND_FRAMES)
        return cw.toByteArray()
    }

這里的訪問者ScanClassVisitor繼承了ClassVisitor,這里我們要修改某一個方法捻脖,所以實現(xiàn)了visitMethod方法中鼠,并篩選其中的sendMessage方法

    static class ScanClassVisitor extends ClassVisitor{
        ScanClassVisitor(int api, ClassVisitor cv) { //這里很奇怪我無法使用繼承Opcodes,內(nèi)部直接調(diào)用ASM5扰肌,只能傳參數(shù)
            super(api, cv)
        }
        @Override
        MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
            MethodVisitor mv =  cv.visitMethod(access, name, desc, signature, exceptions)
            if (name == "sendMessage") {
                mv = new ScanMethodVisitor(Opcodes.ASM5,mv)
            }
            return mv
        }
    }

上邊的ScanMethodVisitor是實現(xiàn)了MethodVisitor訪問者熊杨,看ASM代碼,我們需要在mv.visitFieldInsn(PUTFIELD, "android/os/Message", "what", "I");行后插入代碼桂躏,所以需要實現(xiàn)visitFieldInsn方法

opcode: PUTFIELD
owner: "android/os/Message"
name:"what"
desc:"I"

static class ScanMethodVisitor extends MethodVisitor {
        ScanMethodVisitor(int api, MethodVisitor mv) {
            super(api, mv)
        }
        @Override
        void visitFieldInsn(int opcode, String owner, String name, String desc) {
            if (opcode == Opcodes.PUTFIELD && owner.equals("android/os/Message")) {
                mv.visitFieldInsn(opcode, owner, name, desc)
                mv.visitVarInsn(Opcodes.ALOAD, 2)
                mv.visitVarInsn(Opcodes.ALOAD, 1)
                mv.visitFieldInsn(Opcodes.PUTFIELD, "android/os/Message", "obj", "Ljava/lang/Object;")
            } else {
                mv.visitFieldInsn(opcode, owner, name, desc)
            }
        }
    }

好了接下來運行代碼transform看效果

public class Main2Activity extends AppCompatActivity {
    private static int MESSAGE_KEY = 8217;
    @SuppressLint({"HandlerLeak"})
    private static Handler sHandler = new Handler() {
        public void handleMessage(Message msg) {
            if (msg.what == Main2Activity.MESSAGE_KEY && msg.obj != null) {
                Log.i("xmq", String.valueOf(msg.obj));
            }

        }
    };

    public Main2Activity() {
    }

    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        this.setContentView(2131296284);
        this.sendMessage(this.getClass().getSimpleName());
    }

    private void sendMessage(String string) {
        Message message = new Message();
        message.what = MESSAGE_KEY;
        message.obj = string;
        sHandler.sendMessage(message);
    }
}

總結(jié):ASM直接修改class文件確實效率很高剂习,但因直接操作字節(jié)碼鳞绕,需要有字節(jié)碼知識尸曼,不適合直接上手,相比較來Javassit源碼級修改class文件更方便些冤竹。
demo

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末闸氮,一起剝皮案震驚了整個濱河市控汉,隨后出現(xiàn)的幾起案子在抛,更是在濱河造成了極大的恐慌,老刑警劉巖肠阱,帶你破解...
    沈念sama閱讀 216,372評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡缘回,警方通過查閱死者的電腦和手機典挑,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評論 3 392
  • 文/潘曉璐 我一進店門您觉,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人琳水,你說我怎么就攤上這事〕峡校” “怎么了私沮?”我有些...
    開封第一講書人閱讀 162,415評論 0 353
  • 文/不壞的土叔 我叫張陵仔燕,是天一觀的道長。 經(jīng)常有香客問我晰搀,道長外恕,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,157評論 1 292
  • 正文 為了忘掉前任髓迎,我火速辦了婚禮建丧,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘橄维。我一直安慰自己,他們只是感情好争舞,可當(dāng)我...
    茶點故事閱讀 67,171評論 6 388
  • 文/花漫 我一把揭開白布竞川。 她就那樣靜靜地躺著,像睡著了一般床牧。 火紅的嫁衣襯著肌膚如雪遭贸。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,125評論 1 297
  • 那天著蛙,我揣著相機與錄音耳贬,去河邊找鬼。 笑死暂吉,一個胖子當(dāng)著我的面吹牛缎患,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播挤渔,決...
    沈念sama閱讀 40,028評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼判导!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起绕辖,我...
    開封第一講書人閱讀 38,887評論 0 274
  • 序言:老撾萬榮一對情侶失蹤仪际,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后树碱,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,310評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡框舔,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,533評論 2 332
  • 正文 我和宋清朗相戀三年刘绣,在試婚紗的時候發(fā)現(xiàn)自己被綠了惑淳。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片饺窿。...
    茶點故事閱讀 39,690評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡肚医,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出肠套,到底是詐尸還是另有隱情,我是刑警寧澤瓷耙,帶...
    沈念sama閱讀 35,411評論 5 343
  • 正文 年R本政府宣布刁赖,位于F島的核電站宇弛,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏枪芒。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,004評論 3 325
  • 文/蒙蒙 一纽甘、第九天 我趴在偏房一處隱蔽的房頂上張望贷腕。 院中可真熱鬧,春花似錦泽裳、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽烹笔。三九已至,卻和暖如春抛丽,著一層夾襖步出監(jiān)牢的瞬間谤职,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評論 1 268
  • 我被黑心中介騙來泰國打工亿鲜, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留允蜈,地道東北人。 一個月前我還...
    沈念sama閱讀 47,693評論 2 368
  • 正文 我出身青樓蒿柳,卻偏偏與公主長得像饶套,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子垒探,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,577評論 2 353

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

  • 前言 很早之前就寫過面向切面的編程思想妓蛮,主要學(xué)習(xí)了AOP的思想(參考:AOP簡介)以及使用 AspectJ 實現(xiàn)簡...
    Whyn閱讀 10,790評論 4 40
  • 引言 什么是 ASM 圾叼? ASM 是一個 Java 字節(jié)碼操控框架蛤克。它能被用來動態(tài)生成類或者增強既有類的功能。AS...
    Chauncey_Chen閱讀 1,482評論 0 6
  • 最近進行組內(nèi)分享時選擇了這個Java字節(jié)碼處理這個主題夷蚊,特此記錄下來构挤。眾所周知,Java是一門運行在虛擬機上的語言...
    sheepm閱讀 16,395評論 1 71
  • 1. 概述 AOP(面向切面編程)的概念現(xiàn)在已經(jīng)應(yīng)用的非常廣泛了撬码,下面是從百度百科上摘抄的一段解釋儿倒,比較淺顯易懂 ...
    lijiankun24閱讀 17,929評論 4 33
  • ASM是一款基于java字節(jié)碼層面的代碼分析和修改工具。無需提供源代碼即可對應(yīng)用嵌入所需debug代碼呜笑,用于應(yīng)用A...
    LedBoot閱讀 6,934評論 4 23