20040817

Configurable Factories and Proxy Injection using XStream

XStream is a useful lightweight persistence/transport/config xml round-trip library. Here is an example of how to use it for configurable factories.

So you have got your factory pattern in use:

MyObject.java
MyObjectImpl.java
MyObjectFactory.java

[please don't nit pick about the pattern implementation it is not the point of the blog]
You interface class MyObject is implemented by MyObjectImpl and you use the MyObjectFactory class to bind them together.

Here is the basic factory skeleton:

class MyObjectFactory {
...
MyObject getMyObject() {
MyObject object;
// whatever creational method is used will contain
object = createMyObject();
// and whatever
return object;
}
private MyObject createMyObject() {
// return new MyObjectImpl();
...
}
}


Next we create a utility class to abstract out the file and XStream handling. This is only a simple implementation, if you do this for real you should consider JNDI, getResourceAsStream and the use of patch directories.

public final class PrototypeFileReader {
private static final XStream XSTREAM = new XStream();
public static Object read(Class prototypeClass) {
try {
String fileName = prototypeClass.getName() + "-prototype.xml";
Reader myPrototypeReader = new FileReader(fileName);
return XSTREAM.fromXML(myPrototypeReader);
} catch (FileNotFoundException e) {
e.printStackTrace(); // or whatever
return null;
}
}
}


The factory method then becomes:

Object created = PrototypeFileReader.read(MyObject.class);
if (created == null) {
// throw a runtime exception of your choice
}
return created;


And to make it work you create the MyObject-prototype.xml file:

<example.xstream.MyObjectImpl>
<name>defaultName</name>
<value>defaultValue</value>
</example.xstream.MyObjectImpl>


And to make that file you would use this little utility:

public class PrototypeOutput {
private static final XStream XSTREAM = new XStream();
public static void main(String[] args) {
String className = args[0];
try {
Object object = Class.forName(className).newInstance();
System.out.println(XSTREAM.toXML(object));
} catch (Exception e) { // naughty
e.printStackTrace();
}
}
}

Note that XStream will normally leave null fields out of serialized form. See the documentation for more details.

That is how you get the basic version running. The real power comes when you need to change the implementation either temporarily or permanently.

Here are 2 use cases of how you might want to change what the factory produces:

  • Version 2 needs to use an extended version to support a new feature.

    To do this you add the new class to the classpath and change the xml file to be something like:

    <example.xstream.MyObjectExtendedImpl>
    <name>defaultName</name>
    <value>defaultValue</value>
    <description>defaultDescription</description>
    </example.xstream.MyObjectExtendedImpl>


  • You need to trace the calls to the methods of the object.

    XStream has a DynamicProxyConverter which is very useful for inserting a proxy implementation in the place of your class, as in:

    <dynamic-proxy>
    <interface>example.xstream.MyObject</interface>
    <handler class="example.xstream.MyInvocationHandler">
    <real class="example.xstream.MyObjectImpl">
    <name>UNKNOWN</name>
    <value>UNKNOWN</value>
    </real>
    </handler>
    </dynamic-proxy>

    This example uses the InvocationHandler implementation MyInvocationHandler which is constructed with the member variable 'real' that is invoked as you trace the call. Other handlers might not need the 'real' implementation and could instead bind to test data or raise exceptions.

2 comments:

Anonymous said...

You might also be interested in this blog entry

straun said...

Addendum:

<dynamic-proxy>
<interface>example.xstream.MyObject</interface>
<handler class="example.xstream.MyOtherInvocationHandler">
<real class="dynamic-proxy"> <interface>example.xstream.MyObject</interface>
<handler class="example.xstream.MyInvocationHandler">
<real class="example.xstream.MyObjectImpl">
<name>UNKNOWN</name>
<value>UNKNOWN</value>
</real>
</handler>
</real>
</handler>
</dynamic-proxy>

Just in case you were not impressed already.

[note to self: need a better blog comment tool]