Dependency Injection

Dependency injection is one of the most important design patterns and is a key principle to a modular and component based architecture. The Java Standard for dependency injection is javax.inject (JSR330) that we use in combination with JSR250. Additionally, for scoping you can use CDI (Context and Dependency Injection) from JSR365.

There are many frameworks which support this standard including all recent Java EE application servers. Therefore in devonfw we rely on these open standards and can propagate patterns and code examples that work independent from the underlying frameworks.

Key Principles

Within dependency injection a bean is typically a reusable unit of your application providing an encapsulated functionality. This bean can be injected into other beans and it should in general be replaceable. As an example we can think of a use-case, a repository, etc. As best practice we use the following principles:

  • Stateless implementation
    By default such beans shall be implemented stateless. If you store state information in member variables you can easily run into concurrency problems and nasty bugs. This is easy to avoid by using local variables and separate state classes for complex state-information. Try to avoid stateful beans wherever possible. Only add state if you are fully aware of what you are doing and properly document this as a warning in your JavaDoc.

  • Usage of Java standards
    We use common standards (see above) that makes our code portable. Therefore we use standardized annotations like @Inject (javax.inject.Inject) instead of proprietary annotations such as @Autowired. Generally we avoid proprietary annotations in business code (logic layer).

  • Simple injection-style
    In general you can choose between constructor, setter or field injection. For simplicity we recommend to do private field injection as it is very compact and easy to maintain. We believe that constructor injection is bad for maintenance especially in case of inheritance (if you change the dependencies you need to refactor all sub-classes). Private field injection and public setter injection are very similar but setter injection is much more verbose (often you are even forced to have javadoc for all public methods). If you are writing re-usable library code setter injection will make sense as it is more flexible. In a business application you typically do not need that and can save a lot of boiler-plate code if you use private field injection instead. Nowadays you are using container infrastructure also for your tests (see testing) so there is no need to inject manually (what would require a public setter).

  • KISS
    To follow the KISS (keep it small and simple) principle we avoid advanced features (e.g. custom AOP, non-singleton beans) and only use them where necessary.

  • Separation of API and implementation
    For important components we should separate a self-contained API documented with JavaDoc from its implementation. Code from other components that wants to use the implementation shall only rely on the API. However, for things that will never be exchanged no API as interface is required you can skip such separation.

Example Bean

Here you can see the implementation of an example bean using dependency injection:

@ApplicationScoped
@Named("MyComponent")
public class MyComponentImpl implements MyComponent {
  @Inject
  private MyOtherComponent myOtherComponent;

  @PostConstruct
  public void init() {
    // initialization if required (otherwise omit this method)
  }

  @PreDestroy
  public void dispose() {
    // shutdown bean, free resources if required (otherwise omit this method)
  }

  ...
}

Here MyComponentImpl depends on MyOtherComponent that is injected into the field myOtherComponent because of the @Inject annotation. To make this work there must be exactly one bean in the container (e.g. spring or quarkus) that is an instance of MyOtherComponent. In order to put a bean into the container, we can use @ApplicationScoped in case of CDI (required for quarkus) for a stateless bean. In spring we can ommit a CDI annotation and the @Named annotation is already sufficient as a bean is stateless by default in spring. If we always use @ApplicationScoped we can make this more explicit and more portable accross different frameworks. So in our example we put MyComponentImpl into the container. That bean will be called MyComponent as we specified in the @Named annotation but we can also omit the name to use the classname as fallback. Now our bean can be injected into other beans using @Inject annotation either via MyComponent interface (recommended when interface is present) or even directly via MyComponentImpl. In case you omit the interface, you should also omit the Impl suffix or instead use Bean as suffix.

Multiple bean implementations

In some cases you might have multiple implementations as beans for the same interface. The following sub-sections handle the different scenarios to give you guidance.

Only one implementation in container

In some cases you still have only one implementation active as bean in the container at runtime. A typical example is that you have different implemenations for test and main usage. This case is easy, as @Inject will always be unique. The only thing you need to care about is how to configure your framework (spring, quarkus, etc.) to know which implementation to put in the container depending on specific configuration. In spring this can be archived via the proprietary @Profile annotaiton.

Injecting all of multiple implementations

In some situations you may have an interface that defines a kind of "plugin". You can have multiple implementations in your container and want to have all of them injected. Then you can request a list with all the bean implementations via the interface as in the following example:

  @Inject
  private List<MyConverter> converters;

Your code may iterate over all plugins (converters) and apply them sequentially. Please note that the injection will fail (at least in spring), when there is no bean available to inject. So you do not get an empty list injected but will get an exception on startup.

Injecting one of multiple implementations

Another scenario is that you have multiple implementations in your container coexisting, but for injection you may want to choose a specific implementation. Here you could use the @Named annotation to specify a unique identifier for each implementation what is called qualified injection:

@ApplicationScoped
@Named("UserAuthenticator")
public class UserAuthenticator implements Authenticator {
  ...
}
@ApplicationScoped
@Named("ServiceAuthenticator")
public class ServiceAuthenticator implements Authenticator {
  ...
}
public class MyUserComponent {
  @Inject
  @Named("UserAuthenticator")
  private Authenticator authenticator;
  ...
}
public class MyServiceComponent {
  @Inject
  @Named("ServiceAuthenticator")
  private Authenticator authenticator;
  ...
}

However, we discovered that this pattern is not so great: The identifiers in the @Named annotation are just strings that could easily break. You could use constants instead but still this is not the best solution.

In the end you can very much simplify this by just directly injecting the implementation instead:

@ApplicationScoped
public class UserAuthenticator implements Authenticator {
  ...
}
@ApplicationScoped
public class ServiceAuthenticator implements Authenticator {
  ...
}
public class MyUserComponent {
  @Inject
  private UserAuthenticator authenticator;
  ...
}
public class MyServiceComponent {
  @Inject
  private ServiceAuthenticator authenticator;
  ...
}

In case you want to strictly decouple from implementations, you can still create dedicated interfaces:

public interface UserAuthenticator extends Authenticator {}
@ApplicationScoped
public class UserAuthenticatorImpl implements UserAuthenticator {
  ...
}
public interface ServiceAuthenticator extends Authenticator {}
@ApplicationScoped
public class ServiceAuthenticatorImpl implements ServiceAuthenticator {
  ...
}
public class MyUserComponent {
  @Inject
  private UserAuthenticator authenticator;
  ...
}
public class MyServiceComponent {
  @Inject
  private ServiceAuthenticator authenticator;
  ...
}

However, as you can see this is again introducing additional boiler-plate code. While the principle to separate API and implementation and strictly decouple from implementation is valuable in general, you should always consider KISS, lean, and agile in contrast and balance pros and cons instead of blindly following dogmas.

Imports

Here are the import statements for the most important annotations for dependency injection

import javax.inject.Inject;
import javax.inject.Named;
import javax.enterprise.context.ApplicationScoped;
// import javax.enterprise.context.RequestScoped;
// import javax.enterprise.context.SessionScoped;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;

Dependencies

Please note that with Jakarta EE the dependencies have changed. When you want to start with Jakarta EE you should use these dependencies to get the annoations for dependency injection:

<!-- Basic injection annotations (JSR-330) -->
<dependency>
  <groupId>jakarta.inject</groupId>
  <artifactId>jakarta.inject-api</artifactId>
</dependency>
<!-- Basic lifecycle and security annotations (JSR-250)-->
<dependency>
  <groupId>jakarta.annotation</groupId>
  <artifactId>jakarta.annotation-api</artifactId>
</dependency>
<!-- Context and dependency injection API (JSR-365) -->
<dependency>
  <groupId>jakarta.enterprise</groupId>
  <artifactId>jakarta.enterprise.cdi-api</artifactId>
</dependency>

Please note that with quarkus you will get them as transitive dependencies out of the box. The above Jakarate EE dependencies replace these JEE depdencies:

<!-- Basic injection annotations (JSR-330) -->
<dependency>
  <groupId>javax.inject</groupId>
  <artifactId>javax.inject</artifactId>
</dependency>
<!-- Basic lifecycle and security annotations (JSR-250)-->
<dependency>
  <groupId>javax.annotation</groupId>
  <artifactId>javax.annotation-api</artifactId>
</dependency>
<!-- Context and dependency injection API (JSR-365) -->
<dependency>
  <groupId>jakarta.enterprise</groupId>
  <artifactId>jakarta.enterprise.cdi-api</artifactId>
</dependency>
Last updated 2023-11-20 10:37:01 UTC