Dependency Injection in less then 50 lines of code
javaeasydiI'm a big fan of the Dependency Injection design pattern and I'm using it a lot. I'm saying "design pattern" because when I'm thinking and talking about "DI" I don't think about frameworks, big containers and a lot of magic. Instead, for me DI is a simple concept of how you write your classes and put them together.
In the past I've used many DI frameworks like Guice, CDI/Weld, EJB and Spring. In other use cases (for example my SnakeFX game) I've done the DI by hand:
Simply put all dependencies of a class into the constructor of this class. In the main method you can now start to
instantiate the classes you need starting by the ones that have no dependencies and can therefore be instantiated
without constructor parameters needed. It's easy and this way you can't create cyclic dependencies by accident. Look at
an old version of the
DependencyInjector
class of SnakeFX
to see what I mean.
Learning how to do the DI by hand was a real eye-opener for me. You can learn that DI is no magic at all and you will lose all your fear from the big buzzword "Dependency Injection". It's nothing special. Simply a design pattern.
Dependency Injection Library
Recently I started development of a new small JavaFX app and again the question was on how to do DI this time? While doing DI by hand is possible, there are still downsides:
- It's an annoying task to stupidly call constructors.
- The Dependency Injector class has static dependencies (import statements) to all involved classes. If you make refactorings or change the dependencies of a class you need to adjust the Dependency Injector too.
So which framework to choose? Most of them are cool for bigger applications. But there are always some sort of configuration and other stuff to do before you can use them which isn't cool for small application.
This was the moment I had the idea of writing my own Dependency Injection library targeted at small applications. It should be easy to use without configuration even when the consequence is a limited number of features.
And as it turned out it is really not that hard. With the restriction of only supporting constructor-based injection (like I've done for DI-by-hand) I was able to write a DI library in less then 20 lines of code (without comments):
public <T> T getInstance(Class<T> clazz){
final Constructor<?>[] constructors = clazz.getConstructors();
if(constructors.length != 1){
throw new IllegalStateException("Only one constructor allowed!");
}
final Constructor<?> constructor = constructors[0];
final List<Object> arguments = Arrays.stream(constructor.getParameters())
.map(parameter -> (Object) getInstance(parameter.getType()))
.collect(Collectors.toList());
try {
return (T) constructor.newInstance(arguments.toArray());
} catch (Exception e) {
e.printStackTrace();
}
throw new IllegalStateException("Something went wrong :-(");
}
First it checks that the given class has exactly one public constructor. After that I'm using the new Streaming-API of Java 8 to recursively gather all instances for all parameters of the constructor. And the last thing is to create the new instance of the requested class using the list of constructor parameters.
Easy-DI
Of cause there are some problems with this implementation. The biggest one is that cyclic dependencies aren't detected. Instead a 'StackOverflowException' will be thrown by the JVM in this case.
To fix this I added two java.util.Set
s. The first one is used to store all requested classes and in the second one I'm
storing all classes that where successfully instantiated. When a class is requested for the first time, I'm adding it to
the first set and proceed with the method. When I'm able to create an instance of this class everything is cool and I'm
adding this class to the second set.
But when a class is requested that was already requested before but couldn't be instantiated yet, we have a problem. This means that we are in a recursive call at the moment and we are still gathering the arguments for the requested class. But when one of the arguments needs the requested class itself to be instantiated we have a cyclic dependency.
public class EasyDI {
private Set<Class> requestedClasses = new HashSet<>();
private Set<Class> instantiableClasses = new HashSet<>();
@SuppressWarnings("unchecked")
public <T> T getInstance(Class<T> clazz){
final Constructor<?>[] constructors = clazz.getConstructors();
if(constructors.length != 1){
throw new IllegalStateException("Only one constructor allowed!");
}
final Constructor<?> constructor = constructors[0];
if(requestedClasses.contains(clazz)){
if(!instantiableClasses.contains(clazz)){
throw new IllegalStateException("");
}
}else{
requestedClasses.add(clazz);
}
final List<Object> arguments = Arrays.stream(constructor.getParameters())
.map(parameter -> (Object) getInstance(parameter.getType()))
.collect(Collectors.toList());
try {
final Object newInstance = constructor.newInstance(arguments.toArray());
instantiableClasses.add(clazz); // mark the class as successfully instantiable.
return (T) newInstance;
} catch (Exception e) {
e.printStackTrace();
}
throw new IllegalStateException("Something went wrong :-(");
}
}
I have to admit that I'm not 100% sure that this check really finds every possible cyclic dependency but at least I wasn't able to find any counterexample so I think this is fine.
At this state the DI library has 40 lines of code. The usage looks like this:
public class MyService {
...
}
public class MyExample {
public MyExample(MyService service){
...
}
}
public class App {
public static void main(String...args){
EasyDI easyDI = new EasyDI();
MyExample example = easyDI.getInstance(MyExample.class);
// do something
}
}
As you can see there is no configuration needed. No annotations or other stuff for this simple example.
But there are still missing features for all day use cases: What happens when I'm using interfaces as dependency?
How can I define that from a certain class only one instance should be created and reused every time it is requested (Singleton)?
What if I have more than one constructor?
What if I want to integrate third-party code that only offers a factory to obtain an instance.
And so I created a small github project for this library called "EasyDI" and implemented these features too. It's not done in 40 LOC anymore and for some of these use cases there are annotations or configuration needed but the main goal stays the same:
- For easy use cases there is no configuration needed.
- For a little more advanced use cases the configuration is still easy.
- And for really advanced and special use cases you need to use another full-featured DI-framework like Guice or CDI.
In a future blog post I will show a full example of how to use EasyDI with all it's features.