CDI Tutorial – Introduction to Contexts and Dependency Injection

Last Updated:  May 13, 2020 | Published: October 7, 2019

Dependency Injection (DI) is one of the central techniques in today's applications and targets Separation of concerns. Not only makes this testing easier, but you are also not in charge to know how to construct the instance of a requested class. With Java/Jakarta EE we have a specification which (besides other topics) covers this: Contexts and Dependency Injection (short CDI). CDI is also part of the Eclipse MicroProfile project and many other Java/Jakarta EE specifications already use it internally or plan to use it.

Learn more about the Contexts and Dependency Injection (CDI) specification, its annotations and how to use it in this blog post. Please note that I won't cover every aspect of this spec and rather concentrate on the most important parts. For more in-depth knowledge, have a look at the following book.

Specification profile: Contexts and Dependency Injection (CDI)

  • Current version: 2.0
  • GitHub repository
  • Specification homepage
  • Basic use case: provide a typesafe dependency injection mechanism

Basic dependency injection with CDI

The main use case for CDI is to provide a typesafe dependency injection mechanism. To make a Java class injectable and managed by the CDI container, you just need a default no-args constructor or a constructor with a @Inject annotation.

If you use no further annotations, you have to tell CDI to scan your project for all available beans. You can achieve this which a beans.xml file inside src/main/resources/webapp/WEB-INF using the bean-discovery-mode:

Using this setup, the following BookService can inject an instance of the IsbnValidator class:

You can inject beans via either field-, setter-, constructor-injection or request a bean manually from the CDI runtime:

Once CDI manages a bean, the instances have a well-defined lifecycle and are bound to a scope. You can interact with the lifecycle of a bean while using e.g. @PostConstruct or @PreDestroy. The default scope, if you don't specify any (like in the example above), is the pseudo-scope @Dependent. With this scope, an instance of your bean is bound to the scope of the bean it gets injected to and won't be shared.

However, you can specify the scope of your bean using the available scopes in CDI:

  • @RequestScoped – bound to an HTTP request
  • @SessionScoped – bound to the HTTP session of a user
  • @ApplicationScoped – like a Singleton, one instance per application
  • @ConversationScoped – bound to a conversation context e.g. wizard-like web app

If you need a more dynamic approach for creating a bean that is managed by CDI you can use the @Produces annotation. This gives you access to the InjectionPoint which contains metadata about the class who requested an instance:

Using qualifiers to specify beans

In the previous chapter, we looked at the simplest scenario where we just have one possible bean to inject. Imagine the following scenario where have multiple implementations of an interface:

If we now request a bean of the type BookDistributor, which instance do we get? The BookPlaneDistributor or an instance of BookShipDistributor?

… well, we get nothing but an exception, as the CDI runtime doesn't know which implementation to inject:

The stack trace contains an important hint on how to fix such a scenario. If we don't further qualify a bean our beans have the default qualifiers @Any and @Default. In the scenario above the BookStorage class requests for a BookDistributor and also does not specify anything else, meaning it will get the @Default bean. As there are two beans with this default behavior, dependency injection is not possible (without further adjustments) here.

To fix the error above, we have to introduce qualifiers and further specify which concrete bean we want. A qualifier is a Java annotation including @Qualifier:

Once we have this annotation, we can use it both for the implementation and at the injection point:

… and now have a proper injection of our requested bean.

Getting started with Eclipse MicroProfile Course Bundle

NEWS: Up-to-date with MicroProfile 3.3

All you need to know about MicroProfile

Looking for a resource to learn more about MicroProfile and all its specifications in-depth? Signup for the MicroProfile Course Bundle (E-Book & Video Course)

Above all, you can also always request for all instances matching a Java type using the Instance<T>  wrapper class:

Enrich functionality with decorators & interceptors

With CDI we have two mechanisms to enrich/extend the functionality of a class without changing the implementation: Decorators and Interceptors.

Decorators allow a type-safe way to decorate your actual implementation. Given the following example of an Account  interface and one implementation:

We can now write a decorator to make special checks if the amount of money to withdraw meets a threshold:

With interceptors, we get a more generic approach and don't have the same method signature as the intercepted class, rather an InvocationContext. This offers more flexibility as we can reuse our interceptor on multiple classes/methods. A lot of cross-cutting logic in Java/Jakarta EE like transactions and security is actually achieved with interceptors. For an example on how to write interceptors, have a look at one of my previous blog posts.

Both decorators and interceptors are inactive by default. To activate them, you either have to specify them in your beans.xml file:

or using the @Priority annotation and specify the priority value:

Decouple components with CDI events

Last but not least, the CDI specification provides a sophisticated event notification model. You can use this to decouple your components and use the Observer pattern to notify all listeners once a new event is available.

The event notification in CDI is available both in a synchronous and asynchronous way. The payload of the event can be any Java class and you can use qualifiers to further specialize an event. Firing an event is as simple as the following:

Observing such an event requires the @Observes annotation on the receiver-side:

Using the asynchronous way, you receive a CompletionStage<T> as a result and can add further processing steps or handle errors:

Listening to async events requires the @ObservesAsync  annotation instead of @Observes :

YouTube video for using CDI 2.0 specification

Watch the following YouTube video of my Getting started with Eclipse MicroProfile series to see CDI 2.0 in action:

If you are looking for resources to learn more advanced CDI concepts in-depth, have a look at this book.

You can find the source code with further instructions to run this example on GitHub.

Have fun using the CDI specification,

Phil

  • {"email":"Email address invalid","url":"Website address invalid","required":"Required field missing"}

    Join Our Mailing List To Get 3x Free Cheat Sheets

    Free Java Cheat Sheets
    >