Implement interface with ASM: ClassCastException
-
21-12-2019 - |
Question
I'm writing an application which requires the dynamic implementation of an interface. I had no (obvious) issues regarding generating the class: I verified it with both javap and a decompiler.
The problem is, after I generate the class I define and instantiate. Finally, I cast it to the type of the implemented interface.
The problem is that I am being hit with a java.lang.ClassCastException: MyGeneratedClass cannot be cast to MyInterface
, though I do add MyInterface
as an interface when I call ClassWriter.visit
.
Below is an SSCCE demonstrating the issue for an interface named MyInterface
full of void methods.
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
cw.visit(V1_6,
ACC_PUBLIC | ACC_SUPER,
"MyGeneratedClass",
null,
"java/lang/Object",
new String[]{MyInterface.class.getName().replace(".", "/"});
{
MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
mv.visitVarInsn(ALOAD, 0);
mv.visitMethodInsn(INVOKESPECIAL,
"java/lang/Object",
"<init>",
"()V");
mv.visitInsn(RETURN);
mv.visitMaxs(0, 0);
mv.visitEnd();
}
for (Method call : MyInterface.class.getDeclaredMethods()) {
MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, call.getName(), Type.getMethodDescriptor(call), null, null);
// ...
mv.visitInsn(RETURN);
mv.visitMaxs(0, 0);
mv.visitEnd();
}
cw.visitEnd();
byte[] raw = cw.toByteArray();
try {
return (MyInterface) new ClassLoader() {
public Class defineClass(byte[] bytes) {
return super.defineClass("MyGeneratedClass", bytes, 0, bytes.length);
}
}.defineClass(raw).newInstance();
} catch (InstantiationException | IllegalAccessException e) {
e.printStackTrace();
return null;
}
Printing out the generatedObject.getClass().getInterfaces()
does indeed show that MyInterface
is implemented. But generatedObject instanceof MyInterface
returns false, something which I find a contradiction.
Can anyone shed some light on whats happening here? Any help would be greatly appreciated.
Solution
I'd guess it's because the new classloader isn't a child of the classloader that loaded MyInterface
, which means you end up with two different classes which aren't the same despite being to all appearances identical. Try changing
return (MyInterface) new ClassLoader() {
to
return (MyInterface) new ClassLoader(MyInterface.class.getClassLoader()) {