Picocontainer Null Object Builder
Charles Miller pointed out that passing null
instead of a null object would just as happily generate an exception. I like to be explicit though, especially when cheating and purposefully not providing a component implementation. As promised in the comments, here is a much fancier null component builder (and testcase) than you'll ever really need ;-)
package org.skife.picotools;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class NC
{
private static class NCHandler implements InvocationHandler
{
private final Class iface;
NCHandler(final Class iface)
{
this.iface = iface;
}
public Object invoke(final Object proxy,
final Method method,
final Object[] args) throws Throwable
{
final StringBuffer signature = new StringBuffer();
signature.append(method.getDeclaringClass().getName()).append("#");
signature.append(method.getName()).append("(");
final Class[] params = method.getParameterTypes();
for (int i = 0; i != params.length; i++)
{
final Class param = params[i];
signature.append(param.getName());
if (i != params.length - 1) signature.append(", ");
}
signature.append("): ")
.append(method.getReturnType().getName())
.append("]");
throw new IllegalStateException(new StringBuffer()
.append("Invoked method [")
.append(signature.toString())
.append(" on null component implementing [")
.append(iface.getName())
.append("]").toString());
}
};
public static Object build(final Class iface)
{
return Proxy.newProxyInstance(iface.getClassLoader(),
new Class[]{iface},
new NCHandler(iface));
}
}
package org.skife.picotools;
import java.util.Map;
import junit.framework.TestCase;
public class NCTest extends TestCase {
public void testThing() {
final Map null_map = (Map) NC.build(Map.class);
try {
null_map.put("key", "value");
fail("Should have thrown excpetion");
} catch (IllegalStateException e) {
assertTrue("Go through here", true);
}
}
}
Exception message will look like Invoked method [java.util.Map#put(java.lang.Object, java.lang.Object): java.lang.Object] on null component implementing [java.util.Map]
Easy, and the error is much more explicit than a null pointer exception. Admittedly, (WombatDAO)NC.build(WombatDAO.class)
is 21 keystrokes more than (WombatDAO)null
but we are using Java here, not Perl =)
2 writebacks [/src/java] permanent link
Two idioms I have found very useful when working with Picocontainer:
When you refactor a component to add a dependency (new constructor arg), leave the old constructor signnature, but have it pass a null object (not null!) to the new constructor. The null object throws an exception (I like UnsupportedOperation or IllegalState personally) if anything on it is called. This solves the annoying "I don't want to add the dependency because it will break the unit test compiles" problem (side note, setter injection people like to argue that si does this for free. Really, you just get a partially initialized component, not a solution). As pico uses the greediest constructor it can find, it will use the new one.
The second idiom is using a Component
and ComponentConfig
where the ComponentConfig
is a simple bean which holds the primitive (Strings are primitive in Java, darn it) dependencies. If you really like your XML you can use xmlbeans to generate the config classes and read them at runtime. I don't. Avoid the pull of putting all the config values in a single big config class which everything is dependent on. The phrase "everything is dependent on" should be sufficient to stop you, right there.