Serializing CGLIB enhanced proxy into JSON using XStream
I use a custom very lightweight persistence library that does lazy loading using CGLIB Enhanced proxy.
THe generated proxy for domain objects use Key/value Map data fed to it via a static factory method.
When a get or set method is called for the wrapped domain, the proxy check the value for the field in the Map,
if it finds an entry for the field, which means the field hasn’t been initialised, it sets the field value from Map and removes
the entry from the Map.
If no entry is found in the Map, the method returns value of the field of the domain object encapsulated by the getter method
rather than getting it from the Map.
Of course if the field is a domain object, then the object is fetched using the PK value in the Map.
That gives me the same lazy loading voodoo magic like Hibernate- the 10 pound gorrila.
The persistence is backed by a cache which looks at a domain’s metadata and
only caches those that are specified as cacheable.
Everything works like a charm except that the CGLIB enhanced objects don’t play nice with XStream at all.
When serializing enhanced objects XStream spits out gory details of classe/superclasses/interfaces and full dissection of a Map that is fed to a static method when creating the proxy object, when all i want is the pojo object wrapped by the proxy.
I believe the XStream library simply gets the value of the field rather than calling the getter method using reflection when serializing.
That must be the reason that in my case all serialised objects have empty values for fields because the fields don’t get set when the proxy is
created, they get set only setter method is called on it or getter method is called for the first time .
I think that’s the same problem with Hibernate and I guess that’s the reason it throws Lazy Initilization Exception.
I tried the CGLIBEnhancedConverter that comes with the library but it didn’t work for me.
I couldn’t find a simple enough solution in the internet and the ones I found were all Hibernate specific.
Since this is the second time I had been trying to solve the issue, I decided to push forward and find a work around myself.
After trying out a few different solutions I finally came up with a dead simple solution.
IT involves a static method to deproxy the enhanced domain objects and a XStream custom converter class.
There may be performance cost as it uses reflection eventhough getter setter methods for domain classes are cached for reuse.
Here’s the simplified method.
/**
* Deproxies a CGLIB enhanced proxy object
* @param <T>
* @param proxy
* @return deproxied domain object
*/
public static <T > T deproxyReflection(final T proxy) {
Class entityClass = proxy.getClass().getSuperclass();
T entity = (T) ClassUtil.getInstance(entityClass);
Field[] fields = entityClass.getDeclaredFields();
for (Field f : fields) {
try {
// no need to set the values of collections as they won;t be serialized.
//we just need the actaul properties taht amp to table columns
if(List.class.isAssignableFrom(f.getType()))
continue;
//I use a utility method to fetch getter/setter methods.
//it simply loops through the object and super class to find the methods.
//so instead of screwing arund with the field's security settings we'll use getters/setters to ensure its unintrusive
Method getter = ClassUtil.getPojoMethod(entityClass, f.getName(), ClassUtil.MethodType.GETTER);
Method setter = ClassUtil.getPojoMethod(entityClass, f.getName(), new Class[]{f.getType()}, ClassUtil.MethodType.SETTER);
Object val = getter.invoke(proxy, null);
setter.invoke(entity, new Object[]{val});
} catch (Exception ex) {
Logger.getLogger(EntityProxyFactory.class.getName()).log(Level.SEVERE, null, ex);
}
try {
f.set(entity, f.get(proxy));
} catch (Exception ex) {
Logger.getLogger(EntityProxyFactory.class.getName()).log(Level.SEVERE, null, ex);
}
}
return entity;
}
We need to register a converter with XStream that used the above method to sanitize the domain objects before serializing.
/**
* XStream converter to convert a CGLIB enhanced proxy to the wrapped pojo object.
*
*/
public class CGLIBEnhancedEntityConverter implements Converter {
public boolean canConvert(Class clazz) {
return (Enhancer.isEnhanced(clazz) || clazz == CGLIBMapper.Marker.class) ;
}
public void marshal(Object source, HierarchicalStreamWriter writer, MarshallingContext context) {
// deproxy before sending it off the chain for serialization. ANd that's it.
// XStream will get a simple pojo object and it will serialize it like any other pojo object.
context.convertAnother(EntityProxyFactory.deproxyReflection(source));
}
public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) {
throw new Error("not supported");
}
}
And that’s it. That’s all it takes.
If a domain object has other domain objects as fields, those fields will not be handled by the above method.
For that there are two choices. Firstly you can look at the field’s class type and say if t implements or extends your base Domain class/interface, you can deproxy them recursively.
In my case all domain objects implement a bare minimum interface called IEntity with only one getId() method.
the above method can be modified to recursively deproxy/sanitize a domain’s fields if they are proxied objects.
//.............
Object val = getter.invoke(proxy, null);
if (IEntity.class.isAssignableFrom(f.getType()) {
val=deproxy(val);//recursively deproxy the fields if they are enhanced objects
}
setter.invoke(entity, new Object[]{val});
If you only have to serialize a handful of domain objects, it might be better to register converters
for those fields at startup instead of using the above method.
Say, we have a domain class State with Country as one of its field, we can register the above converter to convert the country object as below.
//.............
//entity manager that handles all persistence needs
IEntityManager em= (IEntityManager) BeanFactory.getBean("entityManager");
//fetch a list of State objects with id smaller than3
List<State> list= em.fetch(State.class, "id<3");
//uses factory method to instantiate Xstream
xstream xstream= XStreamFactory.getXstream(Format.JSON);
CGLIBEnhancedEntityConverter cglibConverter= new CGLIBEnhancedEntityConverter();
//register default converter
xstream.registerConverter(cglibConverter);
//register field converter. country field for State objects
xstream.registerLocalConverter(State.class, "country", cglibConverter);
//spit out the serialized json objects
System.out.println(xs.toXML(list));
here’s the result without deproxying the domain object:
The before version
{"list": [
{
"code": "",
"name": "",
"daylightSavingStart": "",
"daylightSavingEnd": "",
"CGLIB$BOUND": true,
"CGLIB$CALLBACK_0": {
"@class": "com.barahisolutions.proxy.EntityProxyFactory$EntityMethodInterceptor",
"data": [
{},
{
"default": {
"loadFactor": 0.75,
"threshold": 12
},
"int": 16,
"int": 8,
"string": "id",
//..................................................
//there's 10 times more of this garbage which i didn;t bother to print out.
And here's the nice and clean pojo version of the deproxied proxy objects.
I used the recursive option to inspect properties if objects and deproxy them if they are proxied domain
objects.
The after version
{"list": [
{
"id": 1,
"code": "NSW",
"name": "New South Wales",
"utcOffset": 10.0,
"daylightSavingOn": false,
"daylightSavingStart": "",
"daylightSavingEnd": "",
"country": {
"id": 18,
"code": "",
"name": "Barbados"
}
},
{
"id": 2,
"code": "VIC",
"name": "Victoria",
"daylightSavingOn": false,
"daylightSavingStart": "",
"daylightSavingEnd": "",
"country": {
"id": 12,
"code": "AUS",
"name": "Australia"
}
}
]}

