4. Instantiators
Overview
At deserialization time, from Flex to Java, GraniteDS must instantiate and populate new JavaBeans with serialized data. The populate problem (strictly private field), as we have seen before, is addressed by externalizers. But there is still a problem with classes that do not declare a default constructor. How do we instantiate those classes with meaningful parameters at deserialization time?
When GraniteDS encounters classes without a default constructor, it tries to instantiate them by using the Sun JVM sun.reflect.ReflectionFactory class that bypasses this limitation. Then, if it can successfully instantiate this kind of class, fields deserialization follows the standard process with or without externalization. This solution has three serious limitations however: it only works with a Sun JVM, it does not take care of complex initialization you may have put in your custom contructor, and it cannot simply work with classes that should be created via a static method, such as singleton.
With GraniteDS instantiators, you may control the instantiation process, delaying the actual instantiation of the class after all its serialized data has been read.
Standard Instantiators
There are two standard instantiators that come with GDS:
- org.granite.messaging.amf.io.util.instantiator.EnumInstantiator: This instantiator is used in order to get an Enum constant value from an Enum class and value (the String representation of the constant), by means of the java.lang.Enum.valueOf(Class<? extends Enum> enumType, String name) method.
- org.granite.hibernate.HibernateProxyInstantiator: It is used when GDS needs to recreate an HibernateProxy. See source code for details.
Note that those instantiators do not require an entry in granite-config.xml, they are respectively used by the EnumExternalizer, HibernateExternalizer, and TopLinkExternalizer.
Custom Instantiators
Let's say you have a JavaBean like this one:
package org.test; import java.util.Map; import java.util.HashMap; import java.io.UnsupportedEncodingException; import java.net.URLEncoder; public class MyBean { private final static Map<String, MyBean> beans = new HashMap<String, MyBean>(); private final String name; private final String encodedName; protected MyBean(String name) { this.name = name; try { this.encodedName = URLEncoder.encode(name, "UTF-8"); } catch (UnsupportedEncodingException e) { throw new RuntimeException(e); } } public static MyBean getInstance(String name) { MyBean bean = null; synchronized (beans) { bean = beans.get(name); if (bean == null) { bean = new MyBean(name); beans.put(name, bean); } } return bean; } public String getName() { return name; } public String getEncodedName() { return encodedName; } }
With this kind of Java class, even with the help of the GDS DefaultExternalizer and the Sun ReflexionFactory facility, you will not be able to get the cached instance of your bean and the encodedName field will not be correctly initialized. Instead, a new instance of MyBean would be created with a simulated default constructor and the name field would be assigned with serialized data.
The solution is to write a custom instantiator that will be used at deserialization time:
package org.test; import java.util.Collections; import java.util.List; import java.util.ArrayList; import org.granite.messaging.amf.io.util.instantiator.AbstractInstanciator; public class MyBeanInstanciator extends AbstractInstanciator<MyBean> { private static final long serialVersionUID = -1L; private static final List<String> orderedFields; static { List<String> of = new ArrayList<String>(1); of.add("name"); orderedFields = Collections.unmodifiableList(of); } @Override public List<String> getOrderedFieldNames() { return orderedFields; } @Override public MyBean newInstance() { return MyBean.getInstance((String)get("name")); } }
You should finally use a granite-config.xml file as follows in order to use your instantiator:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE granite-config PUBLIC "-//Granite Data Services//DTD granite-config internal//EN" "http://www.graniteds.org/public/dtd/2.0.0/granite-config.dtd"> <granite-config> <externalizers> <externalizer type="org.granite.messaging.amf.io.util.externalizer.DefaultExternalizer"> <include type="org.test.MyBean"/> </externalizer> </externalizers> <instanciators> <instanciator type="org.test.MyBean">org.test.MyBeanInstanciator</instanciator> </instanciators> </granite-config>
