java ASM创建一个简单的类

准备工作

  • 建议使用IDEA
  • 导入ASM依赖

gradle方式

// https://mvnrepository.com/artifact/org.ow2.asm/asm
implementation 'org.ow2.asm:asm:9.2'

maven方式

<!-- https://mvnrepository.com/artifact/org.ow2.asm/asm -->
<dependency>
    <groupId>org.ow2.asm</groupId>
    <artifactId>asm</artifactId>
    <version>9.2</version>
</dependency>

具体流程

创建基本的类

先准备一个ClassLoader用于加载构建好的代码

public class SimpleClassLoader extends ClassLoader {

    private SimpleClassLoader() {}

    private static final SimpleClassLoader instance = new SimpleClassLoader();

    public static SimpleClassLoader getInstance() {
        return instance;
    }


    public Class<?> forBytes(String name, byte[] bytes) {
        return super.defineClass(name, bytes, 0, bytes.length);
    }

}

创建具体实现

首先我们先通过正常的方式写一个类

public class CreateSimpleClassExample {

    public CreateSimpleClassExample() {
        System.out.println("Hello word!");
    }
}

然后使用IDEA的View => Show Bytecode查看正常类的字节码,下面是构造器的字节码

  public <init>()V
   L0
    LINENUMBER 15 L0
    ALOAD 0
    INVOKESPECIAL java/lang/Object.<init> ()V
   L1
    LINENUMBER 16 L1
    GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
    LDC "Hello word!"
    INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
   L2
    LINENUMBER 17 L2
    RETURN
   L3
    LOCALVARIABLE this Lrunstatic/asmexample/CreateSimpleClassExample; L0 L3 0
    MAXSTACK = 2
    MAXLOCALS = 1

拿到字节码后我们就可以使用ASM照葫芦画瓢, 每行看起如ALOAD这个我们如何知道谁能操作他,这个时候我们用到org.objectweb.asm.Opcodes这个常量类, 用搜索去匹配他的注释如ALOAD其实对应的就是visitVarInsn这个方法,具体用的时候就可以通过这种方式去找方法操作!
image.png

public static void main(String[] args) throws Exception {
    // 这里面给0即刻 ClassWriter.COMPUTE_MAXS和ClassWriter.COMPUTE_FRAMES暂时不需要用
    ClassWriter cw = new ClassWriter(0);

    // 构造
    cw.visit(
            V1_8,                                      // 生成的源码版本
            ACC_PUBLIC,                                // 生成的作用域
            "runstatic/asmexample/SimpleClass",  // 生成的Class名称
            null,                             // 泛型信息会传入此参数 现在不考虑泛型
            Type.getInternalName(Object.class),        // 这个是他的父类, 没有就给个Object.class就好
            new String[0]                              // 这个是实现的接口, 没有就不传
    );


    // 创建一个基本的构造器
    // 注意: 构造器也是方法
    final MethodVisitor mv = cw.visitMethod(
            ACC_PUBLIC,                                // 生成构造器的作用域
            "<init>",                            // 构造器都叫<init> 而不是类名这里面需要注意
            Type.getMethodDescriptor(Type.VOID_TYPE),  // 返回值但构造器没有返回值 ()V
            null,                             // 泛型没有就给null
            new String[0]                              // 抛出的异常列表
    );

    // 访问开始
    mv.visitCode();

    final Label start = new Label();
    mv.visitLabel(start);
    // 拿出this变量
    mv.visitVarInsn(ALOAD, 0);
    mv.visitMethodInsn(
            INVOKESPECIAL,
            Type.getInternalName(Object.class),
            "<init>",                                 // 方法名
            Type.getMethodDescriptor(Type.VOID_TYPE),       // 描述 ()V
            false                                  // 是不是接口
    );


    // GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
    mv.visitFieldInsn(
            GETSTATIC,
            Type.getInternalName(System.class),
            "out",
            Type.getDescriptor(PrintStream.class)

    );

    // LDC "Hello word!"
    mv.visitLdcInsn("hello word!");

    // INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
    mv.visitMethodInsn(
            INVOKEVIRTUAL,
            Type.getInternalName(PrintStream.class),
            "println",
            Type.getMethodDescriptor(Type.VOID_TYPE, Type.getType(String.class)),
            false
    );

    mv.visitInsn(RETURN);

    final Label end = new Label();

    mv.visitLabel(end);
    mv.visitLocalVariable("this", "Lrunstatic/asmexample/SimpleClass;", null, start, end, 0);
    mv.visitMaxs(2, 1);

    // 访问结束
    mv.visitEnd();

    final byte[] bytes = cw.toByteArray();

    // final OutputStream outputStream = new FileOutputStream(new File("./SimpleClass.class"));
    // outputStream.write(bytes);

    final Class<?> aClass = SimpleClassLoader.getInstance().forBytes("runstatic.asmexample.SimpleClass", bytes);

    final Object instance = aClass.getDeclaredConstructor().newInstance();
    System.out.println(instance);
}

以上代码执行完毕就会拿到你的类了, 输出结果:

hello word!
runstatic.asmexample.SimpleClass@42d3bd8b