Making Readable Code With Dependency Injection and Jakarta CDI

Otavio Santana
xgeeks
Published in
7 min readJan 24, 2022

Learn more about dependency injection with Jakarta CDI and enhance the effectiveness and readability of your code.

Within programming, object orientation (also known as OOP) is one of the wealthiest paradigms in documentation and standards, as with dependency injection. However, the most common is that these standards are misinterpreted and implemented and what would allow a clear vision and a good design for a cooperative application, in practice, harms the quality and efficiency of your code.

In this article, I will bring the advantages of exploring object orientation, more specifically dependency injection, together with the specification of the Java world that covers this API: the Jakarta CDI.

What Is Dependency Injection?

The first step is to contextualize what dependency injection is and why it differs from inversion of controls, although the two concepts are very confused.

In the context of OOP (Object Orientation), dependency is related to any direct subordination of a class, and that can be done in several ways. For example, through constructors, accessor methods, such as setters, or creation methods, such as the Factory Method.

This example injection also can measure how much one class is linked to another, which we call coupling. It is vital because it is part of the job of good code design to be concerned with high cohesion and low coupling.

The Difference Between Dependency Injection and Inversion

Once the basic concepts of OOP are explained, the next step is to understand what differs between injection and dependency inversion.

This distinction happens because of what we call the DIP, or Dependency Inversion Principle, which works as a guide to good practices focused on decoupling a class from concrete dependencies through two recommendations:

  1. High-level modules must not depend on low-level modules, but both must depend on abstractions (Example: interfaces).
  2. Abstractions, in turn, should not depend on details. However, parties or concrete implementations must depend on abstractions.

While inversion assumes all these requirements, dependency injection is a DIP technique to supply a class’s dependencies, usually through a constructor, attribute, or a method such as a setter.

In short: dependency injection is part of the best practices advocated by inversion of control (IoC).

As tricky as these concepts are, at first, they are fundamental because they can be correlated to the Barbara Liskov principle. After all, both the Liskov principle and dependency injection are part of SOLID.

Where Does CDI Come in, and How Do We Get to Jakarta CDI?

As with injection and inversion, it is crucial to take a step back and contextualize what CDI is.

The CDI (Dependency and Contexts Injection) arose from the need to create a specification within the JCP, JSR 365, mainly because, in the Java world and projects like Spring and Google Guice, there are several solutions and implementations of these frameworks.

Briefly, the purpose of Jakarta CDI is to allow the management of stateful or stateless component life control via context and component injection.

It is a project in constant evolution. If you want to keep up with the latest updates, it’s worth knowing Eclipse Open-DI. It is currently being optimized to improve booting through CDI Lite.

CDI in Practice

To exemplify the features of CDI, we will create a simple Java SE application with CDI to show six essential elements within CDI, which are:

  1. Perform a single injection.
  2. Differentiate implementations through qualifications.
  3. Teach CDI to create and distribute objects.
  4. Apply Observer.
  5. Apply Decorator.
  6. Use the interceptor.

In this article, even to avoid making the text too big, we will highlight the codes. If you want, you can later access the code in full.

It is nice to mention that this content was created with Karina Varela and is part of the classes we took throughout 2021, mainly in American and European countries, in partnership with Microstream and Payara.

Perform a Single Injection

In our first implementation, we will create a “Vehicle” interface and an implementation using the Java SE container.

An important point: we have the context in addition to the injection of dependencies. We can define the life cycle of a class or bean, and, in this case, the implementation will have the application scope.

In this piece of code, we have the interface Vehicle and its respective implementation, Car, in which the CDI will be able to inject an instance both through the interface and through the implementation.

Since we are talking about best practices, please understand that we are using the case of a single interface for a single implementation for teaching purposes. In practice, ideally, you don’t do this so as not to break the KISS principle.

A good indicator for this is interfaces that start with “I” or implementations that end with “Impl”. In addition to being an indicator of unnecessary complexity, it is a code smell. After all, it is not a meaningful name, breaking the principle of the Clean Code.

Differentiate Implementations Through Qualifications

In the previous example, we had the case of a one-to-one relationship, that is, an interface for a single implementation. But what happens when we have multiple implementations for the same interface?

If we don’t have this set, CDI won’t know the default implementation and will throw the AmbiguousResolutionException. You can solve this problem using the Named annotation or a Qualifier. In our case, we will use the Qualifiers.

Imagine the following scenario in which we have an orchestra with several musical instruments. This orchestra will need to play all the instruments together and be able to discriminate between them. In our example, this scenario would look something like the following code:

We have the instrument interface, an enumerator to define its type, and the CDI Qualifier through a new annotation in this code example.

After the implementations and qualifiers, it will be pretty simple for the orchestra to play all the instruments together and special tools considering their different types, for example, string, percussion, or wood.

The CDI container injects an Orchestra instance and demonstrates the use of each instrument.

Teach CDI to Create and Distribute Objects

In addition to defining its scope, it is often impossible to perform activities such as determining or creating a class within the CDI container. This type of resource is essential for cases such as, for example, when we want to inject a connection to some service.

It is possible to teach the container to create instances and destroy them within CDI through the Produces and Disposes annotations, respectively. For this example, we will create a connection that will be created and closed.

Right now, we have a connection from which we teach how the CDI should create and close the connection.

Apply Observer

We cannot forget the Observer among the patterns very present in corporate architectures and present in the GoF.

One of the assessments I make of this pattern is that, given its importance, we can see it similarly in architectural practices, such as Event-Driven, and even in a paradigm with reactive programming.

In CDI, we can handle events synchronously and asynchronously. Imagine, for example, that we have a journalist, and he will need to notify all the necessary media. If we do the coupling of these media directly in the “Journalist” class, every time a media is added or removed, it will be necessary to modify it. It breaks the open-closed principle to solve; we will use Observer.

So we created a Journalist class that notifies the media, a novelty thanks to the Observer pattern with the CDI. The event is triggered by the Event instance, and to listen for it; it is necessary to use the @Observers annotation with the specific parameter to be listened to.

Apply Decorator

The Decorator pattern allows us to add behavior inside the object, obeying the principle of composition over inheritance. We see this within the Java world with wrappers of primitive types like Integer, Double, Long, etc.

We created a worker abstraction, the Programmer, and the Manager responsible for delegating the worker. In this way, we could add a behavior, such as sending an email, without modifying the programmer.

The Interceptor

The CDI can also perform and control some operations in the code in a transversal way, similar to what we do with aspect-oriented programming and cutpoints with Spring.

The CDI interceptor tends to be quite helpful when we want, for example, a logging mechanism, transaction control, or a timer for a method that will be executed, among others. In this case, the sample used will be a timer with an interceptor.

The interceptor implementation will allow the creation of the annotation that will be used to indicate the intercept, and the class, in turn, will define how this intercept will be implemented, which, in our case, will be a counter.

The following and last example is to create two methods that we will count, with two implementations, one of which will have a delay of two seconds.

Dependency Injection With Jakarta CDI: More Options for Your Development

Given these practical examples, we can see the various possibilities and challenges possible when working with Object Orientation and better understand some of its principles around dependency injection and CDI.

It’s important to remember that, as Uncle Ben once said, “with great power comes great responsibility.” As such, common sense remains the best compass for exploring these and other CDI features, resulting in a high-quality design and clarity.

References

If you enjoy working on large-scale projects with global impact and if you like a real challenge, feel free to reach out to us at xgeeks! We are growing our team and you might be the next one to join this group of talented people 😉

Check out our social media channels if you want to get a sneak peek of life at xgeeks! See you soon!

--

--

Otavio Santana
xgeeks
Editor for

Empowering developers worldwide to deliver better software in the Cloud. Staff Eg xgeeksio