Реализация абстрактных методов во время выполнения?

Скажем, у меня есть абстрактный класс:

abstract class Foo extends Bar {

    public abstract int foo();

}

который я хочу расширить во время выполнения, чтобы создать объект класса. Надеюсь, что у меня может быть динамически сгенерированный класс:

class FooImpl extends Foo {

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

}

который будет представлен объектом класса, и затем я мог бы использовать отражение для создания новых экземпляров. Суть в том, что я хотел бы определить возвращаемое значение метода 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;
        }
    }
}

person mburke13    schedule 15.09.2011    source источник
comment
Я думаю, вам, возможно, придется взглянуть на синтетические методы.   -  person Shahzeb    schedule 15.09.2011
comment
Определить возвращаемое значение foo во время выполнения? Как вызовет это класс, если он возвращает произвольный тип?   -  person Dave Newton    schedule 15.09.2011
comment
Во время выполнения в том смысле, что значение foo определяется при создании класса, и впоследствии его не нужно изменять.   -  person mburke13    schedule 15.09.2011


Ответы (3)


Возможно, вы захотите взглянуть на использование CGLib. Он может делать то же, что и динамические прокси-серверы Java, но для абстрактных классов, а также для интерфейсов, и для этого у него есть API, аналогичный java.lang.reflect.Proxy. 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();
    }
}
person prunge    schedule 15.09.2011
comment
Я проверил cglib и немного не понял, как реализовать абстрактные методы в сгенерированном классе. У вас есть какая-либо документация или чтение, на которое я могу посмотреть, что нужно сделать? - person mburke13; 16.09.2011
comment
@Matt добавил пример, для получения дополнительной информации посетите веб-сайт (например, примеры или javadocs. - person prunge; 16.09.2011
comment
Спасибо за пример и ссылки. Я выбрал ваш ответ как лучший на мой вопрос. Из любопытства, является ли использование перехватчика метода лучшим/единственным способом реализации абстрактных методов? Хотя он выполняет свою работу, мне интересно, что происходит с классами, которые имеют, скажем, 4 или 5 абстрактных методов, когда имя каждого метода должно быть проверено для выполнения кода метода. Это кажется неуклюжим (т.е. if (abstractMethod1.equals(method.getName()) doThis(); else if (abstractMethod2.equals(method.getName()) doThis1(); и т.д. - person mburke13; 17.09.2011
comment
Как бы вы назвали неабстрактный метод абстрактного класса? - person Njax3SmmM2x2a0Zf7Hpd; 11.08.2014

Что мешает вам прочитать значение 5 из say properties и вернуть его обратно? Это слишком просто, поэтому, я думаю, у вас должно быть что-то более сложное, чем возврат int, который вы хотите выполнить здесь. Я согласен с сообщениями выше, что создание классов во время выполнения было бы очень дорого. Если вы заранее знаете свою бизнес-логику, вы можете применить шаблон Factory для загрузки желаемой реализации определенных интерфейсов во время выполнения. Вот как работают библиотеки JDBC.

Если вы не знаете бизнес-логику заранее и у вас ее много, вы можете извлечь выгоду из использования готового механизма правил для обработки логики и возврата результатов обратно в вашу программу Java. Гораздо проще поддерживать эту логику в механизме правил, особенно если она часто меняется.

person RHT    schedule 15.09.2011

Да, такой подход должен работать. Но это будет дорого, если вы будете делать много генерации классов. (Возможно, речь идет о сотнях тысяч инструкций для создания файла байт-кода и его последующей загрузки. А еще есть память, необходимая для представления класса при его загрузке.)

Другой подход (также дорогой) заключается в создании исходного кода, его компиляции и загрузке во время выполнения.

Наконец, вы должны рассмотреть подход, заключающийся в том, чтобы сделать логику объектов управляемой таблицей или реализовать ее с помощью какого-либо интерпретатора. Если вам действительно нужно иметь разные классы, вы можете обернуть это, используя механизм динамических прокси-классов Java; например см. java.lang.reflect.Proxy

person Stephen C    schedule 15.09.2011
comment
Я рассматривал вариант прокси, но я ограничен тем фактом, что абстрактные методы должны быть объявлены в абстрактном классе, а не в интерфейсе (мне нужен абстрактный класс для расширения класса). Генерация классов будет выполняться при запуске и не будет превышать 10-100 классов. - person mburke13; 15.09.2011
comment
@Matt - вы можете реорганизовать свой абстрактный класс, чтобы он представлял собой интерфейс и абстрактный класс, который его реализует, а затем обернуть экземпляр абстрактного класса внутри прокси. Для такого количества классов подход к генерации кода не должен быть слишком дорогим, но это сложный и (потенциально) хрупкий способ решения этой проблемы... если сгенерированные методы должны быть сложными. (И если они этого не сделают, подход на основе таблиц будет проще.) - person Stephen C; 15.09.2011