Dependency Injection in Java

You might be thinking that this is a long solved problem. I think I have something a little bit different.

This is very similar to the C++ based one that I wrote long ago.

There are several design decisions in this approach that I would like to make explicit.

  • It favors constructor dependency resolution, although it does not require it.
  • It favors type safety.
  • It does not require modification of existing code to add decorators, and thus allows you to mix code from different sources.
  • The dependency declarations are all done in simple Java.
  • The components built this way can implement the composite design pattern, and the framework can be thought of as the builder for them.
  • The declaration of the factories is separate from the implementation of the objects themselves. This allows different factories to be used in different settings.

The resolvers can be chained in parent/child relationships. A common example of this is in a web application, a request will be the child of a session, which in turn will be the child of the web application. The rule is that a child object can resolve a dependency via the parent, but a parent cannot resolve a dependency via a child. Parent scoped objects are expected to live beyond the lifespan of child scoped objects.

Java Generics can be used to select the appropriate factory function to create an instance of a class. Say I want to create an instance of Message pump, a class that pulls a message from a source and sends it to a sink.

MessagePump messagePump = childResolver.fetch(MessagePump.class);

If we have a factory interface like this:

package com.younglogic.resolver;
 
public interface  Factory<T> {
	T create (Resolver registry); 
};

You can collect them up in a registry like this:

package com.younglogic.resolver;
 
import java.util.HashMap;
import java.util.Map;
 
public class Registry {
 
	@SuppressWarnings("rawtypes")
	Map<Class, Factory> factories = new HashMap<Class, Factory>();
 
	public Resolver createResolver(Resolver parent) {
		return new Resolver(this, parent);
	}
 
	<T> void register(Class<T> c, Factory<T> f) {
		factories.put(c, f);
	};
 
}

The actual factory implementation can be anonymous classes.

registry.register(MessageSink.class, new Factory<MessageSink>() {
    @Override
    public ConcreteMessageSink create(Resolver registry) {
	return new ConcreteMessageSink();
    }
});

The resolution is done via a lazy load proxy defined like this:

package com.younglogic.resolver;
 
import java.util.HashMap;
import java.util.Map;
import java.lang.InstantiationError;
 
public class Resolver {
 
	@SuppressWarnings("rawtypes")
	Map<Class, Object> instances = new HashMap<Class, Object>();
 
	private Registry registry;
	private Resolver parent;
 
	Resolver(Registry registry, Resolver parent) {
		super();
		this.registry = registry;
		this.parent = parent;
	}
 
	@SuppressWarnings("unchecked")
	public <T extends Object> T fetch(@SuppressWarnings("rawtypes") Class c) throws InstantiationError {
		T o = (T) instances.get(c);
		if (o == null) {
			// Don't synchronize for the fast path, only the
			// slow path where we need to create a new object.
 
			Factory<T> factory = registry.factories.get(c);
			if (factory == null) {
				if (parent == null){
					throw new InstantiationError();
				}
				return parent.fetch(c);
			}
			synchronized (instances) {
				if (instances.get(c) == null) {
					try {
						o = (T) factory.create(this);
						instances.put(c, o);
					} catch (ClassCastException e) {
						throw new InstantiationError();
					}
				}
			}
		}
		return o;
	}
}

One feature I have not implemented is away to distinguish between two different components that implement the same interface.

The drawback to Java in this case is that there is no clean up; you are still depending on the garbage collector, and finalize might never be called for your objects.

Full code is here: https://github.com/admiyo/javaresolver

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.