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.
diydi
does not expose any dependencies other thanjavax.inject
.diydi
does not side step any Java visibility rules. Objects injected must be visible to it.diydi
classes and packages are not exposed to injected objects.diydi
by default leverages thejavax.inject
set 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
@Inject
that 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);