|
|
|||||||||||||||||||||||||||||||||||||||||||||
One common problem that CGLIB users run into is that there is no way to prevent the code in the superclass' constructor from being run. It is especially annoying for mocking purposes, as some classes will even throw exceptions from their constructors.
This is such a fundamental JVM limitation--if you don't emit bytecode to invoke the super constructor the verifier will complain--that I really didn't think there was any hope of getting around it.
Luckily there are some more optimistic developers out there, including
Joe Walnes, who for his
XStream project actually hooked
into the JVM-specific com.sun.* classes to construct
objects without a default constructor. I didn't want to be in the
business of maintaining different code for every JVM version, but while
chatting on #codehaus Joe
suggested it might be possible to leverage Java serialization to do the
same thing.
So, here is some code that constructs a serialized bytestream manually, in order to generate a deserialized instance without invoking the constructor:
import java.io.*; public class Test { public static void main(String[] args) throws IOException, ClassNotFoundException { Bar bar1 = new Bar(); System.err.println("bar1: " + bar1.x); // should calculate suid using reflection on Bar.class long suid = -8713982946592877696L; Bar bar2 = (Bar)readObject(getSerializedBytes("Bar", suid)); System.err.println("bar2: " + bar2.x); } private static byte[] getSerializedBytes(String className, long suid) throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); DataOutputStream data = new DataOutputStream(baos); data.writeShort(ObjectStreamConstants.STREAM_MAGIC); data.writeShort(ObjectStreamConstants.STREAM_VERSION); data.writeByte(ObjectStreamConstants.TC_OBJECT); data.writeByte(ObjectStreamConstants.TC_CLASSDESC); data.writeUTF(className); data.writeLong(suid); data.writeByte(2); // classDescFlags (2 = Serializable) data.writeShort(0); // field count data.writeByte(ObjectStreamConstants.TC_ENDBLOCKDATA); data.writeByte(ObjectStreamConstants.TC_NULL); return baos.toByteArray(); } private static Object readObject(byte[] bytes) throws IOException, ClassNotFoundException { ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(bytes)); return in.readObject(); } }
The Bar class is simply:
public class Bar implements java.io.Serializable { public int x; public Bar() { x = 666; } }
The output is:
bar1: 666 bar2: 0
As you can see, the constructor code was skipped for the second instance.
Unfortunately this only works for classes that implement the
java.io.Serializable marker interface. I'm sure this is for
valid security reasons, but it is disappointing nonetheless. I haven't
decided whether it is worth adding support for this to CGLIB, since
often one won't have control over the superclass being proxied.