In the previous article, we dabbled briefly in looking at the surface of the iceberg that is the CDI machine for making @Inject
work as a way of answering some basic questions about what, exactly, CDI does and why you might want to use it.
We also, much more importantly, looked into the dependency injection mindset. We talked about abstract notions of producers and consumers and wiring them together. We introduced the idea that if you look through the right lenses, fields, methods and constructors can all be producers housed in particular producer classes. And finally we pointed out that producers can be consumers and vice versa.
Now let’s talk about lifecycles.
Object Lifecycles and “Producer Proxies”
When a producer of any kind “wants” to make or supply or acquire an object, two decisions have to be made:
- how to make or supply or acquire the object
- when to make or supply or acquire the object regardless of how that happens
Some producers, as we saw in the previous article, are, as often found in the trenches of enterprise Java development, what I’ll call singleton suppliers. They acquire the (mostly!) One True Instance™ of the object in question somehow, sometimes creating it once if necessary, sometimes by interrogating some ancient snarling hairball of a legacy system, and then from that time forward in the whole life of the application, that’s the object you get if you ask them to supply it to you.
Other producers, like constructors, create a new object each time one is to be produced because they can’t do anything else, or they’re kind of uninterested in the ways that they might be called, so they shrug and say, hey, if you want a new object, call me; otherwise, don’t; I don’t store or cache nothin’.
What’s interesting about producers of any kind, in isolation, without any other technologies such as CDI or its subsystems in play, is: producers in isolation combine how an object is produced with when it is produced.
For example, what I’ll call singleton suppliers—those all-too-common “enterprisey” methods that somehow acquire a Singleton From Elsewhere™ and then return it when asked—are combining the mechanics of how the singleton is produced (maybe it’s looked up from some other system) with when it is produced (maybe this system lookup only happens once and then the result is stored as a static
singleton).
Or constructors: if you acquire an object from a constructor—a kind of producer, remember—then no matter what you do and no matter what it does you’ll get a new instance of the produced object each time because that’s what constructors do.
What would be nice is to let producers do what they do—acquire or make things when called for—and have there be some other subsystem that controls when a produced object is handed to a consumer. Something that can go “back to the well”—the producer “well”—when needed, but not when not needed, and is in full control of when that happens, but does not itself know how the manufactured items it is storing or caching are made. Something that is kind of like a proxy for producers: something that can stand in front of them and hand out their results when appropriate, regardless of how they were made or from what system they were acquired, according to its own notions of lifecycle.
In such a situation a producer can focus on how to acquire an object but not on how to cache it; a producer proxy can focus on when to cache an object, if at all, and when to clear the cache; a consumer, in grand dependency injection mindset fashion, can remain blissfully ignorant of both of these things.
Also in grand dependency injection mindset fashion, we want this producer proxy (my term, incidentally, not CDI’s) to not look up or acquire or otherwise scrounge around for its relevant producer(s). We want it to simply declare somehow that it needs one, and then punt the problem of getting one to its caller (we’ll talk about who that might be in a moment).
CDI implements this producer proxy concept with a construct called a Context
and it is the first CDI construct we’ll look at in depth.
Context
s…
The first thing to know about a Context
is that as a CDI end user you’ll interact with it many, many times—and as a consumer you’ll never see it or know that’s what you’re doing.
A Context
, in other words, is part of the internal CDI plumbing by which automatic wiring between producers and consumers is implemented.
A Context
is the nexus where a consumer needing an object of a certain kind is wired in an abstract fashion to a producer that is capable of producing objects of that kind, and where the lifecycle of such a produced object is managed.
Inside the depths of CDI, when you mark a field or a parameter with @Inject
, CDI asks a particular Context
for the kind of object you want. That Context
, in turn, ends up asking a producer, when necessary, to make or acquire the kind of object that should go in your @Inject
-annotated slot.
From the standpoint of a Context
, a producer is represented by something kind of odd called a Contextual
. A Contextual
is simply a producer that can also destroy the things it makes. A Contextual
can make any number of different things of a given type (so, for example, a Contextual
whose type parameter is Object
could make Gorp
, Chocolate
, PeanutButter
or whatever). Most of the time, though, a given Contextual
makes one kind of thing.
Finally, a Contextual
should not have, as its core concern, or any concern if at all possible, how long an object should live—it is a “pure” producer: it just makes ’em, ma’am, it doesn’t hang onto ’em.
Here is what the contents of the Contextual
interface look like, in their entirety, and for now we can ignore the second of these two methods:
public T create(CreationalContext<T> creationalContext); | |
public void destroy(T instance, CreationalContext<T> creationalContext); |
I hope you can see how simple that is.
Contextual
is another one of those interfaces that is in the internals of CDI. You rarely, if ever, implement it directly. But you could. And you do in some cases, usually indirectly, as we’ll see much later.
For example, ignoring CreationalContext
—a subject for a later post—you could see that you might implement the create
method in such a way that it wraps a constructor invocation (you’d be implementing a producer that is constructor-based). Or you could see that you could implement the create
method in such a way that it wraps a method invocation (you’d be implementing a producer that is method-based), though consuming other dependencies in this case might be a little trickier. Or a field access. And in many cases your destroy
implementation might not have to do anything.
The takeaway here is that you can represent all producers as Contextual
s of a particular kind.
Context
, then, uses Contextual
s to produce the objects it will then manage the lifecycle of. Here are the (relevant at the moment) contents of this interface, and trust me when I tell you that for now we can ignore the second method:
public <T> T get(Contextual<T> contextual, CreationalContext<T> creationalContext); | |
public <T> T get(Contextual<T> contextual); |
Again, very simple.
The first method is the real workhorse. It takes in a Contextual
and a CreationalContext
, as you can see. Once again, we’ll ignore the CreationalContext
. (The second method is for certain cases where CDI wants to ensure that no creation at all happens: it just wants the Context
to supply a cached instance if one exists; it doesn’t want the Context
to make a new one. We’ll ignore it for the sake of mental clarity.)
The first method’s contract is to acquire and return a T
. You’ll note that Context
itself is not a parameterized type (it doesn’t have any angle brackets or type parameters in its name). So you can see here that a Context
can get
a whole variety of different kinds of objects.
You can also probably squint and see that if I tell you (as I have) that a Context
‘s responsibility is to manage the lifecycle of objects, not actually make them, then when it decides that it needs a new one it can (and should) just call the create
method on the Contextual
that has just been handed to it. The Context
knows “when”; the Contextual
knows “how”.
If it already has an existing instance and has determined that this incoming demand can be satisfied by the existing instance, well, then it can just hand it back.
That’s all very convenient. So a Context
as a consumer remains blissfully ignorant of what kind of Contextual
(producer) has been handed to it, and can use this raw material if it wants in order to fulfill its contract of supplying T
instances. And the Contextual
can remain blissfully ignorant of lifecycle and caching concerns and can just return Its Thing™, whatever that might be, when asked to make it.
…and Dependency Injection
CDI stands for Contexts and Dependency Injection. And now with the introduction of Context
s we have an incomplete and blurry view—but a view nonetheless—of how the major parts work together, and why “Contexts” is important enough to be listed in the title of the specification:
- A consumer of some kind (your business-critical class) gets wired in some currently opaque-to-us way (involving
@Inject
) to aContext
implementation that supplies it with the dependency it needs (via theContext#get(Contextual, CreationalContext)
method). - The
Context
implementation gets wired in some opaque way to aContextual
implementation that knows how, but not necessarily when, to make those kinds of things. - A
Contextual
is a kind of producer but everybody but theContextual
implementation itself doesn’t know what kind.
The result is that CDI is now conceptually capable of injecting dependencies, taking into account desired lifecycles, and letting producers and consumers and producer proxies all focus on what they do best, and on nothing else.
Contextual Instances
I’ve spoken very loosely about producers making things of a particular kind. And I’ve spoken equally loosely about consumers needing things of a particular kind. And I’ve spoken just as loosely about the wiring process where a producer of a thing of a particular kind gets wired in some fashion to a consumer that needs those things, mediated by a producer proxy—a neutral term I invented to describe the lifecycle/lifespan-managing component that decouples when something is handed out from how it is made or acquired.
Now we’ve also learned that producers in CDI are represented by Contextual
s and are effectively “fronted” by Context
s, and that producer proxies are represented by Context
s, and that Context
s therefore are the sources of instances that producers—Contextual
s—make. Consequently when a Context
‘s get
method hands you (indirectly) an object, that object is known throughout CDI’s literature as a contextual instance. It is an instance of something that a given Context
manages.
Still speaking loosely, when you ask for a Gorp
to be supplied to you (injected) by CDI by using the @Inject
annotation, you’re going to get a contextual instance of Gorp
in the annotated “slot”.
Or, equivalently: if an object “comes out of” a Context
, then it is a contextual instance, and its existence must be owed to the fact that CDI invoked get
on a Context
and the Context
ultimately invoked the create
method of a Contextual
that produced it. CDI “knows about” all contextual instances. CDI does not know about (for the most part) objects that are not contextual instances.
In the next post, we’ll look at the mechanics of how CDI wires Contextual
implementations that can make certain contextual instances of particular kinds to Context
implementations that implement a lifecycle, and how CDI wires consumers of contextual instances to Context
instances that can provide them.
3 thoughts on “A CDI Primer: Part 1”
Comments are closed.