Допустим, у меня есть абстрактный класс:
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;
}
}
}