/ CDI

CDI, an overview - Part 1

Introduction

I may be a Spring fanboy but I’m also convinced a technology should embrace standards. Although Spring is now a de facto standard, it is in competition with other products, like Google Guice for example. It makes my work as an architect harder since my craft is to devise the most perennial solutions: standards are my allies in achieving this goal.

CDI, formerly known as JSR 299, is an attempt at describing a true standard on Dependency Injection. What makes CDI appealing at first glance is that both SpringSource and Google took a seat in the specifications team. CDI is a part of the Java EE 6 stack, meaning an application running in a Java EE 6 compatible container can leverage CDI out-of-the-box.

Thus, for a Maven application, all that is required is to add the following dependency:

<dependency>
  <groupId>javax.enterprise</groupId>
  <artifactId>cdi-api</artifactId>
  <version>1.0-SP1</version>
  <scope>provided</scope>
</dependency>

The basics

How is it done? Let’s take a simple example. I still want to use a lighweight service layer: I just need a reference to such a service in my servlet. In order to do that, just add a reference to the service as an attribute and annotate it with @Inject:

public class WeldServlet extends HttpServlet {
  @Inject private HelloService helloService;
  ...
}

Note for Guice users: notice this is the same annotation, only in a standard package (javax.enterprise).

That’s all! No fancy XML configuration, no JNDI resource to use, no nothing. The only thing to do is to follow what I call the Highlander rule:

There can be only one.

This rule enforces that there must be one and only one class on the classpath that extends HelloService, thus fulfilling the interface contract. This is common sense: the injection must be deterministic and you can’t if you have more than one implementation to choose from.

Activation

In truth, if you just use the @Inject annotation, you probably will be faced by a NullPointerException. For CDI to activate, you’ll need to have a XML configuration file named beans.xml under WEB-INF for web applications or META-INF for jars. This file can be empty but is mandatory in order for CDI to bootstrap.

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://java.sun.com/xml/ns/javaee"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/beans_1_0.xsd">
</beans>

Qualifiers

Yet, such condition is not met most of the time since you’ll probably have at least another mock implementation for your unit tests. A concept is missing somewhere: it is the qualifier. Qualifiers add a quality to injection so that the Highlander rule is enforced between all classes that meet all the injection qualifiers. Thus, one can add a distinguishing qualifier to the mock service to resolve our quandary. Let’s design such a qualifier:

  • to be considered a qualifier, it uses the @Qualifier annotation
  • since injection is done at runtime, the retention policy must be runtime
  • target will be type, since it’s the class itself that will be annotated

The result is the following:

@Qualifier
@Retention(RUNTIME)
@Target( { TYPE })
public @interface Mock {}

Just annotate your mock service with @Mock and presto, the injection will succeed again. We haven’t seen how to inject the mocked service, but please bear with me, it will be adressed later.

Setter injection

In fact, this situation should not really happen (at least if you use Maven), since your standard classpath and your testing classpath should be different. Moreover, your unit testing should IMHO not use injection. This would slightly change my servlet to be used both in standard context and unit testing context: I will need to be able to inject in the former case and to set manually in the latter. This is not a problem, since CDI also accepts setter injection like in the following snippet:

public class WeldServlet extends HttpServlet {

  private HelloService helloService;

  @Inject
  public void setHelloService(HelloService helloService) {
    this.helloService = helloService;
  }
  ...
}

More qualifiers

As we saw, the qualifier use-case from above was not a good example. A much better one would be the need for a servlet to report an error. There are much ways of reporting this: mail, log, SMS, etc. The service used to report would be dependent of the servlet, meaning all the services should be available on the classpath. Now, as we have seen previously with @Mock, each service would be annotated with @Mail, @Log, @SMS, etc. What we did not see is how to inject the right service. Nothing could be easier, just tell CDI that which service you need by providing the needed qualifier:

public class WeldServlet extends HttpServlet {
  @Inject @Mail private ReportService reportService;
  ...
}

When you do not define any qualifier, CDI will use one for you under the cover, @Default. That’s why just annotating the mock service with @Mock succeeded: the real service was annotated implicitly with @Default and that was enough to fulfill the Higlander rule.

Qualifiers with attributes

Using the previous method will likely lead to an exponential proliferation of qualifiers that contradict Java EE 6 goals of readibility and maintainability. CDI will still let you reach them goals with annotation members. Now, the classes are:

public enum ReportType {
  MAIL, SMS, LOG;    // Adding one more type here is easy
}

@Qualifier
@Retention(RUNTIME)
@Target( { TYPE, FIELD })
public @interface Report {
    ReportType value();
}

@Report(MAIL)
public class MailReportServiceImpl implements ReportService {...}

public class WeldServlet extends HttpServlet {
  @Inject @Report(MAIL)
  private ReportService reportService;
  ...
}

Creating another reporting service consists of creating the service implementation itself and adding a value to the enumeration.

Singletons

Traditionally, services are stateless and, as such, have little interest in being instantiated more than once: it’s a good practice to make them singletons. Frameworks such as Spring make container-managed beans singletons as a default. Singletons creation is a feature of CDI but beware that singletons should be explicitly marked as such:

@Singleton
public class HelloServiceImpl implements HelloService {
  ...
}

Scoped beans

Singletons are only a particular case of a scope. In Java EE, scopes are well-defined regarding web applications: application, session, request and page. CDI does not manage page scope and adds a conversation scope that is tied to JSF. Scope usage in CDI is similar as Spring: injected bean will be tied to the defined scope and its visibility restricted to that scope.

Information relative to settings and preferences could well take place in a session-scoped bean for instance. Just add the right scope annotation to this bean:

@SessionScoped
public class UserSettings implements Serializable {
  private static final long serialVersionUID = 1L;
  ...
}

At this point, you should probably able to adress 80% of your needs. The next article will tackle advanced subjects like producers and alternatives.

Nicolas Fränkel

Nicolas Fränkel

Developer Advocate with 15+ years experience consulting for many different customers, in a wide range of contexts (such as telecoms, banking, insurances, large retail and public sector). Usually working on Java/Java EE and Spring technologies, but with focused interests like Rich Internet Applications, Testing, CI/CD and DevOps. Also double as a trainer and triples as a book author.

Read More
CDI, an overview - Part 1
Share this