问题
I searched on Google how to generate subclass by asm, there seem to be few people who are concerned about this issue. Is this demand itself not suitable? Perhaps the most common thing that asm does is to add extra code before and after the method by AdviceAdapter
.
I think that generating a subclass is also a very common requirement.In fact, it is not easy to do this. How to make all public or protected methods automatically override the parent class's methods, just like HttpServletRequest
' subclass HttpServletRequestWrapper
did.
I use org.ow2.asm:asm:6.2
to implement it as follow:
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
);
}
It works, but it's not very easy. In fact, I think TraceClassVisitor
works easy.
回答1:
You should really see ByteBuddy library - as it is much more suitable for your goal, there is no reason to use such low level library for such generic task.
In ASM you need to do this all alone - you can't just tell it to generate and implement methods for you, ASM library is all about just modifying raw bytecodes, so you need to read all methods of super class i generate bytecode for each of them. You can use asm module to print asm code for you: https://static.javadoc.io/org.ow2.asm/asm/5.2/org/objectweb/asm/util/ASMifier.html . or from command line:
java -classpath asm.jar:asm-util.jar \
org.objectweb.asm.util.ASMifier \
java.lang.Runnable
And this will create working ASM code to generate given class, like this:
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();
}
}
(There is also plugin to Intellij IDEA that allows you to see bytecode and ASMifed code, just look for ASM in plugins)
And to just create a subclass all you need to do is to pass name of that subclass when generating class in ASM, like in example above:
cw.visit(V1_5, ACC_PUBLIC + ACC_ABSTRACT + ACC_INTERFACE,
"java/lang/Runnable", null, "java/lang/Object", null);
java/lang/Object
is superclass that you want to extend, done.
But for methods you need to manually loop over all methods and generate the code you want.
Unless you want to create something less typical or something more generic like own library for generating proxy classes like ByteBuddy - then it is better to use some already existing solutions: ByteBuddy, Javassist, CGLib. (all of them use ASM to generate bytecode too)
回答2:
We use ASM to generate bytecode for the Saxon XSLT/XQuery processor, and generating a subclass of a known abstract class is the way we normally do things. I won't pretend it's easy, and I don't have time to write you a tutorial, and unfortunately I can't publish our code, but I can assure you that it's possible. I don't think you have to do anything special for overriding methods.
We have a class Generator which subcasses ASM's GeneratorAdapter.
We create the class using something like:
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();
and then proceed to generate other methods in the same way.
来源:https://stackoverflow.com/questions/51399073/how-to-generate-subclass-by-asm