A CDI Primer: Part 2

In the previous post we covered Contexts and Contextuals and the relationship between them.  A Contextual, briefly, is the CDI representation of a producer (a CDI-independent concept within the dependency injection mindset we covered in part 0) focused solely on the mechanics of production, with no responsibility for caching or storage or any other kind of lifecycle management.  A Context is CDI’s façade of sorts for Contextuals that has no responsibility for making objects of a given kind—it uses Contextuals for that—but all kinds of responsibility for managing the lifecycles of those manufactured or acquired objects.

We also learned that contextual instances are effectively the return values from the Context#get(Contextual, CreationalContext) method invoked on a given Context (and by extension the return values of implementations of the Contextual#create(CreationalContext) method.)  If your class has an @Inject-annotated “slot” in it, and it gets “filled” by CDI, then you just took delivery of a contextual instance from a Context, created by a Contextual.

In this post we’ll start looking at the actual mechanics that CDI uses to match contextual instances to consumer “slots”, and the beginnings of how those pieces of the machinery are found in the first place.

A Digression on Java Generics

And now, for everyone’s favorite subject: Java generics.

First, some housekeeping.  I’m going to run right into the utterly awful WordPress editor which is schizophrenic about angle brackets.  Java generics feature a lot of angle brackets.  They disappear in WordPress.  (Using ampersand-lt-semicolon and similar SGML-entity-based “solutions” doesn’t work, because the helpful editor will expand it in place, then save the result, neutering the attempt to use them in the first place.)  So I’ll use curly braces instead.  Just squint a lot and you might even be able to see them as angle brackets.

Suppose I have a field whose type is List{Number} and you have a List{Integer}.  Can I take your List{Integer} and put it into my List{Number}-typed field?  No, I cannot.  You can learn more about this from the Java Tutorial if you need to.  This is a case of trying to figure out assignability of parameterized types (like List{Number} and Class{T} and so on).  In order to do wiring properly, you—or the magic system that is going to do the wiring for you, like CDI—have to get this right.

Now, if I have a field whose type is List{? extends Number} and you have a List{Integer}, that will “go into” my field just fine.  So the person or machine doing wiring has to take wildcards into account as well.

Typesafe Resolution

This process of accounting for all sorts of different types—simple-typed slots like fields with types like Number or complicated slots with parameterized types like List{Number}—and matching those slots to compatible types, such as the return types of all producers in the system, is known as typesafe resolution.  “Typesafe” here is meant to emphasize the fact that—unlike some other dependency injection frameworks such as the machinery that was in Java EE at the time—CDI matches types, not names: your @Inject-annotated field’s type is matched to a producer’s type.  (Earlier dependency injection frameworks used name matches, which are more brittle.)  “Resolution” refers to the fact that a consumer’s slot has been matched, or resolved, to a particular production type—and thereby to a particular dependency.

Let’s say that CDI can somehow find all the slots in my classes that need contextual instances.  And it can find producers that make all different sorts of contextual instances of all different sorts of types.  If there is exactly one producer in that big pile of producers (of any kind—constructor, method, field…) whose production type is assignable to a given slot, then we have a match: we say that typesafe resolution has completed successfully, and we know that that wiring can be set up automatically.  If I have a slot that asks for a Gorp, and there is exactly one producer that returns a Gorp (or a Gorp subclass), then as we saw back in part 1 we can just “see” that that producer should be called to fill that slot.  CDI can therefore also “see” that this is the case, so is able to make it work.

Unsatisfied Dependencies

What happens when there is no producer whose production type is assignable to my @Inject-annotated field’s type?  We say in this case that typesafe resolution failed because we have an unsatisfied dependency.  I asked for a Gorp , but there wasn’t any Gorp producer that could respond.  The wiring cannot be completed automatically.

Ambiguous Dependencies

What happens when there are lots of producers whose production types are all assignable to my @Inject-annotated field’s type?  We say in this case that typesafe resolution failed because it was ambiguous.  The wiring cannot be completed automatically.  Without further information, CDI can’t make a call on which producer to select to produce an object to go in the right slot.

Finding the Right Context

Let’s say that I have a class named Backpack with a field whose type is Gorp, and there is exactly one producer in the world (represented as always in CDI by a Contextual under the covers) in some handwavy unspecified fashion that returns Gorp, and so CDI in the abstract is able to at least conceptually connect the two—i.e. typesafe resolution succeeds.

While talking about typesafe resolution, we’ve been talking about consumers and producers (Contextuals), but we’ve quietly tabled any mention of Contexts.  Let’s bring them back into the conversation.

Recall that a Context is responsible for fronting a producer (a Contextual), and for deciding when and for how long to cache its results, and that it is a Context implementation that ultimately supplies dependencies, calling upon the services of a Contextual implementation when required.

Graphically, our little case might look like this, using UML notation:

ProducerConsumerContextClassDiagram

Here, I’ve colored the CDI internal interfaces gray, our application classes (Backpack and Gorp) cyan, and then have highlighted in pink and red respectively a hypothetical Context implementation and a hypothetical Contextual implementation.  I’ve also used «stereotypes» to help with keeping the terminology straight.  Once again I’m going to have to use curly braces in my text below instead of angle brackets thanks to the WordPress editor’s many flaws.

You can see that in general a Context uses a variety of Contextual implementations to serve dependencies.

You can also see that in this case there is a Some Context Implementation? that fronts a Some Contextual{Gorp} Implementation? that is the ultimate producer of Gorp instances.

The Context implementation is going to have rules in it about when to return the same Gorp instance and when to create a new one.

The Contextual{Gorp} implementation is the producer and is going to be concerned with how to make a Gorp instance.

These classes are components in the same way that our Backpack and Gorp classes are components.  There can be many Context implementations in the system, and there can be many Contextual implementations in the system, all supplying different types, and all blissfully unaware of who might be asking for those types.  Further, a Contextual doesn’t know what Contexts might be using it, and a Context doesn’t know what Contextuals there are in the world.

So how does CDI figure out which Context implementation to pick to serve up Backpack‘s Gorp dependency?  And, even if it somehow magically does that, how does it then also figure out that the Context in question should use a particular Contextual to “back” it?

You can see, I hope, that there must be some kind of a mechanism available to let CDI know at the very least how a Contextual implementation might be linked with a Context implementation.  That hinting mechanism is a CDI construct called a scope.

Scopes

If a Context implementation is where contextual instances used by consumers come from, then a scope is a construct that indicates to CDI that this Contextual over here “goes with” that Context over there.

That is, in a very abstract sense—and we’ll get concrete soon enough—you mark a Contextual in some way that lets CDI see from that marking that the Contextual “belongs” to a certain Context implementation.

So if there is a Context in the world in some kind of handwavy unspecified fashion, and it can somehow identify itself to CDI as a producer of singletons (let’s say), and you have a Contextual implementation whose returned objects you want to be singletons, then you brand your Contextual in a particular way that identifies it as belonging to the singleton Context implementation.

The shape that this mechanism actually takes in CDI is very simple.  A scope is a particular kind of annotation class.  (Scopes happen to be any annotation classes that are themselves annotated with either javax.inject.Scope or javax.enterprise.context.NormalScope, but for this discussion we don’t really care about that for the moment.)

Let’s keep rolling with our singleton Gorp case.

When we introduced Contexts, we focused exclusively on the get methods.  But there is another interesting method on Context.  It is getScope.

That method returns an annotation class that basically ends up labelling the Context implementation.  The Context can now be matched or looked up under this annotation class label.

So, for example, CDI happens to ship with a built-in Context whose getScope method returns javax.inject.Singleton.class.

On the “other side”, it turns out you can mark certain Contextual implementations with that same annotation.  We’ll see exactly what shape this takes a little later; for now just know you can do it.

So if our Contextual{Gorp} implementation is somehow annotated with javax.inject.Singleton, then CDI now has what it needs: it can find the Context implementation indexed under Singleton.class, and “link” it with the Contextual{Gorp} implementation annotated with that annotation.

Then, assuming that typesafe resolution has succeeded, as it would in our trivial example, CDI now knows that it can complete the wiring automatically: our Backpack class will make an implicit request of CDI’s built-in-Context-implementation-associated-with-Singleton.class, and that Context implementation will use the Contextual{Gorp} that was annotated in some handwavy way with @Singleton.  The circuit is complete.

Our Backpack can now take delivery of a Gorp and anyone else who asks for a Gorp will get back the very same Gorp instance, not a new one.

Enough Handwaving

We’ve done a lot of handwaving to get to this point.

  • We’ve handwaved over what Contextual implementations can look like.
  • We’ve handwaved over how CDI figures out what producers and consumers exist in its world.
  • We’ve handwaved over how CDI figures out what Contexts exist in its world.
  • We’ve handwaved over exactly when CDI performs all this discovery.

We’ve probably also handwaved over a lot of other stuff.

At least some of the handwaving will start to end in the next post!

Advertisements

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.

2 thoughts on “A CDI Primer: Part 2”

Comments are closed.