前言
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ù)個核心類:ClassReader
、ClassWrite
携悯、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
:
我們可以發(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ā)
- 這里封裝了對class文件寫的操作
因此,可以這么說: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)注我們的公眾號:咸魚正翻身。