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://ift.tt/19L2NlC"
xmlns:xsi="http://ift.tt/ra1lAU"
xsi:schemaLocation="http://ift.tt/19L2NlC
http://ift.tt/18tV3H8"
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
Continue reading %Introduction to Contexts and Dependency Injection (CDI)%
by Alejandro Gervasio via SitePoint
No comments:
Post a Comment