java ASM创建一个简单的枚举类

准备工作

请看 java ASM创建一个简单的类

了解Enum背后的东西

假如我建立一个Color的枚举类

public enum Color {
    RED, GREEN;
}

他只有俩个分别为RED和GREEN , java在编译后是将RED和GREEN声明了public static final xxx的格式内部有一个$values的变量存储了RED和GREEN, 并且在编译后会有一个默认的构造器去调用Enum(String, Int)的构造器, 并且多出了一个values(), valueOf()等静态方法;

查看Color的字节码

通过IDEA的View -> Show ByteCode功能查看Color字节码
image.png

// class version 52.0 (52)
// access flags 0x4031
// signature Ljava/lang/Enum<Lrunstatic/asmexample/CreateSimpleEnum$Color;>;
// declaration: runstatic/asmexample/CreateSimpleEnum$Color extends java.lang.Enum<runstatic.asmexample.CreateSimpleEnum$Color>
public final enum runstatic/asmexample/CreateSimpleEnum$Color extends java/lang/Enum {

  // compiled from: CreateSimpleEnum.java
  // access flags 0x4019
  public final static enum INNERCLASS runstatic/asmexample/CreateSimpleEnum$Color runstatic/asmexample/CreateSimpleEnum Color

  // access flags 0x4019
  public final static enum Lrunstatic/asmexample/CreateSimpleEnum$Color; RED

  // access flags 0x4019
  public final static enum Lrunstatic/asmexample/CreateSimpleEnum$Color; GREEN

  // access flags 0x101A
  private final static synthetic [Lrunstatic/asmexample/CreateSimpleEnum$Color; $VALUES

  // access flags 0x9
  public static values()[Lrunstatic/asmexample/CreateSimpleEnum$Color;
   L0
    LINENUMBER 12 L0
    GETSTATIC runstatic/asmexample/CreateSimpleEnum$Color.$VALUES : [Lrunstatic/asmexample/CreateSimpleEnum$Color;
    INVOKEVIRTUAL [Lrunstatic/asmexample/CreateSimpleEnum$Color;.clone ()Ljava/lang/Object;
    CHECKCAST [Lrunstatic/asmexample/CreateSimpleEnum$Color;
    ARETURN
    MAXSTACK = 1
    MAXLOCALS = 0

  // access flags 0x9
  public static valueOf(Ljava/lang/String;)Lrunstatic/asmexample/CreateSimpleEnum$Color;
   L0
    LINENUMBER 12 L0
    LDC Lrunstatic/asmexample/CreateSimpleEnum$Color;.class
    ALOAD 0
    INVOKESTATIC java/lang/Enum.valueOf (Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum;
    CHECKCAST runstatic/asmexample/CreateSimpleEnum$Color
    ARETURN
   L1
    LOCALVARIABLE name Ljava/lang/String; L0 L1 0
    MAXSTACK = 2
    MAXLOCALS = 1

  // access flags 0x2
  // signature ()V
  // declaration: void <init>()
  private <init>(Ljava/lang/String;I)V
   L0
    LINENUMBER 12 L0
    ALOAD 0
    ALOAD 1
    ILOAD 2
    INVOKESPECIAL java/lang/Enum.<init> (Ljava/lang/String;I)V
    RETURN
   L1
    LOCALVARIABLE this Lrunstatic/asmexample/CreateSimpleEnum$Color; L0 L1 0
    MAXSTACK = 3
    MAXLOCALS = 3

  // access flags 0x8
  static <clinit>()V
   L0
    LINENUMBER 13 L0
    NEW runstatic/asmexample/CreateSimpleEnum$Color
    DUP
    LDC "RED"
    ICONST_0
    INVOKESPECIAL runstatic/asmexample/CreateSimpleEnum$Color.<init> (Ljava/lang/String;I)V
    PUTSTATIC runstatic/asmexample/CreateSimpleEnum$Color.RED : Lrunstatic/asmexample/CreateSimpleEnum$Color;
    NEW runstatic/asmexample/CreateSimpleEnum$Color
    DUP
    LDC "GREEN"
    ICONST_1
    INVOKESPECIAL runstatic/asmexample/CreateSimpleEnum$Color.<init> (Ljava/lang/String;I)V
    PUTSTATIC runstatic/asmexample/CreateSimpleEnum$Color.GREEN : Lrunstatic/asmexample/CreateSimpleEnum$Color;
   L1
    LINENUMBER 12 L1
    ICONST_2
    ANEWARRAY runstatic/asmexample/CreateSimpleEnum$Color
    DUP
    ICONST_0
    GETSTATIC runstatic/asmexample/CreateSimpleEnum$Color.RED : Lrunstatic/asmexample/CreateSimpleEnum$Color;
    AASTORE
    DUP
    ICONST_1
    GETSTATIC runstatic/asmexample/CreateSimpleEnum$Color.GREEN : Lrunstatic/asmexample/CreateSimpleEnum$Color;
    AASTORE
    PUTSTATIC runstatic/asmexample/CreateSimpleEnum$Color.$VALUES : [Lrunstatic/asmexample/CreateSimpleEnum$Color;
    RETURN
    MAXSTACK = 4
    MAXLOCALS = 0
}

具体javaASM实现

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 | ACC_ENUM,              // 生成的作用域
            "runstatic/asmexample/SimpleEnumClass",                        // 生成的Class名称
            "Ljava/lang/Enum<Lrunstatic/asmexample/SimpleEnumClass;>;", // 泛型信息
            Type.getInternalName(Enum.class),          // 这个是他的父类, 没有就给个Object.class就好
            new String[0]                              // 这个是实现的接口, 没有就给个空的String数组就好
    );

    // SimpleEnumClass.RED 代表常量
    cw.visitField(
            ACC_PUBLIC | ACC_STATIC | ACC_FINAL | ACC_ENUM,     // 作用域
            "RED",                                               // 常量名称
            "Lrunstatic/asmexample/SimpleEnumClass;",        // 常量类型
            null,                                             // 是否有泛型信息
            null                                                 // 默认值
    );

    // SimpleEnumClass.GREEN 代表常量
    cw.visitField(
            ACC_PUBLIC | ACC_STATIC | ACC_FINAL | ACC_ENUM,   // 作用域
            "GREEN",                                           // 常量名称
            "Lrunstatic/asmexample/SimpleEnumClass;",      // 常量类型
            null,                                           // 是否有泛型信息
            null                                               // 默认值
    );

    // $VALUES 就是存放所有枚举的一个数组
    cw.visitField(
            ACC_PRIVATE | ACC_STATIC | ACC_FINAL | ACC_SYNTHETIC,       // 作用域
            "$VALUES",                                                   // 常量名称
            "[Lrunstatic/asmexample/SimpleEnumClass;",               // 常量类型
            null,                                                     // 是否有泛型信息
            null                                                          // 默认值
    );
    
    // 静态代码块
    // 这里面是做SimpleEnumClass.RED和SimpleEnumClass.GREEN的初始化并存入到SimpleEnumClass.$VALUES数组里面
    {
        final MethodVisitor clinit = cw.visitMethod(
                ACC_STATIC,                                // 众所周知静态代码块是静态的
                "<clinit>",                            // 静态代码块<clinit> 而不是类名这里面需要注意
                Type.getMethodDescriptor(Type.VOID_TYPE),  // 返回值但构造器没有返回值 ()V
                null,                             // 泛型没有就给null
                new String[0]                              // 抛出的异常列表
        );

        clinit.visitCode();

        clinit.visitTypeInsn(NEW, "runstatic/asmexample/SimpleEnumClass");
        clinit.visitInsn(DUP);
        clinit.visitLdcInsn("RED");
        clinit.visitInsn(ICONST_0);
        clinit.visitMethodInsn(
                INVOKESPECIAL,
                "runstatic/asmexample/SimpleEnumClass",
                "<init>",
                Type.getMethodDescriptor(Type.VOID_TYPE, Type.getType(String.class), Type.INT_TYPE),
                false
        );

        clinit.visitFieldInsn(
                PUTSTATIC,
                "runstatic/asmexample/SimpleEnumClass",
                "RED",
                "Lrunstatic/asmexample/SimpleEnumClass;"
        );
        clinit.visitTypeInsn(NEW, "runstatic/asmexample/SimpleEnumClass");
        clinit.visitInsn(DUP);

        clinit.visitLdcInsn("GREEN");
        clinit.visitInsn(ICONST_2);
        clinit.visitMethodInsn(
                INVOKESPECIAL,
                "runstatic/asmexample/SimpleEnumClass",
                "<init>",
                Type.getMethodDescriptor(Type.VOID_TYPE, Type.getType(String.class), Type.INT_TYPE),
                false
        );
        clinit.visitFieldInsn(
                PUTSTATIC,
                "runstatic/asmexample/SimpleEnumClass",
                "GREEN",
                "Lrunstatic/asmexample/SimpleEnumClass;"
        );

        clinit.visitInsn(ICONST_2);
        clinit.visitTypeInsn(ANEWARRAY, "runstatic/asmexample/SimpleEnumClass");
        clinit.visitInsn(DUP);
        clinit.visitInsn(ICONST_0);
        clinit.visitFieldInsn(
                GETSTATIC,
                "runstatic/asmexample/SimpleEnumClass",
                "RED",
                "Lrunstatic/asmexample/SimpleEnumClass;"
        );
        clinit.visitInsn(AASTORE);
        clinit.visitInsn(DUP);
        clinit.visitInsn(ICONST_1);
        clinit.visitFieldInsn(
                GETSTATIC,
                "runstatic/asmexample/SimpleEnumClass",
                "GREEN",
                "Lrunstatic/asmexample/SimpleEnumClass;"
        );

        clinit.visitInsn(AASTORE);

        clinit.visitFieldInsn(
                PUTSTATIC,
                "runstatic/asmexample/SimpleEnumClass",
                "$VALUES",
                "[Lrunstatic/asmexample/SimpleEnumClass;"
        );

        clinit.visitInsn(RETURN);
        clinit.visitMaxs(4, 0);

        clinit.visitEnd();
    }

    // 构造器 
    // 这里面是做 声明一个参数为String Int 的私有构造器
    // 如RED 相当于 new SimpleClassEnum("RED", 1);
    // 第一个参数代表名称第二个代表位置
    {
        // 创建一个基本的构造器
        // 注意: 构造器也是方法
        final MethodVisitor mv = cw.visitMethod(
                ACC_PRIVATE,                                // 生成构造器的作用域
                "<init>",                            // 构造器都叫<init> 而不是类名这里面需要注意
                Type.getMethodDescriptor(Type.VOID_TYPE, Type.getType(String.class), Type.INT_TYPE),  // 返回值但构造器没有返回值 ()V
                null,                             // 泛型没有就给null
                new String[0]                              // 抛出的异常列表
        );

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

        final Label start = new Label();

        mv.visitLabel(start);

        // 拿出this变量
        mv.visitVarInsn(ALOAD, 0);
        mv.visitVarInsn(ALOAD, 1);
        mv.visitVarInsn(ILOAD, 2);
        mv.visitMethodInsn(
                INVOKESPECIAL,
                Type.getInternalName(Enum.class),
                "<init>",                                 // 方法名
                Type.getMethodDescriptor(Type.VOID_TYPE, Type.getType(String.class), Type.INT_TYPE),       // 描述 ()V
                false                                  // 是不是接口
        );

        mv.visitInsn(RETURN);

        final Label end = new Label();

        mv.visitLabel(end);
        mv.visitLocalVariable("this", "Lrunstatic/asmexample/SimpleEnumClass;", null, start, end, 0);
        mv.visitMaxs(3, 3);

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

    // valueOf() 方法
    // 这里面做了是声明一个参数为String的静态方法
    // 内部调用的还是Enum.valueOf(Class, String)的方法
    // 如SimpleEnumClass.valueOf("RED") === Enum.valueOf(SimpleEnumClass.class, "RED")
    {
        final MethodVisitor valueOf = cw.visitMethod(
                ACC_PUBLIC | ACC_STATIC,
                "valueOf",
                "(Ljava/lang/String;)Lrunstatic/asmexample/SimpleEnumClass;",
                null,
                new String[0]

        );

        valueOf.visitCode();

        valueOf.visitLdcInsn("Lrunstatic/asmexample/SimpleEnumClass;.class");

        valueOf.visitVarInsn(ALOAD, 0);
        valueOf.visitMethodInsn(
                INVOKESTATIC,
                Type.getInternalName(Enum.class),
                "valueOf",
                Type.getMethodDescriptor(Type.getType(Enum.class), Type.getType(Object.class), Type.getType(String.class)),
                false
        );

        valueOf.visitTypeInsn(CHECKCAST, "runstatic/asmexample/SimpleEnumClass");
        valueOf.visitInsn(ARETURN);

        valueOf.visitLocalVariable(
                "name",
                Type.getDescriptor(String.class),
                null,
                new Label(),
                new Label(),
                0
        );

        valueOf.visitMaxs(2, 1);
        valueOf.visitEnd();

    }
    
    // values() 方法
    // 这里面做的相当于返回了SimpleEnumClass.$VALUES这个常量
    {
        final MethodVisitor values = cw.visitMethod(
                ACC_PUBLIC | ACC_STATIC,
                "values",
                "()[Lrunstatic/asmexample/SimpleEnumClass;",
                null,
                new String[0]
        );

        values.visitCode();

        values.visitFieldInsn(
                GETSTATIC,
                "runstatic/asmexample/SimpleEnumClass",
                "$VALUES",
                "[Lrunstatic/asmexample/SimpleEnumClass;"
        );

        values.visitMethodInsn(
                INVOKEVIRTUAL,
                "[Lrunstatic/asmexample/SimpleEnumClass;",
                "clone",
                Type.getMethodDescriptor(Type.getType(Object.class)),
                false

        );

        values.visitTypeInsn(CHECKCAST, "[Lrunstatic/asmexample/SimpleEnumClass;");
        values.visitInsn(ARETURN);

        values.visitMaxs(1, 0);


        values.visitEnd();
    }

    // 类访问结束 (本方法写与不写没什么区别)
    cw.visitEnd();

    // 拿出构建的字节码
    final byte[] bytes = cw.toByteArray();

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

    final Class aClass = SimpleClassLoader.getInstance().forBytes("runstatic.asmexample.SimpleEnumClass", bytes);
    final Enum red = Enum.valueOf(aClass, "RED");
    final Enum green = Enum.valueOf(aClass, "GREEN");
    System.out.println(red);
    System.out.println(green);
    final Method values = aClass.getDeclaredMethod("values");
    System.out.println(Arrays.asList((Enum[]) values.invoke(null)));

}

输出结果

RED
GREEN
[RED, GREEN]