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

How it works

Multiple modules

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>

Use SystemContext

Use SystemContext to obtain beans from the DI context.

More details

// 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.

More details

// 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();
}

Documentation