asm如何生成子类
how to generate subclass by asm
我在Google上搜索了asm如何生成subclass,好像很少有人关心这个问题。这个需求本身不合适吗?也许 asm 做的最常见的事情就是在 AdviceAdapter
方法前后添加额外的代码。
我觉得生成一个subclass也是一个很常见的requirement.In其实,做到这一点并不容易。如何使所有 public 或受保护的方法自动覆盖父 class 的方法,就像 HttpServletRequest
' subclass HttpServletRequestWrapper
所做的那样。
我用org.ow2.asm:asm:6.2
实现如下:
public class InheritMethodVisitor extends ClassVisitor {
/** the return opcode for different type */
public static final Map<Type, Integer> RETURN_OPCODES = new HashMap<>();
/** the load opcode for different type */
public static final Map<Type, Integer> LOAD_OPCODES = new HashMap<>();
static {
RETURN_OPCODES.put(Type.VOID_TYPE, Opcodes.RETURN);
RETURN_OPCODES.put(Type.BOOLEAN_TYPE, Opcodes.IRETURN);
RETURN_OPCODES.put(Type.BYTE_TYPE, Opcodes.IRETURN);
RETURN_OPCODES.put(Type.SHORT_TYPE, Opcodes.IRETURN);
RETURN_OPCODES.put(Type.INT_TYPE, Opcodes.IRETURN);
RETURN_OPCODES.put(Type.LONG_TYPE, Opcodes.LRETURN);
RETURN_OPCODES.put(Type.FLOAT_TYPE, Opcodes.FRETURN);
RETURN_OPCODES.put(Type.DOUBLE_TYPE, Opcodes.DRETURN);
LOAD_OPCODES.put(Type.BOOLEAN_TYPE, Opcodes.ILOAD);
LOAD_OPCODES.put(Type.BYTE_TYPE, Opcodes.ILOAD);
LOAD_OPCODES.put(Type.SHORT_TYPE, Opcodes.ILOAD);
LOAD_OPCODES.put(Type.INT_TYPE, Opcodes.ILOAD);
LOAD_OPCODES.put(Type.LONG_TYPE, Opcodes.LLOAD);
LOAD_OPCODES.put(Type.FLOAT_TYPE, Opcodes.FLOAD);
LOAD_OPCODES.put(Type.DOUBLE_TYPE, Opcodes.DLOAD);
}
private Class<?> superClass;
private ClassVisitor cv;
public InheritMethodVisitor(int api, ClassVisitor classVisitor, Class<?> superClass) {
super(api);
this.cv = classVisitor;
this.superClass = Objects.requireNonNull(superClass);
}
@Override
public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
//Inherit all public or protect methods
if (Modifier.isStatic(access) || Modifier.isPrivate(access)) return null;
if (name.equals("<init>") || Modifier.isProtected(access)) access = Opcodes.ACC_PUBLIC;
MethodVisitor mv = cv.visitMethod(access, name, descriptor, signature, exceptions);
Type methodType = Type.getMethodType(descriptor);
Type[] argumentTypes = methodType.getArgumentTypes();
if (!name.equals("<init>")) {
//TODO Customize what you want to do
//System.out.println(name)
mv.visitFieldInsn(Opcodes.GETSTATIC, Type.getInternalName(System.class), "out", "Ljava/io/PrintStream;");
mv.visitLdcInsn(name);
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, Type.getInternalName(PrintStream.class), "println", "(Ljava/lang/String;)V", false);
}
//load this
mv.visitVarInsn(Opcodes.ALOAD, 0);
//load arguments
IntStream.range(0, argumentTypes.length).forEach(value ->
mv.visitVarInsn(LOAD_OPCODES.getOrDefault(argumentTypes[value], Opcodes.ALOAD), value + 1)
);
//invoke super.{method}()
mv.visitMethodInsn(
Opcodes.INVOKESPECIAL,
Type.getInternalName(superClass),
name,
descriptor,
false);
//handle return
mv.visitInsn(RETURN_OPCODES.getOrDefault(methodType.getReturnType(), Opcodes.ALOAD));
//for ClassWriter.COMPUTE_FRAMES the max*s not required correct
int maxLocals = argumentTypes.length + 1;
mv.visitMaxs(maxLocals + 2, maxLocals);
mv.visitEnd();
return null;
}}
@Test
public void create() throws Exception {
//generate a subclass of AdviceAdapter to add logger info
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
//generate class name
cw.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC, "com/github/Generated",
null, Type.getInternalName(AdviceAdapter.class), null);
//generate overwrite methods
new ClassReader(AdviceAdapter.class.getName())
.accept(
new InheritMethodVisitor(Opcodes.ASM6, cw, AdviceAdapter.class),
ClassReader.EXPAND_FRAMES
);
//TODO AdviceAdapter.class.getSuperclass() not supported
//end
cw.visitEnd();
//save to file
byte[] bytes = cw.toByteArray();
Files.write(Paths.get(getClass().getResource("/com/github").getPath(), "Generated.class"), bytes);
//show use of it
ClassReader classReader = new ClassReader(AdviceAdapter.class.getName());
classReader.accept(
new ClassVisitor(Opcodes.ASM6, new ClassWriter(classReader, ClassWriter.COMPUTE_FRAMES)) {
public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
MethodVisitor methodVisitor = super.visitMethod(access, name, descriptor, signature, exceptions);
try {
Class<?> aClass = Class.forName("com.github.Generated");
return (MethodVisitor) aClass.getConstructors()[0].newInstance(Opcodes.ASM6, methodVisitor, access, name, descriptor);
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | InvocationTargetException e) {
throw new IllegalStateException(e);
}
}
},
ClassReader.EXPAND_FRAMES
);
}
可行,但不是很容易。事实上,我认为 TraceClassVisitor
工作起来很简单。
您真的应该看看 ByteBuddy 库 - 因为它更适合您的目标,所以没有理由为此类通用任务使用如此低级的库。
在 ASM 中,您需要单独完成所有这些工作 - 您不能只告诉它为您生成和实现方法,ASM 库只是修改原始字节码,因此您需要阅读 super class 我为他们每个人生成字节码。您可以使用 asm 模块为您打印 asm 代码: https://static.javadoc.io/org.ow2.asm/asm/5.2/org/objectweb/asm/util/ASMifier.html 。
或从命令行:
java -classpath asm.jar:asm-util.jar \
org.objectweb.asm.util.ASMifier \
java.lang.Runnable
这将创建工作的 ASM 代码以生成给定的 class,如下所示:
package asm.java.lang;
import org.objectweb.asm.*;
public class RunnableDump implements Opcodes {
public static byte[] dump() throws Exception {
ClassWriter cw = new ClassWriter(0);
FieldVisitor fv;
MethodVisitor mv;
AnnotationVisitor av0;
cw.visit(V1_5, ACC_PUBLIC + ACC_ABSTRACT + ACC_INTERFACE,
"java/lang/Runnable", null, "java/lang/Object", null);
{
mv = cw.visitMethod(ACC_PUBLIC + ACC_ABSTRACT, "run", "()V",
null, null);
mv.visitEnd();
}
cw.visitEnd();
return cw.toByteArray();
}
}
(Intellij IDEA也有插件可以查看字节码和ASMifed代码,在插件里找ASM即可)
要创建一个子class,您需要做的就是在 ASM 中生成 class 时传递该子class 的名称,如上例所示:
cw.visit(V1_5, ACC_PUBLIC + ACC_ABSTRACT + ACC_INTERFACE,
"java/lang/Runnable", null, "java/lang/Object", null);
java/lang/Object
是您想扩展的超级class,完成。
但是对于方法,你需要手动循环遍历所有方法并生成你想要的代码。
除非你想创建一些不太典型的东西或一些更通用的东西,比如自己的库来生成像 ByteBuddy 这样的代理 classes——那么最好使用一些已经存在的解决方案:ByteBuddy、Javassist、CGLib。 (他们也都使用ASM生成字节码)
我们使用 ASM 为 Saxon XSLT/XQuery 处理器生成字节码,并且生成已知抽象 class 的子 class 是我们通常做事的方式。我不会假装这很容易,我没有时间给你写教程,不幸的是我不能发布我们的代码,但我可以向你保证这是可能的。我认为您不必为覆盖方法做任何特别的事情。
我们有一个 class 生成器,它子类化了 ASM 的生成器适配器。
我们使用类似以下内容创建 class:
String className = "xxx" + compiler.getUniqueNumber();
ClassVisitor cv = new ClassWriter(flags);
cv.visit(Opcodes.V1_5, Opcodes.ACC_PUBLIC, className, null,
"com/saxonica/ee/bytecode/iter/CompiledBlockIterator", new String[]{});
// CompiledBlockIterator is the superclass name
// generate constructor
Method m = Method.getMethod("void <init> ()");
Generator ga = new Generator(Opcodes.ACC_PUBLIC, m, false, cv);
ga.loadThis();
ga.invokeConstructor(Type.getType(CompiledBlockIterator.class), m);
ga.returnValue();
ga.endMethod();
然后以同样的方式继续生成其他方法。
我在Google上搜索了asm如何生成subclass,好像很少有人关心这个问题。这个需求本身不合适吗?也许 asm 做的最常见的事情就是在 AdviceAdapter
方法前后添加额外的代码。
我觉得生成一个subclass也是一个很常见的requirement.In其实,做到这一点并不容易。如何使所有 public 或受保护的方法自动覆盖父 class 的方法,就像 HttpServletRequest
' subclass HttpServletRequestWrapper
所做的那样。
我用org.ow2.asm:asm:6.2
实现如下:
public class InheritMethodVisitor extends ClassVisitor {
/** the return opcode for different type */
public static final Map<Type, Integer> RETURN_OPCODES = new HashMap<>();
/** the load opcode for different type */
public static final Map<Type, Integer> LOAD_OPCODES = new HashMap<>();
static {
RETURN_OPCODES.put(Type.VOID_TYPE, Opcodes.RETURN);
RETURN_OPCODES.put(Type.BOOLEAN_TYPE, Opcodes.IRETURN);
RETURN_OPCODES.put(Type.BYTE_TYPE, Opcodes.IRETURN);
RETURN_OPCODES.put(Type.SHORT_TYPE, Opcodes.IRETURN);
RETURN_OPCODES.put(Type.INT_TYPE, Opcodes.IRETURN);
RETURN_OPCODES.put(Type.LONG_TYPE, Opcodes.LRETURN);
RETURN_OPCODES.put(Type.FLOAT_TYPE, Opcodes.FRETURN);
RETURN_OPCODES.put(Type.DOUBLE_TYPE, Opcodes.DRETURN);
LOAD_OPCODES.put(Type.BOOLEAN_TYPE, Opcodes.ILOAD);
LOAD_OPCODES.put(Type.BYTE_TYPE, Opcodes.ILOAD);
LOAD_OPCODES.put(Type.SHORT_TYPE, Opcodes.ILOAD);
LOAD_OPCODES.put(Type.INT_TYPE, Opcodes.ILOAD);
LOAD_OPCODES.put(Type.LONG_TYPE, Opcodes.LLOAD);
LOAD_OPCODES.put(Type.FLOAT_TYPE, Opcodes.FLOAD);
LOAD_OPCODES.put(Type.DOUBLE_TYPE, Opcodes.DLOAD);
}
private Class<?> superClass;
private ClassVisitor cv;
public InheritMethodVisitor(int api, ClassVisitor classVisitor, Class<?> superClass) {
super(api);
this.cv = classVisitor;
this.superClass = Objects.requireNonNull(superClass);
}
@Override
public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
//Inherit all public or protect methods
if (Modifier.isStatic(access) || Modifier.isPrivate(access)) return null;
if (name.equals("<init>") || Modifier.isProtected(access)) access = Opcodes.ACC_PUBLIC;
MethodVisitor mv = cv.visitMethod(access, name, descriptor, signature, exceptions);
Type methodType = Type.getMethodType(descriptor);
Type[] argumentTypes = methodType.getArgumentTypes();
if (!name.equals("<init>")) {
//TODO Customize what you want to do
//System.out.println(name)
mv.visitFieldInsn(Opcodes.GETSTATIC, Type.getInternalName(System.class), "out", "Ljava/io/PrintStream;");
mv.visitLdcInsn(name);
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, Type.getInternalName(PrintStream.class), "println", "(Ljava/lang/String;)V", false);
}
//load this
mv.visitVarInsn(Opcodes.ALOAD, 0);
//load arguments
IntStream.range(0, argumentTypes.length).forEach(value ->
mv.visitVarInsn(LOAD_OPCODES.getOrDefault(argumentTypes[value], Opcodes.ALOAD), value + 1)
);
//invoke super.{method}()
mv.visitMethodInsn(
Opcodes.INVOKESPECIAL,
Type.getInternalName(superClass),
name,
descriptor,
false);
//handle return
mv.visitInsn(RETURN_OPCODES.getOrDefault(methodType.getReturnType(), Opcodes.ALOAD));
//for ClassWriter.COMPUTE_FRAMES the max*s not required correct
int maxLocals = argumentTypes.length + 1;
mv.visitMaxs(maxLocals + 2, maxLocals);
mv.visitEnd();
return null;
}}
@Test
public void create() throws Exception {
//generate a subclass of AdviceAdapter to add logger info
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
//generate class name
cw.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC, "com/github/Generated",
null, Type.getInternalName(AdviceAdapter.class), null);
//generate overwrite methods
new ClassReader(AdviceAdapter.class.getName())
.accept(
new InheritMethodVisitor(Opcodes.ASM6, cw, AdviceAdapter.class),
ClassReader.EXPAND_FRAMES
);
//TODO AdviceAdapter.class.getSuperclass() not supported
//end
cw.visitEnd();
//save to file
byte[] bytes = cw.toByteArray();
Files.write(Paths.get(getClass().getResource("/com/github").getPath(), "Generated.class"), bytes);
//show use of it
ClassReader classReader = new ClassReader(AdviceAdapter.class.getName());
classReader.accept(
new ClassVisitor(Opcodes.ASM6, new ClassWriter(classReader, ClassWriter.COMPUTE_FRAMES)) {
public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
MethodVisitor methodVisitor = super.visitMethod(access, name, descriptor, signature, exceptions);
try {
Class<?> aClass = Class.forName("com.github.Generated");
return (MethodVisitor) aClass.getConstructors()[0].newInstance(Opcodes.ASM6, methodVisitor, access, name, descriptor);
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | InvocationTargetException e) {
throw new IllegalStateException(e);
}
}
},
ClassReader.EXPAND_FRAMES
);
}
可行,但不是很容易。事实上,我认为 TraceClassVisitor
工作起来很简单。
您真的应该看看 ByteBuddy 库 - 因为它更适合您的目标,所以没有理由为此类通用任务使用如此低级的库。
在 ASM 中,您需要单独完成所有这些工作 - 您不能只告诉它为您生成和实现方法,ASM 库只是修改原始字节码,因此您需要阅读 super class 我为他们每个人生成字节码。您可以使用 asm 模块为您打印 asm 代码: https://static.javadoc.io/org.ow2.asm/asm/5.2/org/objectweb/asm/util/ASMifier.html 。 或从命令行:
java -classpath asm.jar:asm-util.jar \
org.objectweb.asm.util.ASMifier \
java.lang.Runnable
这将创建工作的 ASM 代码以生成给定的 class,如下所示:
package asm.java.lang;
import org.objectweb.asm.*;
public class RunnableDump implements Opcodes {
public static byte[] dump() throws Exception {
ClassWriter cw = new ClassWriter(0);
FieldVisitor fv;
MethodVisitor mv;
AnnotationVisitor av0;
cw.visit(V1_5, ACC_PUBLIC + ACC_ABSTRACT + ACC_INTERFACE,
"java/lang/Runnable", null, "java/lang/Object", null);
{
mv = cw.visitMethod(ACC_PUBLIC + ACC_ABSTRACT, "run", "()V",
null, null);
mv.visitEnd();
}
cw.visitEnd();
return cw.toByteArray();
}
}
(Intellij IDEA也有插件可以查看字节码和ASMifed代码,在插件里找ASM即可)
要创建一个子class,您需要做的就是在 ASM 中生成 class 时传递该子class 的名称,如上例所示:
cw.visit(V1_5, ACC_PUBLIC + ACC_ABSTRACT + ACC_INTERFACE,
"java/lang/Runnable", null, "java/lang/Object", null);
java/lang/Object
是您想扩展的超级class,完成。
但是对于方法,你需要手动循环遍历所有方法并生成你想要的代码。
除非你想创建一些不太典型的东西或一些更通用的东西,比如自己的库来生成像 ByteBuddy 这样的代理 classes——那么最好使用一些已经存在的解决方案:ByteBuddy、Javassist、CGLib。 (他们也都使用ASM生成字节码)
我们使用 ASM 为 Saxon XSLT/XQuery 处理器生成字节码,并且生成已知抽象 class 的子 class 是我们通常做事的方式。我不会假装这很容易,我没有时间给你写教程,不幸的是我不能发布我们的代码,但我可以向你保证这是可能的。我认为您不必为覆盖方法做任何特别的事情。
我们有一个 class 生成器,它子类化了 ASM 的生成器适配器。
我们使用类似以下内容创建 class:
String className = "xxx" + compiler.getUniqueNumber();
ClassVisitor cv = new ClassWriter(flags);
cv.visit(Opcodes.V1_5, Opcodes.ACC_PUBLIC, className, null,
"com/saxonica/ee/bytecode/iter/CompiledBlockIterator", new String[]{});
// CompiledBlockIterator is the superclass name
// generate constructor
Method m = Method.getMethod("void <init> ()");
Generator ga = new Generator(Opcodes.ACC_PUBLIC, m, false, cv);
ga.loadThis();
ga.invokeConstructor(Type.getType(CompiledBlockIterator.class), m);
ga.returnValue();
ga.endMethod();
然后以同样的方式继续生成其他方法。