So many tutorials about CDI have been written I’m a little nervous putting down my own. But here we are.
I believe that most of the CDI articles and posts I’ve read over my many, many years of programming don’t follow the right road for developers just starting out with CDI. As a result, people think of CDI as magic. Because its terminology is also unusual, misconceptions get built upon at the earliest stages of learning and you end up with developers sticking beans.xml
files everywhere in a prayer to the gods to get their stuff to somehow work.
I also want to try to get the high level concepts in place first before diving deeper. As a result, I’m going to speak a bit fast and loose here to start with. My hope is that over time these foundations will demystify the great CDI machine.
Lastly, I’m primarily focusing on CDI alone, i.e. not on any aspect of its integrations with technologies like Java EE.
Let’s dive in. We’ll start with a section that deals with CDI and @Inject
, since that’s how most people first encounter CDI, but then we’ll move rapidly on to the dependency injection mindset, which is much more important.
CDI Makes @Inject
Work
For anyone wondering “what is CDI? Why would I use it?” the quickest answer that makes immediate sense is:
CDI is the magic machine that lets you use @javax.inject.Inject
.
OK, so why would I use @Inject
? What does it do for me?
Field Injection
Let’s say you have an instance field in your class, and you want it magically set to something, regardless of whether it’s public
, protected
, package-protected or private
.
Do this:
@Inject // magic!
private Gorp myGorp;
CDI makes that work.
That is, some Gorp
instance (we’ll talk about which one, and where it came from, in a bit) will get put there by CDI. Cool!
Think a bit about this: you didn’t have to go hunting around for a Gorp
instance yourself. You didn’t have to look up what kind of Gorp
to make from a properties file or JNDI or ServiceLocator
or anything like that. You asked for a Gorp
, and a Gorp
was delivered unto you from outer space. Nice. Simple. Less of your code; therefore fewer bugs that will be assigned to you. A smaller mental model: fewer second- or third-order concepts in your head means more clarity in programming the important stuff you need to do.
Injection is the overall term for this kind of magic: something comes along (CDI in this case) and injects a Gorp
into, in this case, an instance field of your (business-critical, of course) class. The thing that is being injected is a dependency: it’s something you need for some reason, so therefore whether you like it or not you depend on it.
So when you get a dependency injected into something, you have dependency injection.
We’ll call this particular case of injection field injection.
Where else can you use @Inject
?
Method Parameter Injection
When CDI is in the world, you can also use @Inject
on methods. It’s the same kind of thing. If you do this:
@Inject
private void consume(final Gorp gorp) {
// nom nom
}
…the gorp
“slot” will be filled with a Gorp
instance (as before, we’ll talk about which one, and where it came from, and when in a bit) by CDI. This is often referred to as setter injection, or setter method injection, or parameter injection, and you might think your method therefore has to be named something like setGorp
, but it can be named anything. The important part is that @Inject
“goes on” the method.
Methods with @Inject
“on” them can have as many parameters as you want. CDI will try to “fill” all of them. So if you do this:
@Inject
private void consume(final Gorp gorp, final Cheese cheese) {
// nom nom nom nom
}
…CDI will “fill” the gorp
“slot” with a Gorp
instance, and the cheese
“slot” with a Cheese
instance.
Constructor Parameter Injection
CDI also makes @Inject
on constructors work. Constructors can be public
, protected
, package-private or private
. Just as with methods, @Inject
-annotated constructors’ parameter “slots” will be “filled” by CDI. So if you do this:
@Inject
private Chocolate(final PeanutButter peanutButter) {
super();
}
…the peanutButter
“slot” will be filled by CDI with a PeanutButter
instance.
Making @Inject
Work Is a Kind of Dependency Injection
This style of programming is lumped under the term dependency injection. The idea is that you, the programmer, never look up or make or seek or acquire what you need (your dependencies). Instead you “take in” what you need.
You force your caller to hand you your raw materials instead of hounding off and scavenging them yourself. Always be lazy!
So if you’re writing a Chocolate
, and you need a PeanutButter
, you do not do this:
// Here is an example of a class that is *not* using
// dependency injection. It is brittle and hard to test.
public class Chocolate {
private final PeanutButter peanutButter;
// Note that the constructor doesn't "take in" anything.
// Yet the peanutButter field still needs to be "filled".
// I wonder how that will happen?
public Chocolate() {
super();
// You've seen this kind of thing in your job before.
// There's the magic "look up the thing" method.
// My experience has been that LDAP is usually involved. 😃
this.peanutButter = lookupPeanutButter();
}
// Here is the magic "look up the thing" method.
private PeanutButter lookupPeanutButter() {
// Almost always there's LDAP or a singleton being used. 😞
final GroceryStore singleton = GroceryStore.instance();
// When this lookup breaks, was it because the GroceryStore
// couldn't be found? or the PeanutButter? You will see
// anti-patterns like this throughout enterprise Java
// programming.
return (PeanutButter) singleton.get("Peanut Butter");
}
}
In the class above, really what you need is a PeanutButter
. It doesn’t really matter where it came from, just that you “take it in”. So whether or not you’re ever going to use CDI or Spring or anything else, please write your class like this instead:
public class Chocolate {
private final PeanutButter peanutButter;
public Chocolate(final PeanutButter peanutButter) {
super();
this.peanutButter = peanutButter;
}
}
What’s nice is that your class has now punted the problem of how to acquire the PeanutButter
to the caller. Now the caller has to figure that out. Your Chocolate
class is much simpler and easier to deal with. As someone experiencing the glories of your world-changing code for the first time, I can understand it better and marvel more at your brilliance.
If, on top of this well-designed class, you now put @Inject
in the right place, then CDI’s magic can supply—inject—the PeanutButter
. That is, CDI becomes the caller:
public class Chocolate {
private final PeanutButter peanutButter;
// Let's use constructor parameter injection
@Inject
public Chocolate(final PeanutButter peanutButter) {
super();
this.peanutButter = peanutButter;
}
}
The Dependency Injection Mindset
Now kindly forget CDI for a moment (and Spring if you’re coming from that background).
What is really important here is the mindset.
That mindset is: in any class you’re writing, take in only what you need to get your job done. Pare it down more and more and more until you truly have the object you need. Always be lazy; make your caller do the work of finding your dependencies!
Try if you can to pass them in in the constructor of your class and set them in private final
instance fields. Immutability is good!
The more you relentlessly and recursively pursue this mindset, the simpler your classes will be and the more clear the dependencies between them will be.
The fact that some of them might be “injected” by some kind of magic machine like CDI is utterly immaterial to your class design. The dependency injection mindset is much more about the “dependency” part and much less about the “injection” part.
If there’s one thing you take away from this series of articles, let it be that programming using the dependency injection mindset—even if you never use a dependency injection framework like CDI (or Spring, or Jersey’s HK2)—is its own goodness.
Consumption
Within the dependency injection mindset, there are consumers and producers.
Consumers are like the Chocolate
class above: they “take in” stuff, do their work, and are done. A good consumer declares dependencies on exactly what it needs and doesn’t worry about how that stuff gets made or supplied or looked up or acquired. In CDI, using @Inject
at some location indicates that you’re doing consumer work there.
Consumption is the heart of the dependency injection mindset.
Production
Up to this point we’ve been focused on not worrying about where, for example, instances of PeanutButter
come from or who makes them or how many little subassemblies go into ultimately manufacturing one. All of these concerns are part of production.
For this article, a producer is something whose primary job is “handing out” an instance of something. Sometimes “handing out” means “creating”, and sometimes it means acquiring or looking up.
Sometimes a producer “knows” it should hand out the same thing over and over again. If you’ve programmed in Java for any length of time, you’ve run into someone somewhere who uses singletons and methods to hand them out. These methods that return singletons are doing production work—they’re producers.
If you squint right, and look at things a certain way, another kind of producer that hands out the same thing over and over again is a field! So looked at under the right lights, a field can be a producer.
Other times a producer “knows” it should hand out a new thing whenever called for. Again, if you’ve been in the Java enterprise world for a while you’ve seen various Factory
-suffixed classes. Typically they have methods that start with create
or make
or get
and they create or make something (often times a thing whose class is named the same thing as the factory class, minus the Factory
suffix) and return it whenever they’re invoked. These methods too are producers.
These kind of method-based producers obviously live “inside” classes (they’re methods, after all). The things they make may or may not be instances of the classes the producers live inside. For example, a PeanutButterFactory
class may contain an acquirePeanutButter
method that returns a PeanutButter
instance. Note that the class that houses the PeanutButter
producer—PeanutButterFactory
—in this case is not the same as the class of the return type of the method—PeanutButter
.
Just so we can talk about things later let’s call the “housing” of a producer (whether it’s a field or a method) a producer class. As we’ve seen above, in general—but with one notable exception described immediately below—the class of the thing being made (the field’s type, or the method’s return type) need not be the same as the producer class housing the producer (the field or the method) that makes the thing.
Finally, there are producers all over the place in plain sight that you may not think of. They create new things every time when called for. They’re called constructors! Constructors have an interesting property, which is that they must be housed in the class that is the class of the instances they make. So, for example, a PeanutButter
constructor makes PeanutButter
instances and can’t make anything else.
Another way to put this is that in the case of constructors, the producer class’ producer (the constructor) makes instances of the producer class itself.
So fields, methods and constructors are all producers if you look at them as sources of objects that someone might need.
Production and Consumption
Oftentimes, a method or constructor that is making or getting things to hand to a caller will need other raw materials to get the job done. Maybe, as in our examples earlier, a producer of PeanutButter
instances needs to get them from a GroceryStore
.
Following the dependency injection mindset, there’s nothing to prevent a producer from also being a consumer! That is, a method that for various business-related reasons returns or creates a PeanutButter
instance from a GroceryStory
shouldn’t look up a GroceryStore
instance, or set about some other means of creating a GroceryStore
, it should simply declare that it needs one.
Here’s what a PeanutButter
-returning producer (method) might look like when it’s designed from within the dependency injection mindset. Note that there’s no GroceryStore
acquisition going on (no singletons, no LDAP, no database servers, no configuration subsystem):
public PeanutButter acquirePeanutButter(final GroceryStore store) {
return store.get("Peanut butter");
}
Note that this method is a producer when we’re looking at it as a source of PeanutButter
instances, and a consumer when we’re looking at it as something that needs a GroceryStore
to do its job.
Wiring It Up
Suppose now we’ve written several classes using our dependency injection mindset.
So in our left hand we have consumers that need things to do their job. In our right hand we have producers that make things should they ever be needed by someone (and may also consume things as part of that production process).
We can tell just by surveying the landscape that that method over there that returns PeanutButter
instances—a PeanutButter
producer—”goes with” this consumer over here, Chocolate
, whose constructor needs PeanutButter
instances.
Again, just by looking at things, we can see that if we wanted to ever instantiate a Chocolate
for any reason, we’re first going to need a PeanutButter
.
To get a PeanutButter
, we’re going to have to call that PeanutButter
-returning method.
To call that PeanutButter
-returning method, we’re going to have to create an instance of the class it “lives” in, then…oops, we’re going to need a GroceryStore
because—remember? see the examples above—the PeanutButter
-returning method needs a GroceryStore
to do its job. So we’ll need to recursively go through this effort with GroceryStore
—maybe it in turn is produced by a producer, or maybe we can just call its constructor.
This process of figuring out these relationships and instantiating the right things in the right order in order to come up with other things is known generically and colloquially as wiring. You can do it by hand. It’s not magic.
Wiring By Hand
For example, at the initialization of our program somewhere we could do something like this (this example deliberately has a few problems):
final GroceryStore groceryStore = new GroceryStore();
groceryStore.put("Peanut butter", new AdamsChunky());
final PeanutButterFactory factory = new PeanutButterFactory();
final PeanutButter peanutButter = factory.acquirePeanutButter(groceryStore);
final Chocolate c = new Chocolate(peanutButter);
This is an example of (deliberately slightly bad, but not awful) wiring by hand, but it shows wiring nonetheless.
We’ve made many choices here. Some are obvious; some, when generalized into your enterprise project of choice beyond this stupid example—swap in your favorite system you love to hate in place of GroceryStore
, for example—are perhaps not so easy to see:
- We’ve chosen the kind of
GroceryStore
.
- We’ve chosen the kind of
PeanutButter
.
- We’ve explicitly said that our
PeanutButter
instance, regardless of what choice we made a line above, is going to come out of a GroceryStore
.
- We’ve also said that
PeanutButter
instances can be acquired from a factory method. (Hmm; can that method be subclassed? Are there now two “sources of truth” or more for PeanutButter
instances?)
- We’ve implied that
PeanutButter
instances are (effectively) singletons. Maybe we didn’t mean to do this. Maybe this matters; maybe not.
So we’ve deferred certain choices—a Chocolate
takes in a PeanutButter
, but isn’t choosy about what kind, or where it came from; a GroceryStore
presumably allows you to put
any kind of PeanutButter
you like; we’ve abstracted the production of PeanutButter
behind a black-box method (acquirePeanutButter
)—which is nice. But it is important to note that we’ve wired in many other hardcoded choices above.
Many enterprise projects will have something like this, and a well-meaning developer will say, ah, hardcoding is bad; let’s allow someone to configure the kind of PeanutButter
to use so it isn’t always AdamsChunky
. So they introduce a configuration file or mechanism that looks up the kind of PeanutButter
to use—and now we’re out of the dependency injection mindset. Oops!
That is: there will now be code in this initialization sequence that requires a certain configuration mechanism to look up the precise type of PeanutButter
required. Then someone will come along and try to abstract that configuration mechanism. These are instances of slightly buried service locator patterns applied intentionally and unintentionally in the initialization code—and then suddenly it turns out that for reasons no one is entirely sure of you have to have an LDAP server or database running in order to test a Chocolate
instance. Ugh! What happened to our “just declare the thing you need”?
And where does CDI come in?
Automatic Wiring
CDI is that well-meaning developer, and the author of the initialization code above, but way better: it does the wiring correctly, automatically and well without interfering with the dependency injection mindset. As you can probably see by now, that initialization code is the process of matching producers with consumers, and that is exactly what (this area of) CDI does. It has been doing this work correctly and well for many, many years.
Armed with this foundation (the dependency injection mindset, seeing the world in terms of producers and consumers and a middle-player that wires them together properly and efficiently), we can move on in the next post to how CDI does this.