DInject - Dependency injection

Current version - 1.1

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 Kubernates, 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;
  }

  ...

 

 

@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 onStart() {
  // 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 sometime want to wire the graph but supply some test doubles or mocks for some of the dependencies (similar to Spring test scope).

@Test
public void myComponentTest() {

  // we have some test doubles we want to use
  MyRedisApi mockRedis = mock(MyRedisApi.class);
  MyDatabaseApi mockDb = mock(MyDatabaseApi.class);

  // create the context programmatically with some
  // test doubles rather than the real dependencies
  try (BeanContext context = new BootContext()
    .withBeans(mockRedis, mockDb)
    .load()) {

    // perform a component test (using the test doubles)
    CoffeeMaker coffeeMaker = context.getBean(CoffeeMaker.class);
    coffeeMaker.brew();
  }
}

 

 

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

Add the 2 dependencies.

<dependency>
  <groupId>io.dinject</groupId>
  <artifactId>dinject</artifactId>
  <version>1.1</version>
</dependency>

<dependency>
  <groupId>io.dinject</groupId>
  <artifactId>dinject-generator</artifactId>
  <version>1.1</version>
  <scope>provided</scope>
</dependency>

Use BeanContext

Use SystemContext or BootContext to obtain the bean context and use the beans.

// obtain a bean from the context and use it
CoffeeMaker coffeeMaker = SystemContext.getBean(CoffeeMaker.class);
coffeeMaker.brew();
// create the context programmatically
// ... with some options
try (BeanContext context = new BootContext()
  .withNoShutdownHook()
  .withBeans(someTestDouble)
  .load()) {

  CoffeeMaker coffeeMaker = context.getBean(CoffeeMaker.class);
  coffeeMaker.brew();
}

// Using try with resources block above such that
// the context is closed

Documentation