Quick java puzzle: What does this program print?
import java.io.*;
class A {
public A() { System.out.print("A"); }
}
class B extends A implements java.io.Serializable {
public B() { System.out.print("B"); }
}
public class Test {
public static void main(String[] args) throws Throwable {
ObjectOutputStream oo = new ObjectOutputStream(
new FileOutputStream("/tmp/xx"));
oo.writeObject(new B());
oo.close();
System.out.println("--");
ObjectInputStream oi = new ObjectInputStream(
new FileInputStream("/tmp/xx"));
B b = (B)oi.readObject();
System.out.println("--");
}
}
Welll, I'll spare you the thinking and go straight to the answer, which is
pc-183:/tmp krab$ java Test AB-- A--When reading an instance of B from the serializable stream, class B's constructor is not called! This blog is about why that is so, and the kind of trouble we had to go through in our Java EE implementation because of this.
Here's an interesting piece of history: The only piece of Trifork's Java EE implementation which is not »pure« is hidden within our RMI/IIOP implementation. You may ask why we implemented RMI/IIOP ourselves, so let me answer this before I dive into the actual subject of this posting: Having our own implementation of CORBA, RMI and the integration between these two allowed us to simplify many other things by using the same invocation mechanism universally for local and remote EJB invocations. Further, having the code under our control allowed us to architect everything together for better overall performance. These advantages came however at a pretty high price, since maintaining this part (ans especially interoperability with other peoples CORBA, RMI and/or RMI/IIOP implementations has been a challenge).
Whereas most of RMI logic can be expressed in terms of what is possible with Java reflection, there are two places where java.lang.reflect comes short, both of which are related to object creation in context of unmarshalling.
- when you need to instantiate (unmarshal) an object using the special »serializable« semantics, in which constructors are not called.
- when you need to deserialize an object with final instance variables
For almost any kind of Java field, java.lang.reflection provides means to assign values to them - even private and other hidden fields. Class java.lang.reflect.Field has this magic method setAccessible which allows one to circumvent access rules (if you have authority to do so). Static variables are never transferred as part of serialization, but, but, but, ... there is no magic wand that grants write-access to final fields within the Java realm. For a long time, we had a piece of native code hanging around to handle this, because surprisingly enough JNI's equivalent of java.lang.reflect.Field does allow assigning values to final fields. But it was always a burden to carry a chunk of native code around, and kind of annoying when it was just this tiny little piece, so we figured a hack was in place, and started writing some JVM-specific code to access the classes used to implement java.lang.reflect.Field.
So, the hack for final instance variables is to use sun.misc.Unsafe. This singleton object is available for HotSpot implementations and gives access to not just read/write any kind of field but also many other things. For HotSpot-based JVMs and most of IBMs VMs this hack is also avaiable.
As for handling classes that have no default constructor, we have to follow a similar - albeit much more ugly - hack. Reverse-engineering java.io.ObjectStreamClass lets one find the - typically private static (and native) - methods that can do this for a given VM. The semantics needed is to (a) create a new blank instance of the given class, and (b) find the first superclass of the given class which is not serializable, and then call it's no-arg construtor. If the first such not-serializable class does not have a default constructor, the algorithm fails. For example:
class A extends Object {
A() { print("A"); }
}
class B extends A {
B(int x) { print("B"); }
}
class C extends B implements Serializable {
C() { super(1); print ("C"); }
}
When you say »new C()«, it will print »ABC«, but if such an object is deserialized, it fails! since class B does not have a default constructor. In this case, class C is not serializable at all. Interestingly enough, this is not verifies when the object is written to an ObjectOutputStream, so you're up for a surprise. Even though class C has a default constructor, it not called.
For the intersted reader, out RMI/IIOP implementation is now part of Apache Yoko.
Recent Comments