Introduction to Contexts and Dependency Injection (CDI)

Share this article

Introduction to Contexts and Dependency Injection (CDI)

Key Takeaways

  • CDI simplifies dependency injection in Java, allowing developers to focus more on coding and less on managing dependencies, thanks to its annotation-based framework.
  • The @Inject annotation is crucial in CDI, enabling automatic dependency injection into constructors, fields, or setter methods, thus promoting cleaner and more maintainable code.
  • CDI supports different types of injection and qualifiers, such as @Default, @Alternative, and @Named, which help manage multiple implementations of a single interface.
  • The @Produces annotation in CDI facilitates the integration of the factory pattern within the framework, allowing for custom creation and injection of dependencies.
  • Custom qualifiers in CDI offer a robust way to handle injection more semantically and selectively, enhancing type safety and reducing ambiguities in dependency management.

Table of Contents

CDI (contexts and dependency injection) is a dependency injection (DI) specification bundled with Java EE 6 and higher. It implements an annotation-based DI framework, making it easier to fully automate dependency management and letting developers focus on coding, rather than dealing with DI in all its different slants.

DI is one of the most ubiquitous forms of inversion of control used in object-oriented design. But this doesn’t imply that implementing the pattern manually is inherently easy, as it always requires explicitly implementing some kind of creational pattern, such as factories or builders as the underlying injectors.

Admittedly, several popular frameworks, including Google Guice and even Spring will make the construction of object graphs a lot less painful. CDI is standardized, though, giving you a choice of several implementations. And struggling with the burdens of a fully-fledged framework like Spring isn’t always the best approach to take if you just want to do plain DI. CDI, on the other hand, is aimed at making DI a no-brainer, without having to resort to any external framework whatsoever.

First and foremost, it needs a working implementation to get it up and running. There are a few available right now, including OpenWebBeans, CauchoCanDI and Weld. The latter is the reference implementation, which can be used in different contexts and environments, including Java SE.

Last but not least, there are no restrictions for injecting dependencies, a feature that allows to inject Java EE resources, such as JPA entity managers, in a snap. Other frameworks (Guice is a prime example of this) also provide support for JPA / entity manager injection, but the process is pretty cumbersome and not fully standardized. This opens the door to using the standard in database-driven applications, a topic that I plan to cover in depth in a follow-up.

With that said, in this tutorial I’ll provide you with an pragmatic guide to using CDI / Weld in Java SE.

Easy Dependency Injection with CDI

DI per se becomes a necessity when it comes to designing decoupled components based on several polymorphic implementations. The typical use case is having a segregated contract, defined through an interface or an abstract class, and multiple implementations, where one or more of these must be injected into a client object at runtime.

Creating an injection point

Consider the following example, which is part of a naive application that processes a given string in a few basic ways:

public interface TextProcessor {

    String processText(String text);

}

The API for processing strings is so basic that it doesn’t deserve any further analysis. So, here are the corresponding implementations:

public class LowerCaseTextProcessor implements TextProcessor {

    public String processText(String text) {
        return text.toLowerCase();
    }

}

public class UppercaseTextProcessor implements TextProcessor {

    public String processText(String text) {
        return text.toUpperCase();
    }

}

public class ReverseTextProcessor implements TextProcessor {

    public String processText(String text) {
        return new StringBuilder(text).reverse().toString();
    }

}

At this point, we’ve created three simple implementations, whose functionality boils down to lowercasing, uppercasing and reversing a given string.

Now, suppose we want to inject at runtime an instance of these classes into a client object, in order to process a string entered in the console. In such a case, first we’d need to define a class similar to this one:

public class TextApplication {

    private TextProcessor textProcessor;
    private BufferedReader userInputReader;

    @Inject
    public TextApplication(TextProcessor textProcessor) {
        this.textProcessor = textProcessor;
        this.userInputReader =
                new BufferedReader(new InputStreamReader(System.in));
    }

    public void run() throws IOException {
        System.out.println("Enter the text to process : ");
        String text = userInputReader.readLine();
        System.out.println(textProcessor.processText(text));
    }

}

Asides from the evil new operator I used to create a BufferedReader object (bear with me for now, as I’ll refactor it later), the first thing worth pointing out here is the use of the @Inject annotation, which tells CDI that an instance of the TextProcessor interface must be injected into the class’ constructor. This is vanilla constructor injection made easy!

Bootstrapping Weld

Well, not so easy. First we need to download the Weld artifact – here it is for Maven:

<dependency>
    <groupId>org.jboss.weld.se</groupId>
    <artifactId>weld-se</artifactId>
    <version>2.4.0.Final</version>
</dependency>

With the Weld artifact in place, we must create a beans.xml file in the src/main/java/resources/META-INF/ directory, as CDI needs to scan this file, even if it doesn’t contain additional injection options. At its bare bones, here’s how a typical version of this file might look:

<?xml version="1.0" encoding="UTF-8"?>
<beans
        xmlns="http://xmlns.jcp.org/xml/ns/javaee"
        xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
                      http://xmlns.jcp.org/xml/ns/javaee/beans_1_1.xsd"
        bean-discovery-mode="all">
</beans>

Next, we must programmatically bootstrap Weld, grab an instance of the TextApplication class from the Weld container, and finally start using it at will, as follows:

public class Main {

    public static void main(String[] args) throws IOException {
        Weld weld = new Weld();
        WeldContainer container = weld.initialize();
        TextApplication textApplication =
                container.instance().select(TextApplication.class).get();
        textApplication.run();
        weld.shutdown();
    }

}

Everything should work as expected. Or shouldn’t it?

Qualifying Implementations with @Default and @Alternative

When you run the application, CDI will throw an ugly DeploymentException with the message “Unsatisfied dependencies for type TextProcessor with qualifiers @Default”. In a nutshell, CDI is just telling you that it doesn’t know what implementation to inject into the constructor of the TextApplication class.

How do we tell CDI what implementation to pick up at runtime? Well, the first and most elemental approach is to use the @Default and @Alternative annotations and instruct CDI to inject a specific TextProcessor implementation.

By default CDI assigns the @Default annotation to all the potentially injectable objects. So, if we’d want to inject the UppercaseTextProcessor implementation, the implementers should be annotated as follows:

@Default
public class UppercaseTextProcessor implements TextProcessor { ... }

@Alternative
public class LowercaseTextProcessor implements TextProcessor { ... }

@Alternative
public class ReverseTextProcessor implements TextProcessor { ... }

After changing the classes run the application once again. You should be prompted to enter a string in the console, and as expected, the output would be a nicely uppercased version! See how easy is to use CDI / Weld and how powerful they are, even when doing something as trivial as parsing a string?

If you’re picky enough, however, you’ll be wondering what’s the point of having several interface implementers annotated with @Alternative, if only the one marked with @Default will get injected? Alternatives are a nifty feature that allow you to choose the implementation at launch time as opposed to at compile time as we did above. To do that annotate all the implementers with the @Alternative annotation and use the mandatory beans.xml file for specifying which implementation should be injected into a given client object:

<beans>
    <alternatives>
<!-- in a real system the class names need to be fully qualified -->
        <class>UppercaseTextProcessor</class>
<!--    <class>LowercaseTextProcessor</class> -->
<!--    <class>ReverseTextProcessor</class> -->
    </alternatives>
</beans>

As shown above, all the implementations commented out won’t be parsed by CDI, thus the UppercaseTextProcessor implementation will be the one that’s injected. Obviously, there’s plenty of room to experiment here. If you’re interested in peeking at a few more handy options, feel free to check the official docs.

But there’s a lot more to cover yet. Even when the @Default and @Alternative annotations do a fairly decent job when it comes to instructing CDI about what implementations to inject at runtime into a client object, their functionality is pretty limited, as most IDEs just won’t give you a single clue on what you’re trying to inject and where.

To deal with this issue, CDI provides a few more intuitive mechanisms for achieving the same result, including the @Named annotation and custom qualifiers.

Introducing the @Named Annotation

In fact, using the @Named annotation isn’t so different from working with @Default and @Alternative. Its major benefit lies on the ability of binding a more semantic and meaningful name to a specific implementation. In addition, many IDEs will give you a hint on what dependency you want to inject.

Consider the refactored versions of the previous implementations:

@Named("Uppercase")
public class UppercaseTextProcessor implements TextProcessor { ... }

@Named("Lowercase")
public class LowercaseTextProcessor implements TextProcessor { ... }

@Named("Reverse")
public class ReverseTextProcessor implements TextProcessor { ... }

Now, if we’d wish to inject an instance of the UppercaseTextProcessor class, the injection point should be refactored as follows:

@Inject
public TextApplication(
        @Named("Uppercase") TextProcessor textProcessor) { ... }

This will work like a charm and you will get the type you want without creating a compile-time dependency on it. Even better, try injecting different implementations and entering several values in the console, and you’ll see for yourself why CDI is a powerful beast when it comes to managing dependencies in a straightforward manner.

Moreover, CDI won’t constrain you to just doing plain constructor injection, as field and setter injection are neatly supported as well.

Published by Andreas Ruda under CC-BY 2.0
Published by Andreas Ruda under CC-BY 2.0

Using Field and Setter Injection

Even when the general consensus among seasoned developers is that constructor injection is considered the most effective way to do DI, as it makes sure that an object is always initialized in a valid state, CDI will let you do field and setter injection in a painless way.

Here’s how the former should be done, when the implementers of the TextProcessorinterface use the @Default and @Alternative annotations:

@Inject
private TextProcessor textProcessor;

And here’s the same injection method, but this time using the @Named annotation:

@Inject
@Named("Uppercase")
private TextProcessor textProcessor;

The same rules regarding injection syntax apply to the latter:

// setter injection with @Default / @Alternative
@Inject
public void setTextProcessor(TextProcessor textProcessor) {
    this.textProcessor = textProcessor;
}

// setter injection with @Named
@Inject
public void setTextProcessor(
        @Named("Uppercase") TextProcessor textProcessor) {
    this.textProcessor = textProcessor;
}

At this point, it’s clear to see that CDI will let you inject literally any kind of object(s)… yes, into any other object(s), either by using field, setter or constructor injection.

But there’s a tiny catch here: What happens if a dependency requires some type of initial configuration, or has its own network of dependencies? In other words, how to deal with a situation where a class declares a dependency on an entire object graph?

This is precisely the case of the TextApplication class. It uses a BufferedReader object, sure, but the object is created ad-hoc in the constructor. This is exactly the code smell that CDI tries to address, and the reason why it exists after all!

Using the @Produces Annotation

To this end the standard provides the @Produces annotation, which allows to encapsulate the creation of dependencies behind the fences of factory classes.

Consider the refactored constructor of the TextApplication class:

@Inject
public TextApplication(
        @UppercaseTextProcessor TextProcessor textProcessor,
        BufferedReader userInputReader) {
    this.textProcessor = textProcessor;
    this.userInputReader = userInputReader;
}

Definitively, it looks much better. The evil new operator has been removed from the constructor, hence making the class much more declarative and easier to test. It doesn’t hide its direct dependency to a BufferedReader object anymore. Logically this collaborator has to be created in some way.

Here’s exactly where the @Produces annotation comes into play:

public class BufferedReaderProducer {

    @Produces
    BufferedReader getBufferedReader() {
        return new BufferedReader(new InputStreamReader(System.in));
    }

}

In a nutshell, the BufferedReaderProducer class is just a plain factory that creates a BufferedReader object. Considering that the getBufferedReader() method has been annotated with the @Produces annotation, in CDI terminology the method is called a producer.

As in this case, the BufferedReader class isn’t an implementer of any interface in particular, CDI will first call the pertaining producer method for getting the object in question, and then it’ll inject it into the client class.

It’s clear to see that producer methods are a very powerful CDI feature, as they’re a neat implementation of the factory pattern, wrapped inside the boundaries of just one single annotation. But CDI provides even more useful and advanced features for defining injection points, such as custom qualifiers.

Working with Custom Qualifiers

Custom qualifiers are a must when you want to create more semantic, selective, and meaningful injection points across an application. While the name is slightly intimidating, a custom qualifier is just a simple annotation that is associated with a specific implementation. At first glance it is like a @Named annotation that is always used with the same string qualifier.

For instance, for the text processor classes the qualifiers can be defined as follows:

@Qualifier
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.METHOD,
        ElementType.TYPE, ElementType.PARAMETER})
public @interface LowercaseTextProcessor { }

@Qualifier
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.METHOD,
        ElementType.TYPE, ElementType.PARAMETER})
public @interface UppercaseTextProcessor { }

@Qualifier
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.METHOD,
        ElementType.TYPE, ElementType.PARAMETER})
public @interface ReverseTextProcessor { }

The @Qualifier annotation tells CDI that they will be used for injection. But first they need to be applied to an implementation so that CDI knows which types they are associated with:

@LowercaseTextProcessor
public class LowercaseTextProcessor implements TextProcessor { ... }

@UppercaseTextProcessor
public class UppercaseTextProcessor implements TextProcessor { ... }

@ReverseTextProcessor
public class ReverseTextProcessor implements TextProcessor { ... }

Now that CDI was made aware that, for example, @UppercaseTextProcessor (the annotation) injects UppercaseTextProcessor (the implementation), the corresponding injection point can look like this:

@Inject
public TextApplication(
    @UppercaseTextProcessor TextProcessor textProcessor,
    BufferedReader userInputReader) {
        ...
}

Pretty straightforward, right? The biggest benefits of using custom qualifiers over simpler annotations, such as @Named, can be boiled down to the following points:

  1. Make type safety even stronger. For instance, binding a custom qualifier to a subclass of a base class in a given hierarchy makes sure that only the subtype will get injected. The @Default, @Alternative and @Named annotations currently don’t support this feature. Also, neither can be bound to a retention policy or to a legal target but custom qualifiers can, which makes it easier to create more focused qualifiers.

  2. Resolve injection ambiguity exceptions. It is easy to create ambiguous situations, for example if more than one implementation is marked with @Default or with the same qualifier for @Named. For obvious reasons, in such cases CDI won’t be able to figure out at run time what implementation must be injected into the target object (the injection process is ambiguous) and will thus throw a deployment exception – in CDI terminology, a dependency injection ambiguity exception. Custom qualifiers make it easier to avoid these ambiguities by means of semantic annotations, which contain injection-related metadata.

  3. Gather metadata and bind it to injectable classes. This includes the retention policy and the legal target(s) where the qualifiers can be applied, such as fields and methods.

At this point, it’s easy to see that custom qualifiers are a more refined and effective mechanism for binding implementations to injection points. But they also have a drawback worth stressing: They may easily produce an exponential proliferation of interface annotations, thus unnecessarily polluting your code.

Pretty much the same can be said about producer methods: You don’t want to have a bunch of factories scattered all over your application, only because you can, right?

As a rule of thumb, use these features when they’re really needed, and only if the @Default, @Alternative and @Named annotations just don’t fit your needs.

Summary

Like many other design patterns, dependency injection is a language-agnostic paradigm that plays a fundamental role in object-oriented design. To put it in a simpler way: If you’re doing good object-oriented design, you just can’t live without DI.

In this tutorial, I walked you through an approachable guide on using CDI / Weld in a very pragmatic way. You learned how to do painless DI by using the standard’s core features, such as the @Default and @Alternative annotations, and also working with more refined ones, including producer methods and custom qualifiers.

Quite possibly, the most appealing facet of CDI (and all its implementations) is that it does what it promises at face value, no strings attached: plain neat DI without using an external framework. Moreover, considering that any object can become an injectable component as long as it uses the standard’s annotations, it’s pretty simple to use CDI in a much more productive way.

It’s clear to see that using CDI / Weld as the underlying DI mechanism in Java SE can be mastered without beating your head against a wall. You’ll be wondering, of course, how to consume CDI in a real-world use case. Building a string parsing application that runs in the console is all well and fine from a didactic point of view, and certainly can be used as a starting point to get familiarized with the standard’s basic facts.

But there’s still more ground to cover. As I stated in the introduction, CDI allows to inject Java EE resources with the same ease as when injecting POJOs. This inherent ability can be exploited for using CDI in the development of database-driven applications.

To illustrate this, in the second part we’ll learn how to consume the standard for developing a JPA-based application.

Stay tuned!

Frequently Asked Questions (FAQs) about Contexts and Dependency Injection (CDI)

What is the main difference between CDI and DI?

Contexts and Dependency Injection (CDI) and Dependency Injection (DI) are both design patterns used in Java to manage dependencies between objects. The main difference between the two lies in their scope. DI is a broader concept that refers to the process of providing the required dependencies to an object. On the other hand, CDI is a specific type of DI that not only manages dependencies but also manages the lifecycle of stateful components in a contextual scope.

How does CDI improve code quality?

CDI improves code quality by promoting loose coupling and enhancing modularity. It allows developers to inject dependencies, which means that the dependent object does not need to look up its dependencies or even know where they come from. This makes the code easier to test, maintain, and reuse. Furthermore, CDI manages the lifecycle of stateful components, which can help to prevent resource leaks and other common problems.

Can CDI be used with other Java frameworks?

Yes, CDI can be used with other Java frameworks. It is part of the Java EE platform, but it can also be used in Java SE applications. Furthermore, CDI can be integrated with other frameworks such as Spring and JSF to provide a unified approach to dependency management.

What is the role of the @Inject annotation in CDI?

The @Inject annotation is used in CDI to mark a dependency that should be injected. When the CDI container sees this annotation, it will automatically provide the required dependency at runtime. This eliminates the need for the dependent object to look up its dependencies, which can make the code cleaner and easier to maintain.

How does CDI handle multiple implementations of an interface?

CDI provides a way to handle multiple implementations of an interface using the @Qualifier annotation. This annotation can be used to create custom annotations that specify which implementation should be used in a particular context. This allows developers to control which implementation is used without changing the code that uses the interface.

What is the difference between @RequestScoped and @SessionScoped in CDI?

These are two types of contextual scopes in CDI. @RequestScoped means that the bean is created and destroyed within the lifecycle of a single HTTP request. On the other hand, @SessionScoped means that the bean is created for a particular HTTP session and destroyed when that session ends.

How does CDI support event-driven programming?

CDI supports event-driven programming through its event notification model. This model allows beans to produce and consume events. When an event is fired, the CDI container automatically notifies all beans that observe that type of event.

Can CDI be used in a multi-threaded environment?

Yes, CDI can be used in a multi-threaded environment. The CDI container manages the lifecycle of beans and ensures that they are used in a thread-safe manner. However, developers still need to be aware of potential concurrency issues and design their code accordingly.

What is the role of the beans.xml file in CDI?

The beans.xml file is used in CDI to enable the CDI container and to specify which beans should be managed by the container. This file is typically placed in the META-INF directory of the application.

How does CDI compare to Spring’s dependency injection?

Both CDI and Spring’s dependency injection provide similar functionality, but there are some differences. One of the main differences is that CDI is part of the Java EE platform, while Spring is a separate framework. This means that CDI can be used in any Java EE application, while Spring requires additional configuration. Furthermore, CDI provides some features that are not available in Spring, such as contextual lifecycle management and event notification.

Alejandro GervasioAlejandro Gervasio
View Author

Alejandro Gervasio is a senior System Analyst from Argentina who has been involved in software development since the mid-80's. He has more than 12 years of experience in PHP development, 10 years in Java Programming, Object-Oriented Design, and most of the client-side technologies available out there.

dependency injectiondesign patternsJava EEnicolaipWeld
Share this article
Read Next
Get the freshest news and resources for developers, designers and digital creators in your inbox each week