7

假设我有一个抽象类:

abstract class Foo extends Bar {

    public abstract int foo();

}

我想在运行时扩展以创建一个 Class 对象。希望我可以有一个动态生成的类:

class FooImpl extends Foo {

    @Override
    public int foo() {
        return 5;
    }

}

这将由一个 Class 对象表示,然后我可以使用反射来创建新的实例。关键是我想在运行时决定方法 foo() 的返回值。我的想法是使用 ASM 为类创建字节码,然后在 ClassLoader 对象上使用反射来定义类。

使用 ASM,然后在生成的字节上反射方法 ClassLoader#defineClass 是在运行时使用非硬编码值实现抽象方法的最佳方式吗?

如果是,我将如何去做。我的直觉是使用 ASMifierClassVisitor,但我不太确定这样做的确切方法。我知道如果所有其他方法都失败了,我可以手动执行定义特定类所需的 JVM 指令,但我觉得必须有更简单的方法。

如果不是,最好的方法是什么,我将如何使用最好的方法?

编辑:我检查了所有的答案,我认为它们都不是我想要的。我最终创建了一个小型实现,用于实现我在 ASM 中所讨论的内容。我想我应该把它贴在这里:

import org.objectweb.asm.*;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.HashMap;

/**
 * Created by IntelliJ IDEA.
 * User: Matt
 * Date: 9/17/11
 * Time: 12:42 PM
 */
public class OverrideClassAdapter extends ClassAdapter {

    private final HashMap<String, Object> code;
    private final String className;

    private final ClassWriter writer;

    private String superName;

    public OverrideClassAdapter(ClassWriter writer, String className, Queue<int[]> constructorCode, HashMap<String, Object> code) {
        super(writer);
        this.writer = writer;
        this.className = className;
        this.code = code;
    }

    @Override
    public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
        this.superName = name;
        if((access & Opcodes.ACC_ABSTRACT) != 0)
            access &= ~Opcodes.ACC_ABSTRACT;
        if((access & Opcodes.ACC_INTERFACE) != 0)
            access &= ~Opcodes.ACC_INTERFACE;
        cv.visit(version, access, className, signature, name, null);
    }

    @Override
    public void visitSource(String source, String debug) {
    }

    @Override
    public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
        boolean isAbstract = (access & Opcodes.ACC_ABSTRACT) != 0;
        if(isAbstract)
            access &= ~Opcodes.ACC_ABSTRACT;
        MethodWriter mw = (MethodWriter) cv.visitMethod(access, name, desc, signature, exceptions);
        Object value = code.get(name);
        if(isAbstract || value != null) {
            if(value instanceof BytecodeValue) {
                BytecodeValue returnableValue = (BytecodeValue) value;
                int[] byteCode = new int[returnableValue.getValueCode().length + 1];
                System.arraycopy(returnableValue.getValueCode(), 0, byteCode, 0, returnableValue.getValueCode().length);
                if(returnableValue.getValueCode().length > 1 && returnableValue.getValueCode()[1] == 0) {
                    byteCode[1] = writer.newConst(returnableValue.getValue());
                }
                byteCode[byteCode.length - 1] = returnableValue.getReturnCode();
                value = byteCode;
            }
            return new OverrideMethodAdapter(mw, (int[]) value);
        }
        return mw;
    }

    private class OverrideMethodAdapter extends MethodAdapter {

        private final int[] code;

        private final MethodWriter writer;

        public OverrideMethodAdapter(MethodWriter writer, int[] code) {
            super(writer);
            this.writer = writer;
            this.code = code;
        }

        @Override
        public void visitEnd() {
            try {
                Field code = MethodWriter.class.getDeclaredField("code");
                code.setAccessible(true);
                ByteVector bytes = new ByteVector();
                for(int b : this.code)
                    bytes.putByte(b);
                code.set(writer, bytes);
            } catch (Exception e) {
              e.printStackTrace();
            }
        }
    }

    public static byte[] extendClassBytes(Class clazz, String className, HashMap<String, Object> methodImpls) throws IOException {
        ClassReader cr = new ClassReader(clazz.getName());
        ClassWriter cw = new ClassWriter(0);
        cr.accept(new OverrideClassAdapter(cw, className, methodImpls), ClassReader.SKIP_DEBUG);
        cr = new ClassReader(cw.toByteArray());
        cw = new ClassWriter(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES);
        cr.accept(cw, ClassReader.SKIP_DEBUG);
        //CheckClassAdapter.verify(new org.objectweb.asm.ClassReader(cw.toByteArray()), true, new PrintWriter(System.out));
        /*File file = new File(className + ".class");
        new FileOutputStream(file).write(cw.toByteArray());*/
        return cw.toByteArray();
    }


    public static Class extendClass(Class clazz, String className, HashMap<String, Object> methodImpls) throws IOException {
        return defineClass(extendClassBytes(clazz, className, methodImpls), className);
    }

    public static Class defineClass(byte[] code, String name) {
        try {
            Method method = ClassLoader.class.getDeclaredMethod("defineClass", String.class, byte[].class, int.class, int.class);
            method.setAccessible(true);
            return (Class) method.invoke(Thread.currentThread().getContextClassLoader(), name, code, 0, code.length);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }
}
4

3 回答 3

6

您可能想看看使用CGLib。它可以做 Java 的动态代理可以做的事情,除了抽象类和接口,它也有一个类似于 java.lang.reflect.Proxy 的 API 来做这件事。无论如何,CGLib 在幕后使用 ASM,但是通过使用 CGLib,您不必直接制作字节码。

以下是如何使用 CGLib 执行此操作的示例:

package cglibtest;

import java.lang.reflect.Method;

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

public class CGLibTest
{
    public static void main(String... args)
    {
        MyAbstract instance = (MyAbstract)Enhancer.create(MyAbstract.class, new MyInterceptor(42));
        System.out.println("Value from instance: " + instance.valueMethod());
    }

    public static class MyInterceptor implements MethodInterceptor
    {
        private final Object constantValue;

        public MyInterceptor(Object constantValue)
        {
            this.constantValue = constantValue;
        }

        @Override
        public Object intercept(Object obj, Method method, Object[] args,
                MethodProxy proxy) throws Throwable
        {
            if ("valueMethod".equals(method.getName()))
                return(constantValue);
            else
                return(null);
        }
    }

    public static abstract class MyAbstract
    {
        public abstract int valueMethod();
    }
}
于 2011-09-15T04:07:43.463 回答
1

是什么阻止您从 say 属性中读取值 5 并将其返回?这太简单了,我想你必须有一些比返回你想要在这里完成的 int 更复杂的东西。我同意上面的帖子,即在运行时生成类会非常昂贵。如果您事先知道您的业务逻辑,您可以应用工厂模式在运行时加载所需的已定义接口的实现。这就是 JDBC 库的工作方式。

如果您事先不知道业务逻辑并且有很多业务逻辑,那么您可能会受益于使用现成的规则引擎来处理逻辑并将结果返回给您的 Java 程序。如果规则引擎经常更改,则在规则引擎中维护此逻辑要容易得多。

于 2011-09-15T04:33:18.053 回答
0

Yes, that approach should work. But it will be expensive if you do a lot of class generation. (We are probably talking about hundreds of thousands of instructions to generate the bytecode file and then load it. And then there's the memory need to represent the class when it is loaded.)

Another approach (also expensive) is to generate source code and compile and load it at runtime.

Finally, you should consider the approach of making the logic of the objects table-driven or implementing it using some kind of interpreter. If you actually need to have different classes, you could wrap this up using Java's dynamic proxy class mechanism; e.g. see java.lang.reflect.Proxy

于 2011-09-15T03:35:03.403 回答