DInject has moved to avaje.io/inject - taking you there now ...
DInject - Dependency injection
Current version - 2.2
Dagger2 style DI for server side JVM developers
No reflection, no classpath scanning, no dynamic proxies
APT based - generates source code for DI
About DInject
Uses APT
DInject uses annotation processing to generate Java source code to perform dependency injection much like Google Dagger
This makes it fast with low overhead, perfect for Kubernetes, Docker, Microservices style development.
Annotate components with @Singleton
and use @Inject
to specify constructor
injection or field injection.
@Singleton
public class CoffeeMaker {
private final Pump pump;
@Inject
CoffeeMaker(Pump pump) {
this.pump = pump;
}
...
SystemContext
SystemContext provides a simple way to get beans from the context.
// Get all the webroutes
List<WebRoutes> webRoutes = SystemContext.getBeans(WebRoutes.class);
// register them with Javalin
Javalin app = Javalin.create().disableStartupBanner();
app.routes(() -> webRoutes.forEach(WebRoutes::registerRoutes));
app.start(8080);
// Get a bean and use it
CoffeeMaker coffeeMaker = SystemContext.getBean(CoffeeMaker.class);
coffeeMaker.brew();
@PostConstruct & @PreDestroy
Bean lifecycle support via @PostConstruct
and @PreDestroy
. These methods
are most often used to open resources on startup (e.g. connections to remote services and databases) and
close those resources on shutdown.
...
@PostConstruct
void onStart() {
// perform action on startup, open resources etc
}
@PreDestory
void onStop() {
// close resources on shutdown
}
@Factory and @Bean
Sometimes we need to have some logic when creating beans. We can use @Factory
on a class
that has methods annotated with @Bean
to perform logic when creating beans.
In Spring DI this equates to using @Configuration
and @Bean
.
@Factory
public class MyFactory {
private final Configuration configuraton;
@Inject
MyFactory(Configuration configuraton) {
this.configuraton = configuraton;
}
@Bean
Foo createFoo() {
// potentially perform some logic
return new Foo(...);
}
@Bean
Bar createBar(Foo foo) {
// potentially perform some logic
return new Bar(...);
}
}
@Primary and @Secondary
@Primary
matches Spring DI's @Primary
in terms of function and use.
When we have multiple dependencies we can use @Named
to specify a specific dependency to use.
Alternatively we can use @Primary
to make a dependency the preferred dependency and
@Secondary
to make a dependency the least preferred dependency.
Generally we use @Primary
and @Secondary
most often when we are have dependencies in
multiple modules/jars.
@Primary
@Singleton
public class BestEmailSender implements EmailSender {
...
}
Testing
During testing we often want to supply some test doubles or use Mockito mocks or spy's.
@Test
public void myComponentTest() {
try (BeanContext context = new BootContext()
.withMock(Pump.class)
.withMock(Grinder.class, grinder -> {
// setup the mock
when(grinder.grindBeans()).thenReturn("stub response");
})
.load()) {
// Act
CoffeeMaker coffeeMaker = context.getBean(CoffeeMaker.class);
coffeeMaker.makeIt();
verify(grinder).grindBeans();
}
}
Modules
We can also want perform dependency injection with beans across multiple modules (jars).
We do this by using @ContextModule
to define the module dependencies.
The dependsOn
defines other modules that should be wired before this
module and that provide beans that can injected in our module.
We name modules with common functionality that we want to inject in other modules.
@ContextModule(name="feature-toggle")
Modules can specify that they depend on other modules. In the example below the "job system" is expecting to inject beans from a common "feature toggle" module.
@ContextModule(name="job-system", dependsOn={"feature-toggle"})
Getting started
Maven
For Maven and Java add the dependency for dinject and add the annotation process dinject-generator as provided scope.
Get started with Maven
Get started with Gradle
<dependency>
<groupId>io.dinject</groupId>
<artifactId>dinject</artifactId>
<version>2.2</version>
</dependency>
<!-- Annotation processor -->
<dependency>
<groupId>io.dinject</groupId>
<artifactId>dinject-generator</artifactId>
<version>2.2</version>
<scope>provided</scope>
</dependency>
// obtain a bean and use it
CoffeeMaker coffeeMaker = SystemContext.getBean(CoffeeMaker.class);
coffeeMaker.brew();
// register all webroutes with Javalin
List<WebRoutes> webRoutes = SystemContext.getBeans(WebRoutes.class);
Javalin app = Javalin.create().disableStartupBanner();
app.routes(() -> webRoutes.forEach(WebRoutes::registerRoutes));
app.start(8080);
Use BeanContext (for testing)
Use BootContext to wire the context for testing purposes with mocks, stubs and test doubles.
// create the context programmatically
// ... with some options
try (BeanContext context = new BootContext()
.withMock(Grinder.class)
.withMock(Pump.class)
.load()) {
CoffeeMaker coffeeMaker = context.getBean(CoffeeMaker.class);
coffeeMaker.brew();
verify(grinder).grindBeans();
}