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这个方法,具体用的时候就可以通过这种方式去找方法操作!
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