1.ASM简介
动态修改Java的class字节码的框架
1.1 ASM的作用
- 注解+注入:假设有一个判断登录功能,可以使用注解,在方法开始的地方,解析注解,然后插入代码
- 闭源代码修改:假设有一个闭源的代码内部有bug,可以通过修改字节码的方式来进行修改
- 统计功能:Android中可以在方法开始和方法结束,插入代码桩,来得出时间,找出anr
1.2 ASM简单使用
使用idea新建工程,首先导入asm:
implementation("org.ow2.asm:asm:9.2")
implementation("org.ow2.asm:asm-commons:9.2")
编写简单的测试类:
ASM01.java
package com.ifreedomer.asm;
public class ASM01 {
public static void main(String[] args) {
System.out.println("hello asm");
}
}
安装插件: asm-bytecode-outline
然后双击shift->show bytecode outline,得到如下字节码:
// class version 52.0 (52)
// access flags 0x21
public class com/ifreedomer/asm/ASM01 {
// compiled from: ASM01.java
// access flags 0x1
public <init>()V
L0
LINENUMBER 2 L0
ALOAD 0
INVOKESPECIAL java/lang/Object.<init> ()V
RETURN
L1
LOCALVARIABLE this Lcom/ifreedomer/asm/ASM01; L0 L1 0
MAXSTACK = 1
MAXLOCALS = 1
// access flags 0x9
public static main([Ljava/lang/String;)V
L0
LINENUMBER 4 L0
GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
LDC "hello asm"
INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
L1
LINENUMBER 5 L1
RETURN
L2
LOCALVARIABLE args [Ljava/lang/String; L0 L2 0
MAXSTACK = 2
MAXLOCALS = 1
}
我们首先看一下最简单的:
package com.ifreedomer.asm
import org.objectweb.asm.*
import java.io.FileInputStream
import org.objectweb.asm.commons.AdviceAdapter
import org.objectweb.asm.commons.Method
fun main(args: Array<String>) {
//读取class
val inputStream = FileInputStream("/Users/xx/Documents/Study/ASMStudy/build/classes/java/main/ASM01.class")
//class解析类
val classReader = ClassReader(inputStream)
//class写出类
val classWriter = ClassWriter(classReader, ClassWriter.COMPUTE_MAXS)
//class访问类,所有类的信息都可以在这里面获取到
val classVisitor = AMSClassVisitor(Opcodes.ASM9, classWriter)
classReader.accept(classVisitor, ClassReader.EXPAND_FRAMES)
}
//记录methodName
var methodName = ""
class AMSClassVisitor(api: Int, classVisitor: ClassVisitor) : ClassVisitor(api, classVisitor) {
override fun visitMethod(
access: Int,
name: String?,
descriptor: String?,
signature: String?,
exceptions: Array<out String>?
): MethodVisitor {
val methodVisitor = super.visitMethod(access, name, descriptor, signature, exceptions)
return ASMMethodVisitor(
api = api,
name = name,
methodVisitor = methodVisitor,
access = access,
descriptor = descriptor
)
}
}
/**
* @param api asm的版本,@link{Opcodes.ASM9}
* @param methodVisitor 方法访问器
* @param access 访问作用于 public protect等
* @param name 方法名
* @param descriptor 类方法的签名描述
*/
class ASMMethodVisitor(
api: Int, methodVisitor: MethodVisitor?, access: Int, name: String?,
descriptor: String?
) : AdviceAdapter(
api,
methodVisitor, access, name, descriptor
) {
//方法进入
override fun onMethodEnter() {
System.out.println("onMethodEnter $methodName")
}
//方法退出
override fun onMethodExit(opcode: Int) {
System.out.println("onMethodExit $methodName")
}
init {
if (name != null) {
methodName = name
}
println("name = $name desc = $descriptor")
}
}
这是ASM最简单的代码了,首先看main方法:
- 读入class
- 构造ClassReader
- 构造ClassWriter
- 构造ClassVisitor
- 替换ClassVisitor内部的visitMothod,换成我们自己的ASMMethodVisitor
看看打印:
name = <init> desc = ()V
onMethodEnter <init>
onMethodExit <init>
name = main desc = ([Ljava/lang/String;)V
onMethodEnter main
onMethodExit main
对比字节码,我们发现,ASM01.class有两个方法,分别是init()和main(),所以我们的的程序正常work,这是ASM最简单的入门