我们知道Java是静态语言,而python、ruby是动态语言,Java程序一旦写好很难在运行时更改类的行为,而python、ruby可以。不过基于bytecode层面上我们可以做一些手脚,来使Java程序多一些灵活性和Magic,ASM就是这样一个应用广泛的开源库。

可能我们在开发中几乎对ASM没有印象,我记得当时学习cglib反向代理的时候,就需要映入asm.jar

ASM概述

  • ASM是一个Java字节码操纵框架,它可以动态生成类或者增强既有类的功能
  • ASM可以直接产生二进制class文件,也可以在类被加载入虚拟机之前动态改变类行为,ASM从类文件中读入信息后,能够改变类行为,分析类信息,甚至能根据要求生成新类
  • 目前许多框架如cglib、Hibernate、Spring都直接或间接地使用ASM操作字节码

ASM编程模型

    ASM提供了两种编程模型:基于事件形式的编程模型(Core API)和基于树形的编程模型(Tree API)。可以用XML解析做形象的比喻,Core API类似于XML解析的SAX解析,而Tree API类似于XML解析的Document解析。

ASM Core API

    该模型不需要一次性将整个类的结构读取到内存中,因此这种方式更快,需要更少的内存,但这种编程方式难度较大(类似于XML解析的SAX解析)。

    ASM Core API中操纵字节码的功能基于ClassVisitor接口。这个接口中的每个方法对应了class文件中的每一项。ASM提供了三个基于ClassVisitor接口的类来实现class文件的生成和转换。

  • ClassReader: ClassReader解析一个类的class字节码
  • ClassAdapter: ClassAdapt是ClassVisitor的实现类,实现要变化的功能
  • ClassWriter: ClassWriter也是ClassVisitor的实现类,可以用来输出变化后的字节码

ASM Tree API

    该模型需要一次性将一个类的完整结构全部读取到内存当中,所以这种方法需要更多的内存,这种编程方式较简单(类似于XML解析的document解析)。因为Java ASM基本上都是Core API,所以这里不对ASM Tree API进行介绍。

ASMifier工具

前面我们已经知道ASM是字节码操纵框架,那我们要怎样操纵字节码?怎样通过编码的方式改变字节码?

ASM给我们提供了ASMifier工具来帮助开发,可使用ASMifier工具生成ASM结构来对比字节码的变化。

IDEA安装ASM插件

    asm插件中集成了ASMifier工具,我们在idea中安装相关的插件,方便开发。在idea插件安装界面搜索asm,出现如下两个插件,任选一个进行安装(或者两个都装上)

asm java 当当网 java asm全称_ASM


在对应的类上右键

asm java 当当网 java asm全称_asm java 当当网_02

通过ASMifier查看ASM结构

asm java 当当网 java asm全称_asm java 当当网_03

注意:asm是字节码操纵框架,前提是有字节码的存在。所有如果是没有看到ASMifier,请查看java类是否已完成编译。

ASM开发

    下面我们来使用asm来改变.class文件。创建一个简单的案例

public class AsmTest {

    public void m1(){

        System.out.println("now in method m1");
        /**
         * InterruptedException相当于一个局部变量,会影响结果(需将异常注释)
         */
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

    ASMifier生成对应的代码如下:

package asm.cn.crazy.dreamer.asm;

import java.util.*;

import org.objectweb.asm.*;

public class AsmTestDump implements Opcodes {

    public static byte[] dump() throws Exception {

        ClassWriter cw = new ClassWriter(0);
        FieldVisitor fv;
        MethodVisitor mv;
        AnnotationVisitor av0;

        cw.visit(52, ACC_PUBLIC + ACC_SUPER, "cn/crazy/dreamer/asm/AsmTest", null, "java/lang/Object", null);

        cw.visitSource("AsmTest.java", null);

        {
            mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
            mv.visitCode();
            Label l0 = new Label();
            mv.visitLabel(l0);
            mv.visitLineNumber(9, l0);
            mv.visitVarInsn(ALOAD, 0);
            mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
            mv.visitInsn(RETURN);
            Label l1 = new Label();
            mv.visitLabel(l1);
            mv.visitLocalVariable("this", "Lcn/crazy/dreamer/asm/AsmTest;", null, l0, l1, 0);
            mv.visitMaxs(1, 1);
            mv.visitEnd();
        }
        {
            mv = cw.visitMethod(ACC_PUBLIC, "m1", "()V", null, new String[]{"java/lang/Exception"});
            mv.visitCode();
            Label l0 = new Label();
            Label l1 = new Label();
            Label l2 = new Label();
            mv.visitTryCatchBlock(l0, l1, l2, "java/lang/InterruptedException");
            Label l3 = new Label();
            mv.visitLabel(l3);
            mv.visitLineNumber(13, l3);
            mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
            mv.visitLdcInsn("now in method m1");
            mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
            mv.visitLabel(l0);
            mv.visitLineNumber(18, l0);
            mv.visitLdcInsn(new Long(100L));
            mv.visitMethodInsn(INVOKESTATIC, "java/lang/Thread", "sleep", "(J)V", false);
            mv.visitLabel(l1);
            mv.visitLineNumber(21, l1);
            Label l4 = new Label();
            mv.visitJumpInsn(GOTO, l4);
            mv.visitLabel(l2);
            mv.visitLineNumber(19, l2);
            mv.visitFrame(Opcodes.F_SAME1, 0, null, 1, new Object[]{"java/lang/InterruptedException"});
            mv.visitVarInsn(ASTORE, 1);
            Label l5 = new Label();
            mv.visitLabel(l5);
            mv.visitLineNumber(20, l5);
            mv.visitVarInsn(ALOAD, 1);
            mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/InterruptedException", "printStackTrace", "()V", false);
            mv.visitLabel(l4);
            mv.visitLineNumber(22, l4);
            mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
            mv.visitInsn(RETURN);
            Label l6 = new Label();
            mv.visitLabel(l6);
            mv.visitLocalVariable("e", "Ljava/lang/InterruptedException;", null, l5, l4, 1);
            mv.visitLocalVariable("this", "Lcn/crazy/dreamer/asm/AsmTest;", null, l3, l6, 0);
            mv.visitMaxs(2, 2);
            mv.visitEnd();
        }
        cw.visitEnd();

        return cw.toByteArray();
    }
}

    修改java代码,我们添加一个计时器,计算方法执行的时间

public class AsmTest {

    public void m1(){
        long start = System.currentTimeMillis();
        System.out.println("now in method m1!!!");
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        long end = System.currentTimeMillis();
        System.out.println("total time = " + (end - start));
    }
}

ASMifier生成对应的代码如下:

{
    methodVisitor = classWriter.visitMethod(ACC_PUBLIC, "m1", "()V", null, null);
    methodVisitor.visitCode();
    Label label0 = new Label();
    Label label1 = new Label();
    Label label2 = new Label();
    methodVisitor.visitTryCatchBlock(label0, label1, label2, "java/lang/InterruptedException");
    Label label3 = new Label();
    methodVisitor.visitLabel(label3);
    methodVisitor.visitLineNumber(12, label3);
    methodVisitor.visitMethodInsn(INVOKESTATIC, "java/lang/System", "currentTimeMillis", "()J", false);
    methodVisitor.visitVarInsn(LSTORE, 1);
    Label label4 = new Label();
    methodVisitor.visitLabel(label4);
    methodVisitor.visitLineNumber(13, label4);
    methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
    methodVisitor.visitLdcInsn("now in method m1!!!");
    methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
    methodVisitor.visitLabel(label0);
    methodVisitor.visitLineNumber(15, label0);
    methodVisitor.visitLdcInsn(new Long(100L));
    methodVisitor.visitMethodInsn(INVOKESTATIC, "java/lang/Thread", "sleep", "(J)V", false);
    methodVisitor.visitLabel(label1);
    methodVisitor.visitLineNumber(18, label1);
    Label label5 = new Label();
    methodVisitor.visitJumpInsn(GOTO, label5);
    methodVisitor.visitLabel(label2);
    methodVisitor.visitLineNumber(16, label2);
    methodVisitor.visitFrame(Opcodes.F_FULL, 2, new Object[]{"cn/crazy/dreamer/asm/AsmTest", Opcodes.LONG}, 1, new Object[]{"java/lang/InterruptedException"});
    methodVisitor.visitVarInsn(ASTORE, 3);
    Label label6 = new Label();
    methodVisitor.visitLabel(label6);
    methodVisitor.visitLineNumber(17, label6);
    methodVisitor.visitVarInsn(ALOAD, 3);
    methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "java/lang/InterruptedException", "printStackTrace", "()V", false);
    methodVisitor.visitLabel(label5);
    methodVisitor.visitLineNumber(19, label5);
    methodVisitor.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
    methodVisitor.visitMethodInsn(INVOKESTATIC, "java/lang/System", "currentTimeMillis", "()J", false);
    methodVisitor.visitVarInsn(LSTORE, 3);
    Label label7 = new Label();
    methodVisitor.visitLabel(label7);
    methodVisitor.visitLineNumber(20, label7);
    methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
    methodVisitor.visitTypeInsn(NEW, "java/lang/StringBuilder");
    methodVisitor.visitInsn(DUP);
    methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/StringBuilder", "<init>", "()V", false);
    methodVisitor.visitLdcInsn("total time = ");
    methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false);
    methodVisitor.visitVarInsn(LLOAD, 3);
    methodVisitor.visitVarInsn(LLOAD, 1);
    methodVisitor.visitInsn(LSUB);
    methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(J)Ljava/lang/StringBuilder;", false);
    methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "toString", "()Ljava/lang/String;", false);
   methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
   Label label8 = new Label();
   methodVisitor.visitLabel(label8);
   methodVisitor.visitLineNumber(21, label8);
   methodVisitor.visitInsn(RETURN);
   Label label9 = new Label();
   methodVisitor.visitLabel(label9);
   methodVisitor.visitLocalVariable("e", "Ljava/lang/InterruptedException;", null, label6, label5, 3);
   methodVisitor.visitLocalVariable("this", "Lcn/crazy/dreamer/asm/AsmTest;", null, label3, label9, 0);
   methodVisitor.visitLocalVariable("start", "J", null, label4, label9, 1);
   methodVisitor.visitLocalVariable("end", "J", null, label7, label9, 3);
   methodVisitor.visitMaxs(6, 5);
   methodVisitor.visitEnd();
}

    我们用文本比较工具Beyond Compare比较上面两段代码中m1方法部分的代码(红色的为不同部分):

asm java 当当网 java asm全称_ASM_04

asm java 当当网 java asm全称_AOP_05

注:如下代码意义不大,可忽略
Label label7 = new Label();
methodVisitor.visitLabel(label7);
methodVisitor.visitLineNumber(20, label7);

    现在我们用ASM来实现统计程序用时。

    编写ClassVisitor类:

/**
 * @Description 自定义ClassVisitor
 * @Author zhangli
 * @Date 2020/10/13 21:18
 * @Version 1.0
 **/
public class  MyClassVisitor extends ClassVisitor {


	// vision与jdk版本有关
    public MyClassVisitor(int vision, ClassVisitor classVisitor) {
        super(vision, classVisitor);
    }

    /* public MyClassVisitor(ClassVisitor classVisitor) {
        super(Opcodes.ASM7, classVisitor);
    }*/

    @Override
    public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
        cv.visit(version, access, name, signature, superName, interfaces);
    }

    @Override
    public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {

        MethodVisitor mv = cv.visitMethod(access, name, descriptor, signature, exceptions);

        // 对非init方法增加记录方法执行时间的功能
        if(!"<init>".equals(name) && mv != null){

            // 增加功能
           mv = new MyMethodVisitor(mv);


        }

        return mv;
    }

    class MyMethodVisitor extends MethodVisitor{
        public MyMethodVisitor(MethodVisitor methodVisitor) {
            super(Opcodes.ASM5, methodVisitor);
        }

        // 相当于开始
        @Override
        public void visitCode() {
            mv.visitCode();
			// 将上面asm代码前一部分新增的部分添加在visitCode()中
            mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/System", "currentTimeMillis", "()J", false);
            mv.visitVarInsn(Opcodes.LSTORE, 1);
        }

        // 相当于结束
        @Override
        public void visitInsn(int opcode) {

			// 如果是return或异常,说明程序结束
            if(opcode >= Opcodes.IRETURN && opcode <= Opcodes.RETURN
                || opcode == Opcodes.ATHROW){
				//将上面asm代码后一部分新增的添加到visitInsn()中
                mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/System", "currentTimeMillis", "()J", false);
                mv.visitVarInsn(Opcodes.LSTORE, 3);
                Label label7 = new Label();
                mv.visitLabel(label7);
                mv.visitLineNumber(20, label7);
                mv.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
                mv.visitTypeInsn(Opcodes.NEW, "java/lang/StringBuilder");
                mv.visitInsn(Opcodes.DUP);
                mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/StringBuilder", "<init>", "()V", false);
                mv.visitLdcInsn("total time = ");
                mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false);
                mv.visitVarInsn(Opcodes.LLOAD, 3);
                mv.visitVarInsn(Opcodes.LLOAD, 1);
                mv.visitInsn(Opcodes.LSUB);
                mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(J)Ljava/lang/StringBuilder;", false);
                mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "toString", "()Ljava/lang/String;", false);
                mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
            }



            mv.visitInsn(opcode);

        }
    }
}

    生成ASM代码:

/**
 * @Description asm生成对应的ClassVisitor
 * @Author zhangli
 * @Date 2020/10/13 21:46
 * @Version 1.0
 **/
public class Generator {

    public static void main(String[] args) throws Exception {

        ClassReader reader = new ClassReader("cn/crazy/dreamer/asm/AsmTest");

        ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_MAXS);

        ClassVisitor cv = new MyClassVisitor(Opcodes.ASM5,writer);

        reader.accept(cv, ClassReader.SKIP_DEBUG);

        byte[] data = writer.toByteArray();

        // 输出
        File file = new File("F:\\crazy\\freedom\\ifd-jvm\\target\\classes\\cn\\crazy\\dreamer\\asm\\AsmTest.class");

        FileOutputStream fos = new FileOutputStream(file);
        fos.write(data);
        fos.close();
        System.out.println("Generator AsmTest class success");



    }
}

    编写测试类:

/**
 * @Description TODO
 * @Author zhangli
 * @Date 2020/10/13 23:02
 * @Version 1.0
 **/
public class Test {
    public static void main(String[] args) throws Exception {

        AsmTest test = new AsmTest();
        test.m1();
    }
}

    现在我们将java代码还原为:

public class AsmTest {

    public void m1(){

        System.out.println("now in method m1");
        /**
         * InterruptedException相当于一个局部变量,会影响结果(需将异常注释)
         */
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

    执运行程序,出现如下错误:

Exception in thread "main" java.lang.VerifyError: Bad local variable type
Exception Details:
  Location:
    cn/crazy/dreamer/asm/AsmTest.m1()V @46: lload_1
  Reason:
    Type top (current frame, locals[1]) is not assignable to long
  Current Frame:
    bci: @46
    flags: { }
    locals: { 'cn/crazy/dreamer/asm/AsmTest', top, top, long, long_2nd }
    stack: { 'java/io/PrintStream', 'java/lang/StringBuilder', long, long_2nd }
  Bytecode:
    0x0000000: b800 1140 b200 1712 19b6 001f 1400 20b8
    0x0000010: 0027 a700 084c 2bb6 002a b800 1142 b200
    0x0000020: 17bb 002c 59b7 002d 122f b600 3321 1f65
    0x0000030: b600 36b6 003a b600 1fb1               
  Exception Handler Table:
    bci [12, 18] => handler: 21
  Stackmap Table:
    same_locals_1_stack_item_frame(@21,Object[#19])
    same_frame(@26)

	at cn.crazy.dreamer.asm.Test.main(Test.java:12)

    上面的错误提示错误的变量类型,原因:is not assignable to long,即不支持long类型,是因为我们使用ASM添加的是统计耗时的代码,是long类型的,但是实际上是将InterruptedException异常类型赋值给long类型了,所以我们要将异常抛出,而不是try(try的话会将异常也出现在m1方法中)

    将代码改成:

public class AsmTest {

    public void m1() throws Exception{

        System.out.println("now in method m1");
        /**
         * InterruptedException相当于一个局部变量,会影响结果(需将异常注释)
         */
        /*try {*/
            Thread.sleep(100);
        /*} catch (InterruptedException e) {
            e.printStackTrace();
        }*/
    }
}

    再次执行Test.java,运行结果如下

now in method m1
total time = 101

    虽然我们实现了计时功能,但是效果并不理想,代码侵入严重。要怎么完善呢?下面我们用ASM实现类型于AOP的功能

ASM模拟AOP功能

java代码如下

public class JvmTest {

    public void test(){
        System.out.println("init test() method!!!");
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

    我们简单定义一个logger类,用于计时

public class MyTimeLogger {

    private static long start = 0;

    public static void start(){
        start = System.currentTimeMillis();
    }

    public static void end(){
        long end = System.currentTimeMillis();
        System.out.println("total = " + (end - start));
    }
}

    JvmTest.java中test()方法对应的ASM代码如下:

{
            methodVisitor = classWriter.visitMethod(ACC_PUBLIC, "test", "()V", null, null);
            methodVisitor.visitCode();
            Label label0 = new Label();
            Label label1 = new Label();
            Label label2 = new Label();
            methodVisitor.visitTryCatchBlock(label0, label1, label2, "java/lang/InterruptedException");
            Label label3 = new Label();
            methodVisitor.visitLabel(label3);
            methodVisitor.visitLineNumber(12, label3);
            methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
            methodVisitor.visitLdcInsn("init test() method!!!");
            methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
            methodVisitor.visitLabel(label0);
            methodVisitor.visitLineNumber(14, label0);
            methodVisitor.visitLdcInsn(new Long(100L));
            methodVisitor.visitMethodInsn(INVOKESTATIC, "java/lang/Thread", "sleep", "(J)V", false);
            methodVisitor.visitLabel(label1);
            methodVisitor.visitLineNumber(17, label1);
            Label label4 = new Label();
            methodVisitor.visitJumpInsn(GOTO, label4);
            methodVisitor.visitLabel(label2);
            methodVisitor.visitLineNumber(15, label2);
            methodVisitor.visitFrame(Opcodes.F_SAME1, 0, null, 1, new Object[]{"java/lang/InterruptedException"});
            methodVisitor.visitVarInsn(ASTORE, 1);
            Label label5 = new Label();
            methodVisitor.visitLabel(label5);
            methodVisitor.visitLineNumber(16, label5);
            methodVisitor.visitVarInsn(ALOAD, 1);
            methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "java/lang/InterruptedException", "printStackTrace", "()V", false);
            methodVisitor.visitLabel(label4);
            methodVisitor.visitLineNumber(18, label4);
            methodVisitor.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
            methodVisitor.visitInsn(RETURN);
            Label label6 = new Label();
            methodVisitor.visitLabel(label6);
            methodVisitor.visitLocalVariable("e", "Ljava/lang/InterruptedException;", null, label5, label4, 1);
            methodVisitor.visitLocalVariable("this", "Lcn/crazy/dreamer/asm/logger/JvmTest;", null, label3, label6, 0);
            methodVisitor.visitMaxs(2, 2);
            methodVisitor.visitEnd();
        }

    修改JvmTest.java,加入程序计时功能

public class JvmTest {

    public void test(){
        MyTimeLogger.start();
        System.out.println("init test() method!!!");
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        MyTimeLogger.end();
    }
}

    查看此时test()对应ASM代码:

{
            methodVisitor = classWriter.visitMethod(ACC_PUBLIC, "test", "()V", null, null);
            methodVisitor.visitCode();
            Label label0 = new Label();
            Label label1 = new Label();
            Label label2 = new Label();
            methodVisitor.visitTryCatchBlock(label0, label1, label2, "java/lang/InterruptedException");
            Label label3 = new Label();
            methodVisitor.visitLabel(label3);
            methodVisitor.visitLineNumber(12, label3);
            methodVisitor.visitMethodInsn(INVOKESTATIC, "cn/crazy/dreamer/asm/logger/MyTimeLogger", "start", "()V", false);
            Label label4 = new Label();
            methodVisitor.visitLabel(label4);
            methodVisitor.visitLineNumber(13, label4);
            methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
            methodVisitor.visitLdcInsn("init test() method!!!");
            methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
            methodVisitor.visitLabel(label0);
            methodVisitor.visitLineNumber(15, label0);
            methodVisitor.visitLdcInsn(new Long(100L));
            methodVisitor.visitMethodInsn(INVOKESTATIC, "java/lang/Thread", "sleep", "(J)V", false);
            methodVisitor.visitLabel(label1);
            methodVisitor.visitLineNumber(18, label1);
            Label label5 = new Label();
            methodVisitor.visitJumpInsn(GOTO, label5);
            methodVisitor.visitLabel(label2);
            methodVisitor.visitLineNumber(16, label2);
            methodVisitor.visitFrame(Opcodes.F_SAME1, 0, null, 1, new Object[]{"java/lang/InterruptedException"});
            methodVisitor.visitVarInsn(ASTORE, 1);
            Label label6 = new Label();
            methodVisitor.visitLabel(label6);
            methodVisitor.visitLineNumber(17, label6);
            methodVisitor.visitVarInsn(ALOAD, 1);
            methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "java/lang/InterruptedException", "printStackTrace", "()V", false);
            methodVisitor.visitLabel(label5);
            methodVisitor.visitLineNumber(19, label5);
            methodVisitor.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
            methodVisitor.visitMethodInsn(INVOKESTATIC, "cn/crazy/dreamer/asm/logger/MyTimeLogger", "end", "()V", false);
            Label label7 = new Label();
            methodVisitor.visitLabel(label7);
            methodVisitor.visitLineNumber(20, label7);
            methodVisitor.visitInsn(RETURN);
            Label label8 = new Label();
            methodVisitor.visitLabel(label8);
            methodVisitor.visitLocalVariable("e", "Ljava/lang/InterruptedException;", null, label6, label5, 1);
            methodVisitor.visitLocalVariable("this", "Lcn/crazy/dreamer/asm/logger/JvmTest;", null, label3, label8, 0);
            methodVisitor.visitMaxs(2, 2);
            methodVisitor.visitEnd();
        }

通过对比两段代码,我们发现基本上只是增加了MyTimeLogger类的start()和end():

methodVisitor.visitMethodInsn(INVOKESTATIC, "cn/crazy/dreamer/asm/logger/MyTimeLogger", "start", "()V", false);

methodVisitor.visitMethodInsn(INVOKESTATIC, "java/lang/Thread", "sleep", "(J)V", false);

编写ClassVisitor类

public class MyLoggerClassVisitor extends ClassVisitor {
    public MyLoggerClassVisitor(int vision, ClassVisitor classVisitor) {
        super(vision, classVisitor);
    }

    @Override
    public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
        cv.visit(version, access, name, signature, superName, interfaces);
    }

    @Override
    public MethodVisitor visitMethod(int access, String name, String description, String signature, String[] exceptions) {

        MethodVisitor mv = cv.visitMethod(access, name, description, signature, exceptions);

        // 对非init方法增加功能
        if(!"<init>".equals(name) && mv != null){

            // 增加功能
            mv = new MyLoggerMethodVisitor(Opcodes.ASM5, mv);
        }

        return mv;
    }

    class MyLoggerMethodVisitor extends MethodVisitor{
        public MyLoggerMethodVisitor(int vision, MethodVisitor methodVisitor) {
            super(vision, methodVisitor);
        }

        // 相当于开始
        @Override
        public void visitCode() {
            mv.visitCode();

            mv.visitMethodInsn(Opcodes.INVOKESTATIC, "cn/crazy/dreamer/asm/logger/MyTimeLogger", "start", "()V", false);
        }

        // 相当于结束
        @Override
        public void visitInsn(int opcode) {

            // 结束标识
            if(opcode >= Opcodes.IRETURN && opcode <= Opcodes.RETURN
                    || opcode == Opcodes.ATHROW){

                mv.visitMethodInsn(Opcodes.INVOKESTATIC, "cn/crazy/dreamer/asm/logger/MyTimeLogger", "end", "()V", false);

            }

            mv.visitInsn(opcode);
        }
    }
}

    通过ASM生成class文件,我们将测试方法一起实现:

public class MyLoggerGenerator {

    public static void main(String[] args) throws Exception {

        ClassReader reader = new ClassReader("cn/crazy/dreamer/asm/logger/JvmTest");

        ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_MAXS);

        ClassVisitor cv = new MyLoggerClassVisitor(Opcodes.ASM5,writer);

        reader.accept(cv, ClassReader.SKIP_DEBUG);

        byte[] data = writer.toByteArray();

        // 输出(先要编译产生class文件)
        File file = new File("F:\\crazy\\freedom\\ifd-jvm\\target\\classes\\cn\\crazy\\dreamer\\asm\\logger\\JvmTest.class");

        FileOutputStream fos = new FileOutputStream(file);
        fos.write(data);
        fos.close();
        System.out.println("Generator JvmTest class success");


        test();

    }

    public static void test(){
        JvmTest jvmTest = new JvmTest();
        jvmTest.test();
    }
}

    运行结果如下:

Generator JvmTest class success
init test() method!!!
total = 100

    我们通过ASM实现了类似AOP的功能,其他AOP框架的实现都是类似的,当然要复杂的多,我们这里只演示基本原理。