Friday, July 2, 2010

Bordello - Poor man's dependency injection

What do you do when there is no Spring or other facility to handle dependency injection? How do you fake services in your unit tests?

One way is to use Bordello to instantiate services in the application code. It is not dependency injection per se but it lets you test.

import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
/**
* Service Locator Pattern - Dependency Injection by any other name.
* Allows unit tests to set a mock instance for some interface or class.
*
* <pre> SomeClass instance = Bordello.get(SomeClass.class); </pre>
*
* Requires simple constructor. Interfaces declare the implementing class. <pre>
* {@code @}Implementor(SomeTingImpl.class)
* public interface SomeTing {
* ...
* </pre>
*/
public class Bordello {
// ConcurrentMap handles multi-threading and the Double-Check Locking problem.
private final static ConcurrentMap<Class<?>, Object> services = new ConcurrentHashMap<Class<?>, Object>();
/**
* Acquire an implementation of a service. If one has not already
* been instantiated, instantiate the class defined by the
* Implementor annotation on the interface
*/
public static <T> T get(Class<T> interfaceClass) {
Object service = services.get(interfaceClass);
if (service != null) {
return interfaceClass.cast(service);
}
// service does not yet exist
try {
Implementor annote = interfaceClass.getAnnotation(Implementor.class);
Class<?> implementingClass;
if (annote != null) {
implementingClass = annote.value();
} else {
implementingClass = interfaceClass;
}
Object newservice = implementingClass.newInstance();
service = services.putIfAbsent(interfaceClass, newservice);
if (service == null) {
// put succeeded, use new value
service = newservice;
}
} catch (Exception e) {
throw new RuntimeException(e);
}
return interfaceClass.cast(service);
}
/**
* Set an alternate service implementation.
* Typically only called in unit tests.
*/
public static <T> void set(Class<T> interfaceClass, T providor) {
synchronized (interfaceClass) {
services.put(interfaceClass, providor);
}
}
/**
* Clear all previous instances - use before setting up for a unit test.
*/
public static void reset() {
services.clear();
}
}
view raw bordello.java hosted with ❤ by GitHub


This is a variation on www.artima.com/weblogs/viewpost.jsp?thread=238562 that handles concurrency correctly.

No comments:

Post a Comment