Do-It-Yourself Dependency Injection
The Dependency Injection (DI) pattern is probably the most recognized Inversion of Control pattern in the Java world. Several popular frameworks exist for the pattern, 3 of the most popular being Dagger, Spring, and Guice. In addition to these popular frameworks, the DI pattern works quite well without a framework.
I have, on several occasions, discovered that I want some sort of framework for DI, but the popular ones are more complex than is appropriate for my project. Maybe I have teammates who struggle with the ideas behind DI frameworks. Maybe the popular frameworks allow patterns or functionality that is incompatible with the rest of my project. Whatever the reason, I find that I want simple DI, with a small set of rules that I define myself. I've written this sort of custom, simple DI framework on more than one occasion now. The point of DIY DI is to make this sort of endeavor simple and straightforward -- solving the reflection and management issues once. A project, library, or framework building on top of DIY DI simply needs to define a ruleset for fetching objects and then use the provided factory to instantiate objects.
- All of the hard stuff is handled by the library.
diydidoes not expose any dependencies other thanjavax.inject.diydidoes not side step any Java visibility rules. Objects injected must be visible to it.diydiclasses and packages are not exposed to injected objects.diydiby default leverages thejavax.injectset of annotations.- All customizations ship with "principle of least surprise"-based default implementations.
- The implementor can pick and choose which areas to customize and keep default implementations elsewhere.
import io.bunting.diydi.*;
public class Main {
public static void main(String ...args) {
DIFactory factory = DIFactory.create("myDI")
.withInstantiatorDiscovery(InstantiatorDiscovery::defaultMechanism)
.build();
MyObject object = factory.access(MyObject.class);
}
}
By default, diydi will instantiate objects using a public constructor. To
qualify for instantiation, a class must either:
- have no constructor,
- have exactly one public constructor
- have exactly one public constructor annotated with
@Inject - have exactly one static method annotated with
@Injectthat returns this type or a subtype
Implementors may override the discovery of an instantiation method by providing
a InstantiatorDiscovery implementation. It has the single method
signature:
@Nonnull
<T> Executable discover(Class<T> type);
By default, diydi will consider the following to be dependencies of a given
class:
- Any class specified as a parameter to the instantiation method.
- Any class specified as a parameter on an instance method which is annotated
with
@Inject.
Implementors may override the detection of dependencies by providing a
DependencyDetector implementation. It has the single method signature:
@Nonnull
ResolvedDependencies detect(Method<T> instantiator);
By default, diydi will resolve a dependency by calling the getRawType method
on the Dependency object and returning that class.
Implementors may override the resolution of a dependency by providing a
DependencyResolver implementation.
@Nonnull
<T> Optional<Class<? extends T>> resolve(Dependency<T> dependency);
Implementors may add additional post processing of an instantiated object by
providing a PostProcessor implementation.
@Nonnull
<T> T postProcess(T object, CollectedDependencies dependencies);