Digamos que tengo una clase abstracta:¿Implementando métodos abstractos en tiempo de ejecución?
abstract class Foo extends Bar {
public abstract int foo();
}
que quiero extender en tiempo de ejecución para crear un objeto de clase. La esperanza sería que pudiera tener una clase generada dinámicamente:
class FooImpl extends Foo {
@Override
public int foo() {
return 5;
}
}
que sería representado por un objeto de clase y que luego podría utilizar la reflexión para crear nuevas instancias de. La clave es que me gustaría decidir el valor de retorno del método foo() en tiempo de ejecución. Mi idea es usar ASM para crear el bytecode para la clase y luego usar la reflexión en un objeto ClassLoader para definir la clase.
Está utilizando ASM y luego refleja el método ClassLoader # defineClass en los bytes generados la mejor manera de implementar métodos abstractos en tiempo de ejecución con valores no codificados?
En caso afirmativo, ¿cómo podría hacerlo? Mi instinto es utilizar ASMifierClassVisitor, pero no estoy seguro del método exacto para hacerlo. Sé que si todo lo demás falla, puedo consultar manualmente las instrucciones de JVM necesarias para definir una clase específica, pero creo que debe haber una manera más fácil.
Si no, ¿cuál es la mejor manera y cómo haré para usar la mejor manera?
EDIT: Revisé todas las respuestas y decidí que ninguna de ellas era exactamente lo que estaba buscando. Terminé creando una pequeña implementación de lo que estaba hablando con ASM. Pensé que debería publicarlo aquí:
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;
}
}
}
Creo que es posible que tenga que mirar los métodos sintéticos. – Shahzeb
¿Decida el valor de retorno de 'foo' en el tiempo de ejecución? ¿Cómo llamaría una clase a eso si devuelve un tipo arbitrario? –
En tiempo de ejecución en el sentido de que el valor de foo se determina cuando se genera la clase, y no necesita cambiar después. – mburke13