how to generate subclass by asm

泪湿孤枕 提交于 2020-07-19 11:30:14

问题


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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!