基本概念

这一末节 内容会比较单调,也比较难。 个人建议的便是 有空读一下 揭秘java虚拟机 这本书的相关章节,然后再来看这一末节,理解起来会更简略一些。

另外这一末节最佳的学习办法还是自己下个asm bytecode viewer 的插件,然后自己多写代码 然后比对着翻译后的字节码指令 多领会。

常见的字节码指令

字节码指令由一个标识该指令的操作码和固定数目的参数组成:

  • 操作码是一个无符号字节值——即字节代码名,由注记符号标识
  • 参数是静态值,确定了精确的指令行为,紧跟在操作码之后 字节码指令大致能够分为两类,一类指令用于局部变量和操作数栈之间传递值。另一类用于对操作数栈的值 进行弹出和计算,并压入栈中。

常见的局部变量操作指令有:

  • ILOAD:用于加载 boolean、int、byte、short 和 char 类型的局部变量到操作数栈
  • FLOAD:用于加载 float 类型局部变量到操作数栈
  • LLOAD:用于加载 lang 类型局部变量到操作数栈,需求加载两个槽 slot
  • DLOAD:用于加载 double 类型局部变量到操作数栈,需求加载两个槽 slot
  • ALOAD:用于加载非基础类型的局部变量到操作数栈,比方目标之类的

常见的操作数栈指令有:

  • ISTORE:从操作数栈弹出 boolean、int、byte、short 和 char 类型的局部变量,并将它存储在由其索引 i 指定的局部变量中
  • FSTORE:从操作数栈弹出 float 类型的局部变量,并将它存储在由其索引 i 指定的局部变量中
  • LSTORE:从操作数栈弹出 long 类型的局部变量,并将它存储在由其索引 i 指定的局部变量中
  • DSTORE:从操作数栈弹出 double 类型的局部变量,并将它存储在由其索引 i 指定的局部变量中
  • ASTORE:用于弹出非基础类型的局部变量,并将它存储在由其索引 i 指定的局部变量中

经过上面能够看到,每种对应的数据类型都对应不同的 XLOADXSTORE。这是为了确保不会履行 不合法的转化。

  • 将一个值存储在局部变量表中,在以不同的类型加载它是不合法操作,比方存入 ISTORE 类型,运用 FLOAD 加载
  • 假如向一个局部变量表中的方位存储一个值,而这个值不同于原来的存储类型,这种操作是合法的

局部变量表

局部变量表是一组变量值的存储空间,用于寄存办法参数和办法内部界说的局部变量。在 .java 编译成 .class 文件时,已经确定了所需求分配的局部变量表和操作数栈的巨细。

变量槽

局部变量表的容量单位是变量槽(Variable Slot)。每个变量槽最多能够存储 32 为长度的空间,所以关于 byte、char、boolean、short、int、float、reference 是占用一个变量槽, 关于 64 位长度的 long 和 double 占用连个变量槽。 运用局部变量表时,经过索引定位对应数据的方位,索引值的规模是从 0 开端至局部变量表最大的变量槽数量。 假如拜访的是 32 位数据类型的变量,索引 N 就代表了运用第 N 个变量槽,假如拜访的是 64 位数据类型的变量,则说明会一起运用第 N 和 N+1 两个变量槽。 关于两个相邻的共同寄存一个 64 位数据的两个变量槽,虚拟机不允许选用任何办法独自拜访其中的某一个,假如遇到进行这种操作的字节码,Java 虚拟机就会在类加载的校验阶段中抛出反常。

当一个办法被调用时,会运用局部变量表来完结参数值到参数变量列表的传递进程。假如履行的是目标实例的成员办法(不是 static 润饰的办法),那么局部变量表中第 0 位索引的变量槽默许便是该目标实例的引证(this),在办法中能够经过关键字 this 来拜访到这个隐含的参数。 其他参数则依照参数表次序排列,参数表分配完毕后,再依据办法体内部界说的局部变量次序和作用域分配其他的变量槽。 为了尽可能节省栈帧所耗的内存空间,局部变量表中的变量槽是能够重用的,当办法体中界说的局部变量超出其作用域时,该局部变量对应的变量槽就能够交给其他变量来重用。

虚拟机重要概念

JVM 中的解说器会逐条解说履行字节码指令。履行进程中,JVM 会保护一个栈,用于存储局部变量、操作数和回来地址。在履行每个指令时,JVM 从栈中取出操作数,履行相应的操作,然后将成果压回栈中。例如,加法指令会从栈中弹出两个数值,相加后将成果压回栈中。

咱们能够看一个简略的比如

asm字节码手册 - 方法(二)

能够看下 字节码:

很简略的,取第一个局部变量,取第二个局部变量, 然后 调用add指令,最后return 回来值

asm字节码手册 - 方法(二)

MAXSTACK 和 MAXLOCALS 是 Java 字节码文件中的两个属性,用于界说办法的最大操作数栈和最大局部变量表巨细。

MAXSTACK 表示该办法运行时所需的最大操作数栈的深度,即在办法履行进程中所需求运用的最大的栈空间。 这个值是在编译期间计算出来的,由于 Java 字节码是一种根据栈的言语,因此在办法运行时,JVM 会依照该值来为办法分配栈空间。

MAXLOCALS 则表示该办法运行时所需的最大局部变量表的巨细,即该办法所运用的局部变量的数量。这个值也是在编译期间计算出来的,由于在办法履行进程中,需求为局部变量分配内存空间。

这儿有的人会觉得奇怪,为什么局部变量表的巨细是3, 我这儿分明只有2个局部变量 分别是参数x 和参数y啊

由于这个办法不是一个静态办法,他是一个类的目标办法,关于这种办法来说,会有一个躲藏的参数 也便是 他自己 便是 this, 所以这儿是局部变量表的巨细是3

这儿要谨记 所有类的成员办法 都有一个躲藏的入参 参数 this

databean的 字节码分析

看一下 get 办法

asm字节码手册 - 方法(二)

看下字节码:

asm字节码手册 - 方法(二)

第一条指令 ALOAD 0 ,这个其实便是 将this 压入操作数栈。 上一个末节咱们提到过 类的成员办法 都有一个躲藏的入参 是this ,方位是在第一个方位 也便是0方位上

第二条指令 便是 从栈中弹出这个值, 并将这个目标的name字段 压入栈中,

最后一条指令 便是从栈中弹出这个值了

再看下set 办法

asm字节码手册 - 方法(二)

第一条指令 前面讲过了 不说了

第二条指令便是 把函数的参数 这个局部变量 压入操作数栈

第三条指令 弹出这2个值,并将int值 存储在name 字段中

最后一条指令 是return,关于void办法来说,这儿都是躲藏的一条retuan指令

再看下这个 databean的结构办法:

asm字节码手册 - 方法(二)

第一条指令 不说了 第二条指令 其实便是 调用Object的结构器

INVOKESPECIAL指令首要用于两种状况:

  1. 调用超类的结构办法:在一个子类的结构办法中,假如需求调用超类的结构办法完结目标初始化,能够运用INVOKESPECIAL指令来调用超类的结构办法。
  2. 调用私有办法:在Java中,私有办法不能被子类重写或继承,可是它们能够在同一类中被其他办法调用。这种状况下,能够运用INVOKESPECIAL指令来调用私有办法。

总归,INVOKESPECIAL指令是Java字节码中的一种重要指令,它首要用于调用超类结构器或私有办法。

一个稍微复杂点的set办法

asm字节码手册 - 方法(二)

在Java字节码中,IFLE是一种条件分支指令,它用于在栈顶值小于或等于0时跳转到指定的目标指令

这个就很好理解了吧, 这儿是先取的 局部变量表中 index为1的 局部变量 也便是函数参数的age

对他进行压栈操作,然后 IFLE 指令 对他进行判断,小于等于0的时分

这儿要注意的便是 在java中 抛出一个反常的固定指令标准 是 先new 再dup 再invokespecial

反常处理器

asm字节码手册 - 方法(二)

TRYCATCHBLOCK指令需求四个参数,分别是:

  1. 第一个参数是一个指向try块的开端方位的偏移量(从字节码办法的最初开端计算)。
  2. 第二个参数是一个指向try块的完毕方位的偏移量。
  3. 第三个参数是一个指向catch块的开端方位的偏移量。
  4. 第四个参数是一个指向catch块反常处理程序代码的反常类型。

接口与组件

上一篇文章中 能够使用visitor来遍历一个类的办法和字段,同样的,关于办法来说,咱们也能够用visitor来遍历这个办法

上一末节咱们有了acccode的扩展函数,这会咱们增加一个**opcode 的扩展函数 **

fun Int.opCode2String(): String {
    mapOpcodes.forEach {
        if (it.value == this) {
            return it.key
        }
    }
    return ""
}

还是要走ClassReader 只不过这次咱们在ClassVisitor的 method回调办法中,传入了 咱们自界说的MethodVisitor

val classReader = ClassReader("TestMethod")
val classVisitor = object : ClassVisitor(Opcodes.ASM7) {
    override fun visitMethod(
        access: Int,
        name: String?,
        descriptor: String?,
        signature: String?,
        exceptions: Array<out String>?
    ): MethodVisitor {
        // 最关键便是这儿要回来一个自界说的 MethodVisitor
        val mv = super.visitMethod(access, name, descriptor, signature, exceptions)
        return MethodPrintVisitor(Opcodes.ASM7, mv)
    }
}
classReader.accept(classVisitor, ClassReader.SKIP_DEBUG)

由于每个办法的字节码都不同,所以这些回调函数的次序也是不同的,但基本上都是遵循visitCode开端 和visitMaxs 完毕

这2个回调之间的 那些回调函数的次序以及呈现次数 就彻底取决于 你函数内部的完成了

package com.andoter.asm_example.part3.vivo
import com.andoter.asm_example.part2.vivo.Utils.opCode2String
import org.objectweb.asm.*
/**
 * MethodVisit 首先会拜访注释和属性信息,然后才是办法的字节码,这些拜访次序在 visitCode 和 visitMaxs 调用之间。所以
 * 这两个办法能够用于检测字节码在拜访序列中的开端和完毕。ASM 中供给了三个根据 MethodVisitor API 的组件,用于生成和转化办法。
 *
 * - ClassReader 类分析已编译办法的内容,其在 accept 办法的参数中传递了 ClassVisitor,ClassReader 类将针对这一 ClassVisitor 回来的
 *  MethodVisitor 目标调用呼应的办法。
 *  - ClassWriter 的 visitMethod 办法回来 MethodVisitor 接口的一个完成,它直接以二进制形式生成已编译办法
 *  - MethodVisitor类将它接收到的所有办法调用委托给另一个MethodVisitor办法。能够将它看作一个事情挑选器。
 *
 *  MethodVisitor 回调办法有:
 *  - visitParameter:拜访办法一个参数
 *  - visitAnnotationDefualt:拜访注解接口办法的默许值
 *  - visitAnnotaion:拜访办法的一个注解
 *  - visitTypeAnnotation:拜访办法签名上的一个类型的注解
 *  - visitAnnotableParameterCount:拜访注解参数数量,便是拜访办法参数有注解参数个数
 *  - visitParameterAnnotation:拜访参数的注解,回来一个 AnnotationVisitor 能够拜访该注解值
 *  - visitAttribute:拜访办法的属性
 *  - 重要----visitCode:开端拜访办法代码,此处能够增加办法运行前拦截器 ,这个回调能够认为是办法拜访的开端
 *  - visitFrame:拜访办法局部变量的当时状况以及操作栈成员信息
 *  - visitIntInsn:拜访数值类型指令,当 int 取值-1~5选用 ICONST 指令,取值 -128~127 选用 BIPUSH 指令,取值 -32768~32767 选用 SIPUSH 指令,取值 -2147483648~2147483647 选用 ldc 指令。
 *  - 重要---- visitVarInsn:拜访本地变量类型指令
 *  - visitTypeInsn:拜访类型指令,类型指令会把类的内部称号当成参数 Type
 *  - 重要-----visitFieldInsn:域操作指令,用来加载或许存储目标的 Field
 *  - 重要---- visitMethodInsn:拜访办法操作指令
 *  - visitDynamicInsn:拜访动态类型指令
 *  - 重要----visitJumpInsn:拜访比较跳转指令
 *  - visitLabelInsn:拜访 label,当会在调用该办法后拜访该label标记一个指令
 *  - visitLdcInsn:拜访 LDC 指令,也便是拜访常量池索引
 *  - visitLineNumber:拜访行号描绘
 *  - 重要-----visitMaxs:拜访操作数栈最大值和本地变量表最大值 能够认为是办法拜访的完毕
 *  - visitLocalVariable:拜访本地变量描绘
 */
class MethodPrintVisitor(api: Int, methodVisitor: MethodVisitor?) : MethodVisitor(api, methodVisitor) {
    override fun visitMultiANewArrayInsn(descriptor: String?, numDimensions: Int) {
        super.visitMultiANewArrayInsn(descriptor, numDimensions)
        println("visitMultiANewArrayInsn, descriptor = $descriptor, numDimensions = $numDimensions")
    }
    override fun visitFrame(
        type: Int,
        numLocal: Int,
        local: Array<out Any>?,
        numStack: Int,
        stack: Array<out Any>?
    ) {
        super.visitFrame(type, numLocal, local, numStack, stack)
        println("visitFrame, type = $type, numLocal = $numLocal, local.size = $(local.size), numStack = $numStack")
    }
    /**
     *  visitVarInsn:拜访本地变量类型指令
     */
    override fun visitVarInsn(opcode: Int, `var`: Int) {
        super.visitVarInsn(opcode, `var`)
        println("visitVarInsn, opcode = ${opcode.opCode2String()}, var = $`var`")
    }
    override fun visitTryCatchBlock(start: Label?, end: Label?, handler: Label?, type: String?) {
        super.visitTryCatchBlock(start, end, handler, type)
        println("visitTryCatchBlock")
    }
    override fun visitLookupSwitchInsn(dflt: Label?, keys: IntArray?, labels: Array<out Label>?) {
        super.visitLookupSwitchInsn(dflt, keys, labels)
        println("visitLookupSwitchInsn")
    }
    /**
     * visitJumpInsn:拜访比较跳转指令
     */
    override fun visitJumpInsn(opcode: Int, label: Label?) {
        super.visitJumpInsn(opcode, label)
        println("visitJumpInsn, opcode = ${opcode.opCode2String()}")
    }
    override fun visitLdcInsn(value: Any?) {
        super.visitLdcInsn(value)
        println("visitLdcInsn, value = $value")
    }
    override fun visitAnnotableParameterCount(parameterCount: Int, visible: Boolean) {
        super.visitAnnotableParameterCount(parameterCount, visible)
    }
    override fun visitIntInsn(opcode: Int, operand: Int) {
        super.visitIntInsn(opcode, operand)
        println("visitIntInsn, opcode = ${opcode.opCode2String()}, operand = $operand")
    }
    override fun visitTypeInsn(opcode: Int, type: String?) {
        super.visitTypeInsn(opcode, type)
        println("visitTypeInsn, opcode = ${opcode.opCode2String()}, type = $type")
    }
    override fun visitAnnotationDefault(): AnnotationVisitor? {
        println("visitAnnotationDefault")
        return super.visitAnnotationDefault()
    }
    override fun visitAnnotation(descriptor: String?, visible: Boolean): AnnotationVisitor? {
        println("visitAnnotation")
        return super.visitAnnotation(descriptor, visible)
    }
    override fun visitTypeAnnotation(
        typeRef: Int,
        typePath: TypePath?,
        descriptor: String?,
        visible: Boolean
    ): AnnotationVisitor? {
        println("visitTypeAnnotation")
        return super.visitTypeAnnotation(typeRef, typePath, descriptor, visible)
    }
    /**
     * 办法拜访的完毕
     */
    override fun visitMaxs(maxStack: Int, maxLocals: Int) {
        super.visitMaxs(maxStack, maxLocals)
        println("visitMaxs")
    }
    override fun visitInvokeDynamicInsn(
        name: String?,
        descriptor: String?,
        bootstrapMethodHandle: Handle?,
        vararg bootstrapMethodArguments: Any?
    ) {
        println("visitInvokeDynamicInsn, name = $name, descriptor = $descriptor, bootstrapMethodHandle = ${bootstrapMethodHandle?.name}")
        super.visitInvokeDynamicInsn(
            name,
            descriptor,
            bootstrapMethodHandle,
            *bootstrapMethodArguments
        )
    }
    /**
     * 当ASM解析字节码时遇到一个标签时,它将调用MethodVisitor的visitLabel办法,并传递标签目标作为参数
      */
    override fun visitLabel(label: Label?) {
        super.visitLabel(label)
        println("visitLabel ")
    }
    override fun visitTryCatchAnnotation(
        typeRef: Int,
        typePath: TypePath?,
        descriptor: String?,
        visible: Boolean
    ): AnnotationVisitor? {
        return super.visitTryCatchAnnotation(typeRef, typePath, descriptor, visible)
    }
    override fun visitMethodInsn(opcode: Int, owner: String?, name: String?, descriptor: String?) {
        super.visitMethodInsn(opcode, owner, name, descriptor)
        println("visitMethodInsn, opcode = ${opcode.opCode2String()}, owner = $owner, name = $name, descriptor = $descriptor")
    }
    /**
     *  visitMethodInsn:拜访办法操作指令
     */
    override fun visitMethodInsn(
        opcode: Int,
        owner: String?,
        name: String?,
        descriptor: String?,
        isInterface: Boolean
    ) {
        super.visitMethodInsn(opcode, owner, name, descriptor, isInterface)
        println("visitMethodInsn, opcode = ${opcode.opCode2String()}, owner = $owner, name = $name, descriptor = $descriptor")
    }
    /**
     * 当ASM在解析字节码时遇到一个不需求操作数的指令时(例如RETURN或NOP指令),、
     * 它将调用MethodVisitor的visitInsn办法,并传递该指令的操作码(opcode)作为参数。
     */
    override fun visitInsn(opcode: Int) {
        super.visitInsn(opcode)
        println("visitInsn, opcode = ${opcode.opCode2String()}")
    }
    override fun visitInsnAnnotation(
        typeRef: Int,
        typePath: TypePath?,
        descriptor: String?,
        visible: Boolean
    ): AnnotationVisitor? {
        println("visitInsnAnnotation")
        return super.visitInsnAnnotation(typeRef, typePath, descriptor, visible)
    }
    override fun visitParameterAnnotation(
        parameter: Int,
        descriptor: String?,
        visible: Boolean
    ): AnnotationVisitor? {
        println("visitParameterAnnotation")
        return super.visitParameterAnnotation(parameter, descriptor, visible)
    }
    override fun visitIincInsn(`var`: Int, increment: Int) {
        super.visitIincInsn(`var`, increment)
        println("visitIincInsn")
    }
    override fun visitLineNumber(line: Int, start: Label?) {
        super.visitLineNumber(line, start)
        println("visitLineNumber")
    }
    override fun visitLocalVariableAnnotation(
        typeRef: Int,
        typePath: TypePath?,
        start: Array<out Label>?,
        end: Array<out Label>?,
        index: IntArray?,
        descriptor: String?,
        visible: Boolean
    ): AnnotationVisitor? {
        println("visitLocalVariableAnnotation")
        return super.visitLocalVariableAnnotation(
            typeRef,
            typePath,
            start,
            end,
            index,
            descriptor,
            visible
        )
    }
    override fun visitTableSwitchInsn(min: Int, max: Int, dflt: Label?, vararg labels: Label?) {
        super.visitTableSwitchInsn(min, max, dflt, *labels)
        println("visitTableSwitchInsn")
    }
    override fun visitEnd() {
        super.visitEnd()
        println("visitEnd")
    }
    override fun visitLocalVariable(
        name: String?,
        descriptor: String?,
        signature: String?,
        start: Label?,
        end: Label?,
        index: Int
    ) {
        super.visitLocalVariable(name, descriptor, signature, start, end, index)
        println(
            "visitLocalVariable, name = $name, descriptor = $descriptor, " +
                    "signature = $signature, start = $start + end = $end, index = $index"
        )
    }
    override fun visitParameter(name: String?, access: Int) {
        super.visitParameter(name, access)
        println("visitParameter, name = $name, access = ${access.opCode2String()}")
    }
    override fun visitAttribute(attribute: Attribute?) {
        super.visitAttribute(attribute)
        println("visitAttribute")
    }
    /**
     * visitFieldInsn:域操作指令,用来加载或许存储目标的 Field
     */
    override fun visitFieldInsn(opcode: Int, owner: String?, name: String?, descriptor: String?) {
        super.visitFieldInsn(opcode, owner, name, descriptor)
        println("visitFieldInsn, opcode = ${opcode.opCode2String()}, owner = $owner, name = $name, descriptor = $descriptor")
    }
    /**
     *  开端拜访办法代码,此处能够增加办法运行前拦截器 ,
     *  这个回调能够认为是办法拜访的开端
     */
    override fun visitCode() {
        super.visitCode()
        println("visitCode")
    }
}

这儿给一个小比如 稍微领会一下methodvisitor

比方说这个结构函数:

asm字节码手册 - 方法(二)

在visit的回调里面 就变成了:

asm字节码手册 - 方法(二)

修正办法的指令—无状况转化

这一末节,咱们来尝试修正一下办法的指令 领会一下如何在字节码中修正一个办法

看一个简略的办法:

public class AddTimerTest {
    public void addTimer() throws InterruptedException {
        Thread.sleep(1000);
    }
}

看一下他的字节码:

asm字节码手册 - 方法(二)

LDC指令是Java虚拟机(JVM)中的一种指令,用于将常量(Constant)推送到栈顶

这个办法的指令特别简略,不解说了

假定此刻咱们想经过字节码的办法 把这个办法修正成:

public class AddTimerTest2 {
    private static long time = 0L;
    public void addTimer() throws InterruptedException {
        time -= System.currentTimeMillis();
        Thread.sleep(1000);
        time += System.currentTimeMillis();
    }
}

看下他的字节码

asm字节码手册 - 方法(二)

LSUB和LADD指令是Java虚拟机(JVM)中的两种指令,分别用于履行长整型(Long)的减法和加法操作。

能够调查一下 这两个办法的字节码指令 差异就在于 最初和结束 增加了4条指令,咱们要做的便是 经过字节码修正的办法 来修正这个办法

fun main() {
    val classReader = ClassReader("com.andoter.asm_example.part3.vivo.AddTimerTest")
    // 第二个参数很重要,能够省略咱们计算max的操作,绝大多数场景咱们用这个参数就能够了
    val classWriter = ClassWriter(classReader, ClassWriter.COMPUTE_MAXS)
    classReader.accept(object : ClassVisitor(Opcodes.ASM7, classWriter) {
        var owner: String? = ""
        override fun visit(
            version: Int,
            access: Int,
            name: String?,
            signature: String?,
            superName: String?,
            interfaces: Array<out String>?
        ) {
            super.visit(version, access, name, signature, superName, interfaces)
            owner = name
        }
        // 不要忘掉 新增一个static的变量
        override fun visitEnd() {
            val fieldVisitor =
                cv.visitField(Opcodes.ACC_PUBLIC + Opcodes.ACC_STATIC, "time", "J", null, null)
            fieldVisitor?.visitEnd()
            super.visitEnd()
        }
        override fun visitMethod(
            access: Int,
            name: String?,
            descriptor: String?,
            signature: String?,
            exceptions: Array<out String>?
        ): MethodVisitor {
            var methodVisitor = cv.visitMethod(access, name, descriptor, signature, exceptions)
            // 不是结构办法才需求增加办法修正逻辑
            if (methodVisitor != null && name != "<init>") {
                methodVisitor = object :MethodVisitor(Opcodes.ASM7, methodVisitor){
                    override fun visitCode() {
                        mv.visitCode()
                        // 在办法的开端处 增加字节码
                        mv.visitFieldInsn(Opcodes.GETSTATIC, owner, "time", "J")
                        mv.visitMethodInsn(
                            Opcodes.INVOKESTATIC,
                            "java/lang/System",
                            "currentTimeMillis",
                            "()J"
                        )
                        mv.visitInsn(Opcodes.LSUB)
                        mv.visitFieldInsn(Opcodes.PUTSTATIC, owner, "time", "J")
                    }
                    override fun visitInsn(opcode: Int) {
                        // 咱们要在return句子之前 增加这段代码
                        if (opcode >= Opcodes.IRETURN && opcode <= Opcodes.RETURN || opcode == Opcodes.ATHROW) {
                            mv.visitFieldInsn(Opcodes.GETSTATIC, owner, "time", "J")
                            mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/System", "currentTimeMillis", "()J")
                            mv.visitInsn(Opcodes.LADD)
                            mv.visitFieldInsn(Opcodes.PUTSTATIC, owner, "time", "J")
                        }
                        mv.visitInsn(opcode)
                    }
                }
            }
            return methodVisitor
        }
    }, ClassReader.SKIP_DEBUG)
    val bytes = classWriter.toByteArray()
    // 实践写文件
    File("asm_example/files2/AddTimerTest.class").sink().buffer().apply {
        write(bytes)
        flush()
        close()
    }
}

咱们当然也能够 实践运行一下这个类 看一下作用:

val myClassloader = MyClassloader()
val loadedClass = myClassloader.loadClassFromFile("/asm_example/files2/AddTimerTest.class", "com.andoter.asm_example.part3.xxx.AddTimerTest")
val method=loadedClass.getMethod("addTimer")
// 获取 time 字段
val field : Field? = loadedClass.getDeclaredField("time")
// 触发办法
method.invoke(loadedClass.newInstance())
// 打印 time 耗时
println("time = ${field?.getLong(loadedClass.newInstance())}")

看一下履行成果 生效的

asm字节码手册 - 方法(二)