Recently, we ran into problems de-serializing proxy objects. The
first pitfall involved a bug in Weblogic that prevented us from
de-serializing a java.lang.reflect.Proxy instance. The second pitfall occurred as a result of using a Cglib proxy. In our efforts to deal with these pitfalls we deepened our understanding of how these libraries create dynamic proxies.
Pitfall #1: Classloader Problems
We wanted to use the java.lang.reflect.Proxy class to make instances
of one (very simple) class assignable to different types of interfaces.
Everything was going just swimingly and we were close to checking in
our code. When it came time to run our integration tests, Weblogic
began throwing a ClassNotFoundException whenever it attempted to
de-serialize a proxy instance in a MessageDrivenBean. Once we confirmed
that the interfaces were indeed on the Classpath, we fired up the
debugger and dug a little deeper (but not until we consumed a a copious
amount of Coca-Cola and Marlboros... thanks for the tip Mike).
We discovered that - in the process of de-serializing our proxy object
- the java.io.ObjectInputStream was trying to load our interfaces with
the wrong classloader. It was using the parent of the Weblogic
application classloader which had no knowledge of the classpath in our
EAR file. We were were pretty certain that the reason was due to a bug
in the way Weblogic was managing classloaders in it's JMS
implementation (of course, Weblogic is not open source so we can't be
100% certain of this).
We couldn't de-serialize the object ourselves inside the message
bean and we couldn't modify the way Weblogic managed classloaders, so
we tried using the Cglib library instead. The Cglib proxy worked? The
reason is because the ObjectInputStream de-serializes Cglib instances
differently than it does a java.lang.reflect.Proxy instance. Why?
Because of a key difference between the two types of proxy objects;
- When you create an instance using the JDK Proxy, it generates a
subclass of the java.lang.reflect.Proxy class for you (the subclass is
assignable to any interfaces you provide). Furthermore, the class
generated by the java.lang.reflect.Proxy will have a different class
descriptor (in fact, a Proxy class descriptor).
- On the other hand, the Cglib library generates a subclass of an
arbitrary class that you provide (as with the JDK proxy, the Cglib
generated subclass will also be assignable to any interfaces you choose
to provide). The class generated by the Cglib library will have a
regular class descriptor.
Whenever the ObjectInputStream de-serializes an object, it peeks at
the Class descriptor. If it has a Proxy class descriptor, it invokes
the ObjectInputStream.resolveProxyClass() method which creates a new
version of the proxy subclass in the JVM (if it didn't already exist)
based on the declared interfaces. Because the Cglib class has a normal
class descriptor, the ObjectInputStream.resolveClass() method is
invoked instead. For reasons we're not sure of, the classloader that is
retrieved in the ObjectInputStream.resolveClass() method is the correct
one and can resolve our interfaces/classes.
Pitfall #2: Generated Subclasses
Now our Cglib proxy was being de-serialized correctly when sent to a
JMS MessageDrivenBean. But we we were also sending these proxies to a
rich client, and the Cglib proxies were not being de-serialized there.
We were stunned, but only temporarily. This result was to be expected.
The Cglib generated objects were instances of a subclass generated
dynamically on a different JVM. Our rich client JVM didn't recognize
the class and was quite justified in throwing a ClassNotFoundException.
It turns out there is a pretty simple solution to this problem.
Basically, you need to implement the writeReplace() and readResolve()
methods on the object that your proxy delegates methods to. The
delegate uses these methods to de-proxy and re-proxy itself depending
on whether it is being serialized or de-serialized. In our case, our
delegate object is the super-class of our Proxy instance, so we
implemented the writeReplace() and readResolve() methods on it. Here is
an example using a class called DataHandler;
public static class DataHandler implements Serializable {
private static final long serialVersionUID = 1L;
private final String someData;
public DataHandler(String someData){
this.someData = someData;
}
public String getSomeData() {
return someData;
}
public boolean equals(Object obj) {
return obj instanceof DataHandler ?
((DataHandler)obj).getSomeData().equals(this.someData)
: false;
}
public Object writeReplace() throws ObjectStreamException{
return new DataHandler(this.someData);
}
public Object readResolve() throws ObjectStreamException{
return ProxyFactory.createCglibProxy(DataHandler.class, new Class[]{IFoo.class}, new Object[] {this.someData});
}
}
public interface IFoo extends java.io.Serializable{
}
We create a proxy with the Cglib library like so;
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(DataHandler.class);
enhancer.setInterfaces(new Class[]{IFoo.class});
enhancer.setCallback(new NoOpCallback());//a NoOp callback. All methods will be invoked on the superclass
//generates a subclass of DataHandler that is assignable to the IFoo interface.
IFoo foo = (IFoo) enhancer.create(new Class[]{String.class}, new Object[] {"someData"});
When the foo instance is serialized, the super-class (DataHandler)
method writeReplace() is invoked. The writeReplace method returns a new
instance of the DataHandler class, thereby discarding the Proxy wrapper
subclass. It's this new instance of DataHandler that gets serialized,
not the original subclass. When the DataHandler instance is eventually
de-serialized, the readResolve() method is invoked on it. The
readResolve() will generate and return a new proxy subclass equivalent
to the original. We learned this little trick by studying the code in
the Hibernate project which uses Cglib for proxies and is open-source.
posted on 2008-08-25 13:29
R.Zeus 阅读(269)
评论(0) 编辑 收藏 所属分类:
Reflection 、
CGLIB