深入淺出ASM

前言

ASM作為一個聲名在外的字節(jié)碼編制工具,無數(shù)“傳奇”框架都基于此展現(xiàn)了花里胡哨的魔法灌具。

最近在工作中發(fā)現(xiàn)需要加強(qiáng)這部分能力,不然很多技術(shù)方案總是很麻煩...但是僅靠ASM實(shí)際也無法“無所欲為”,因?yàn)檎f到底它也只是一個方便的改寫class的工具碧囊。想要使其發(fā)揮戰(zhàn)斗力,還需要配合諸如:Gradle的transform api纤怒、注解等角色的支持糯而。

因此接下來的一段時間內(nèi),我會盡可能的把自己在這方面的實(shí)戰(zhàn)內(nèi)容輸出出來泊窘。

正文

這一篇咱們主要聊ASM的一些用法熄驼,核心聚焦于ASM。所以關(guān)于字節(jié)碼的部分就不展開了烘豹,有相關(guān)興趣的同學(xué)可以自行了解吧

說實(shí)話ASM整體使用起來很簡單瓜贾,主要有數(shù)個核心類:ClassReaderClassWrite携悯、ClassVisitor...等各種Visitor系列類祭芦。

從類名的定義上,我們可以猜到ClassReader用于讀取class文件憔鬼;ClassWrite用于改寫class文件龟劲。而ClassVisitor胃夏、FieldVisitor...則抽象類、方法咸灿、對象訪問的流程构订。

第一步先把依賴加上,AMS現(xiàn)在已經(jīng)出到很高的版本了避矢,不過咱們還是隨便用個版本悼瘾,反正夠用~

implementation 'org.ow2.asm:asm:6.0'
implementation 'org.ow2.asm:asm-util:6.0'

一、讀Class

下邊看一個簡單的讀class的demo代碼:

fun main() {
    val cp = ClassPrinter()
    val cr = ClassReader("com.test.asm.AsmDemo")
    cr.accept(cp, 0)
}

class AsmDemo {
    private val hello = "Hello ASM"

    fun testAsm() {
        invokeMethod()
    }

    private fun invokeMethod() {
        print(hello)
    }
}


class ClassPrinter : ClassVisitor(ASM6) {
    override fun visit(
        version: Int, access: Int, name: String?, signature: String?, superName: String?, interfaces: Array<String>
    ) {
        println("$name extends $superName {")
    }
    // 為了看起來簡單审胸,移除了一些不是特別重要的方法

    override fun visitField(access: Int, name: String?, desc: String?, signature: String?, value: Any?): FieldVisitor? {
        println("    visitField(name:$name desc:$desc signature:$signature)")
        return null
    }

    override fun visitMethod(access: Int, name: String?, desc: String?, signature: String?, exceptions: Array<String>?): MethodVisitor? {
        println("    visitMethod(name:$name desc:$desc signature:$signature)")
        return null
    }

    override fun visitEnd() {
        println("}")
    }
}

這段代碼run起來之后輸出了如下的信息:

com/test/asm/AsmDemo extends java/lang/Object {
    visitField(name:hello desc:Ljava/lang/String; signature:null)
    visitMethod(name:testAsm desc:()V signature:null)
    visitMethod(name:invokeMethod desc:()V signature:null)
    visitMethod(name:<init> desc:()V signature:null)
}

從demo中我們看到通過我們通過ClassReader("com.test.asm.AsmDemo")亥宿,讀取對應(yīng)的Class。而ClassPrinter : ClassVisitor(ASM6)通過對應(yīng)的visit方法來了解對應(yīng)類的細(xì)節(jié)砂沛。

二烫扼、寫Class

對于寫的過程相對要復(fù)雜一些,畢竟操作空間比較大碍庵。比如下邊這個移除某方法的操作:

fun main() {
    val cr = ClassReader("com.test.asm.AsmDemo")
    val cw = ClassWriter(cr, 0)
    val adapter = RemoveMethodAdapter(cw, "testAsm")
    cr.accept(adapter, 0)
    // 輸出class
    val outFile = File("...本地地址/com/test/asm/TestAsmDemo.class")
    outFile.writeBytes(cw.toByteArray())
}

public class RemoveMethodAdapter extends ClassVisitor {
      private String mName;
      private String mDesc;
      public RemoveMethodAdapter( ClassVisitor cv, String mName, String mDesc) {
          super(ASM7, cv);
          this.mName = mName;
          this.mDesc = mDesc;
      }
      @Override
      public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
          if (name.equals(mName) && desc.equals(mDesc)) {
              // 不要委托至下一個訪問器 -> 這樣將移除該方
              return null;
          }
          return cv.visitMethod(access, name, desc, signature, exceptions);
      }
}

打開輸出的TestAsmDemo.class

image

我們可以發(fā)現(xiàn)testAsm()方法已經(jīng)在ClassWriter這個實(shí)例中被移除了映企。

三、源碼速讀

整個流程的開始静浴,就在于ClassReader的accept()方法:

public void accept(
      final ClassVisitor classVisitor,
      final Attribute[] attributePrototypes,
      final int parsingOptions) {
    // 省略大量代碼     
    if ((parsingOptions & SKIP_DEBUG) == 0
        && (sourceFile != null || sourceDebugExtension != null)) {
      classVisitor.visitSource(sourceFile, sourceDebugExtension);
    }
}

上述簡單截取了一段代碼堰氓,這里本質(zhì)就是讀取class文件,然后按class的規(guī)范去解析苹享,然后回調(diào)ClassVisitor對應(yīng)的接口方法双絮。

對于ClassVisitor的實(shí)現(xiàn)來說,可以是我們自己的實(shí)現(xiàn)類得问,這樣我們就可以訪問到ClassReader解析class的過程囤攀。

但是一般來說我們需要去改寫class,此外核心的類便是ClassWriter宫纬。

public class ClassWriter extends ClassVisitor {
  // 省略大量代碼
  @Override
  public final MethodVisitor visitMethod(
      final int access,
      final String name,
      final String descriptor,
      final String signature,
      final String[] exceptions) {
    MethodWriter methodWriter =
        new MethodWriter(symbolTable, access, name, descriptor, signature, exceptions, compute);
    if (firstMethod == null) {
      firstMethod = methodWriter;
    } else {
      lastMethod.mv = methodWriter;
    }
    return lastMethod = methodWriter;
  }
}

這里我們單獨(dú)截取了visitMethod()方法焚挠,可以看到這里真正的實(shí)現(xiàn)是通過MethodWriter實(shí)現(xiàn)的:

final class MethodWriter extends MethodVisitor {
  // 省略大量代碼
  @Override
  public void visitMethodInsn(
      final int opcode,
      final String owner,
      final String name,
      final String descriptor,
      final boolean isInterface) {
    lastBytecodeOffset = code.length;
    Symbol methodrefSymbol = symbolTable.addConstantMethodref(owner, name, descriptor, isInterface);
    if (opcode == Opcodes.INVOKEINTERFACE) {
      code.put12(Opcodes.INVOKEINTERFACE, methodrefSymbol.index)
          .put11(methodrefSymbol.getArgumentsAndReturnSizes() >> 2, 0);
    } else {
      code.put12(opcode, methodrefSymbol.index);
    }
    // 省略部分代碼
  }
}

可以看到,這里封裝了寫class的代碼漓骚。走到這我們就能明白:我們之所以能夠做到改寫class的效果宣蔚,本質(zhì)是因?yàn)镃lassWriter這個類的封裝,而這個類是基于ClassVisitor的訪問者模式來了解到ClassReader加載解析class的過程认境。

更多的咱們就不看了,大家有興趣自己跟一波吧挟鸠。畢竟以上的代碼就足以讓我們理解ASM整體的工作流程叉信。

四、小總結(jié)

結(jié)合上述的內(nèi)容艘希,咱們來一個總結(jié)

首先ASM整體基于訪問者模式(不了解這個模式也沒關(guān)系硼身,不影響理解)硅急。

  • ClassReader解析class文件,并回調(diào)對應(yīng)ClassVisitor接口的方法
    • ClassReader只負(fù)責(zé)分析class文件佳遂,然后回調(diào)給ClassVisitor营袜,至此ClassReader也就結(jié)束了它的工作
  • ClassWriter 是ClassVisitor接口的實(shí)現(xiàn)
    • 這里封裝了對class文件寫的操作
      • 具體代碼在ClassWriter里邊的各種Writer實(shí)現(xiàn)。
    • ClassWriter也是一個ClassVisitor
      • 它是咱們的第一層“代理”丑罪,我們的自定義ClassVisitor通過傳入ClassWriter荚板,來做到visitor流程的轉(zhuǎn)發(fā)
image

因此,可以這么說:ClassReader + ClassVisitor 采用標(biāo)準(zhǔn)的訪問者模式吩屹。目的在于:ASM框架基于我們一套接口跪另,可以讓我們訪問到一個class文件的各種流程。

當(dāng)我們需要改寫class時候煤搜,我們則需要ClassWriter這個特別的ClassVisitor來進(jìn)行能力的增強(qiáng)免绿。

尾聲

本篇內(nèi)容很短,但也算是拉開“幕后”編改字節(jié)碼的序幕擦盾。

后面的文章會一步步的走入真正的應(yīng)用中去嘲驾。相關(guān)知識涉及面眾多,我會盡可能的用通俗的方式將這部分內(nèi)容展現(xiàn)給大家看迹卢。

更新更多的文章辽故,歡迎關(guān)注我們的公眾號:咸魚正翻身

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末婶希,一起剝皮案震驚了整個濱河市榕暇,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌喻杈,老刑警劉巖彤枢,帶你破解...
    沈念sama閱讀 218,640評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異筒饰,居然都是意外死亡缴啡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,254評論 3 395
  • 文/潘曉璐 我一進(jìn)店門瓷们,熙熙樓的掌柜王于貴愁眉苦臉地迎上來业栅,“玉大人,你說我怎么就攤上這事谬晕〉庠#” “怎么了?”我有些...
    開封第一講書人閱讀 165,011評論 0 355
  • 文/不壞的土叔 我叫張陵攒钳,是天一觀的道長帮孔。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么文兢? 我笑而不...
    開封第一講書人閱讀 58,755評論 1 294
  • 正文 為了忘掉前任晤斩,我火速辦了婚禮,結(jié)果婚禮上姆坚,老公的妹妹穿的比我還像新娘澳泵。我一直安慰自己,他們只是感情好兼呵,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,774評論 6 392
  • 文/花漫 我一把揭開白布兔辅。 她就那樣靜靜地躺著,像睡著了一般萍程。 火紅的嫁衣襯著肌膚如雪幢妄。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,610評論 1 305
  • 那天茫负,我揣著相機(jī)與錄音蕉鸳,去河邊找鬼。 笑死忍法,一個胖子當(dāng)著我的面吹牛潮尝,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播饿序,決...
    沈念sama閱讀 40,352評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼勉失,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了原探?” 一聲冷哼從身側(cè)響起乱凿,我...
    開封第一講書人閱讀 39,257評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎咽弦,沒想到半個月后徒蟆,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,717評論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡型型,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,894評論 3 336
  • 正文 我和宋清朗相戀三年段审,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片闹蒜。...
    茶點(diǎn)故事閱讀 40,021評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡寺枉,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出绷落,到底是詐尸還是另有隱情姥闪,我是刑警寧澤,帶...
    沈念sama閱讀 35,735評論 5 346
  • 正文 年R本政府宣布砌烁,位于F島的核電站筐喳,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜疏唾,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,354評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望函似。 院中可真熱鬧槐脏,春花似錦、人聲如沸撇寞。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,936評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽蔑担。三九已至牌废,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間啤握,已是汗流浹背鸟缕。 一陣腳步聲響...
    開封第一講書人閱讀 33,054評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留排抬,地道東北人懂从。 一個月前我還...
    沈念sama閱讀 48,224評論 3 371
  • 正文 我出身青樓,卻偏偏與公主長得像蹲蒲,于是被迫代替她去往敵國和親番甩。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,974評論 2 355

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