A CDI Primer: Part 1

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:

  1. how to make or supply or acquire the object
  2. 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.

ProducerConsumerUseCase

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.

Contexts…

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:

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 Contextuals of a particular kind.

Context, then, uses Contextuals 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:

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 Contexts 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 a Context implementation that supplies it with the dependency it needs (via the Context#get(Contextual, CreationalContext) method).
  • The Context implementation gets wired in some opaque way to a Contextual implementation that knows how, but not necessarily when, to make those kinds of things.
  • A Contextual is a kind of producer but everybody but the Contextual 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 Contextuals and are effectively “fronted” by Contexts, and that producer proxies are represented by Contexts, and that Contexts therefore are the sources of instances that producers—Contextuals—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.

Author: Laird Nelson

Devoted husband and father; working on Helidon at the intersection of Java, Jakarta EE, architecture, Kubernetes and microservices at Oracle; open source guy; Hammond B3 player and Bainbridge Islander.

3 thoughts on “A CDI Primer: Part 1”

Comments are closed.