CDI Production

Dependency injection involves making things. Here’s a condensed version of how things are made in CDI. You may also be interested in my larger CDI tutorial.

This is a living document that I intend to update frequently. I may break it up into separate posts later.

We’re going to start from the bottom and inside, and work our way up and out. That means we’ll look at the low-level, internal machinery of CDI first, which most users rarely see. Then we will work up and out to annotated classes, from which the low-level constructs are derived, and which are what users create and manipulate.

Finally, terms in CDI can refer to each other, so it is definitely a challenge to present things in order. There will be times where I will ask you to take things on faith, and then will circle back to address them in more detail once the terms I need to talk about them are more firmly in place.

OK, off we go.

Contextuals

At the bottom of CDI you have Contextuals. Contextuals are stateless factories that create and destroy contextual instances. Think of a contextual instance, for now, as a regular Java object, although they can be more complicated than that.

One kind of Contextual is a Bean. A Bean is a Contextual paired with some descriptive and lifecycle-related attributes. We’ll assume that Beans are the only kind of Contextual in the world, so we’ll just talk about them instead.

Beans and Contextual Instances

The lifecycle of any contextual instances that a Bean creates is not under its control. It is instead reified by a Context that the Bean is indirectly affiliated with. A Context doesn’t create or destroy contextual instances, but it controls when they are created by Beans, stores them, and controls when they are destroyed by Beans.

When a Context needs a new instance—because, for example, it has been asked for one and doesn’t have one already stored—it always asks an affiliated, appropriate Bean to create one. This is why an instance created by a Bean and stored by a Context is therefore called a contextual instance. We will discuss why a Context might be asked for a contextual instance, and by whom, soon.

Even though we can’t really fully talk about them, there are some other characteristics of contextual instances that are worth mentioning up front, just to locate them in architectural space. They’re worth mentioning now because it is a Bean‘s create method that is ultimately responsible for them, not some other piece of CDI machinery. Don’t worry if you don’t understand these characteristics yet.

First, a contextual instance may be intercepted or decorated. Because of this, it may be a proxy object—proxying is a common strategy for implementing interception and decoration—or it may not. A Bean does not have to implement decoration or interception at all, but if they are to be implemented, they must be implemented, ultimately, by a Bean‘s create method, in whatever way it chooses. A Bean does not necessarily have to use proxying to implement these features. Don’t worry that we haven’t discussed interception, decoration or what it means for something to be a proxy object yet.

Second, under no circumstances will a contextual instance be a client proxy, a very specific CDI term that we will also cover later (that is sadly unrelated to the possible proxying discussed above). Beans never create client proxies. This will be relevant when we discuss dependency injection in more detail. For now, it is just an interesting fact using a term that has yet to be defined.

Third, a contextual instance is often an instance of a type that depends on other types. A Unicycle interface might depend on a Wheel class, for example: when a new Unicycle implementation of some kind is created (by a Bean), it may need to be supplied with a Wheel object. Acquiring and supplying these objects is, loosely speaking, dependency injection. We can’t fully talk about dependency injection yet, but for now just know that it is a Bean‘s create method’s job to perform it.

So much for Beans and the basics of who calls them and what they do.

Contexts

Rising up and out a level, low-level machinery within CDI can ask a Context for a contextual instance of something. It supplies a Context with a relevant Bean, and asks for an appropriate contextual instance that the Context manages on behalf of that Bean. The Context, in turn, may discover that it does not have an appropriate contextual instance yet, and so might turn around and ask the supplied Bean to create one. A Context never creates contextual instances itself.

A Context can be active or not with respect to a given Thread. There can be only one Context active for a Thread at a time and its activeness can change at any point for any reason.

Scopes

A Context is the partial or full implementation of a notional lifecycle represented by the concept of a scope. This means a Context stores a contextual instance created by a Bean for some period of time, for a given Thread. Or, perhaps it never stores anything but just arranges for contextual instances to be created every time it is asked for them. That is legal too, and in fact CDI depends on the existence of such a Context.

If a Context stores contextual instances, it typically (but, I suppose, not necessarily) does so by affiliating them in storage with the Bean that created them. This means that a Bean is both a mechanism for creating a contextual instance, and, often, a key to identify that contextual instance later.

As noted above, a Context implements part, or all, of a scope. A scope is a concept representing a notional lifecycle and is associated with one or more Contexts. Sometimes people talk about scopes and Contexts as if they are the same thing. They are not, since a scope may be implemented by one or more Contexts. Finally, a scope is notionally identified by its scope type, which is another notion that exists only to identify its scope. A scope may be identified by only one scope type and a scope type identifies only one scope.

A scope type is reified by a Java annotation type that itself must be annotated with either jakarta.inject.Scope or jakarta.enterprise.context.NormalScope. A scope is said to be a normal scope if its reifying annotation type is annotated with jakarta.enterprise.context.NormalScope, and a pseudo scope otherwise. (There are ways in CDI to programmatically register other kinds of arbitrary annotation types as scopes, but for now we’ll ignore those.) We will talk about scopes—particularly normal scopes—more fully in a little bit.

A Context has a method that can retrieve its associated scope type.

Linking Scopes, Contexts, Beans and Contextual Instances

Low-level machinery within CDI often finds itself armed with just a Bean and the need to get a contextual instance that a given Bean can create. To do this, it does not ask the Bean to create such an instance directly. If it did, it would bypass the useful lifecycle management machinery that Contexts implement.

Instead, it asks the Bean for its scope type—something every Bean has as part of its descriptive and lifecycle-related attributes—and then asks other low-level CDI machinery for all the Contexts that also have that scope annotation type and thus collectively implement the corresponding scope (usually there’s just one). From the resulting set of Contexts, it gets the one that is active for the current Thread. Then it passes the Bean to the Context and asks for a corresponding contextual instance. The contextual instance that is returned may have been a cached one or a new one; the caller does not know.

So much for the basics of Contexts and how they interact with Beans.

Normal and Pseudo Scopes

Rising up and out another level, recall that a scope may be a normal scope or a pseudo scope. A normal scope is, loosely speaking, one whose contract guarantees the user that any objects she sees whose types logically belong to that scope will be suitable no matter where they are used.

For example, consider a normal scope representing a request-oriented lifecycle, and a caller who takes delivery of an object whose type logically belongs to that request scope. Wherever in her system that object shows up, regardless of what she does with it, it should be the right object, i.e. the one for the current request, or should be invalid in some way if there is no current request, and so on. So she can’t just take delivery of an ordinary contextual instance, because an object can’t replace itself with another in some magic way.

To help with this, if a Context indicates that it implements a normal scope (remember, a Context can supply its scope annotation type, as can the Beans that make the contextual instances it manages), then a CDI implementation must do some prescribed things to ensure the normal scope contract is honored.

First, from the user’s perspective, if she receives a Unicycle object from as-yet-unspecified machinery that plays by the normal scope contract rules, that Unicycle object must behave like a “real” Unicycle object. Methods that she can invoke on a “real” Unicycle object must work as expected. instanceof tests must succeed. And so on.

Second, when she invokes a method on her received Unicycle object, it must, because of normal scope rules, somehow just-in-time find the “real” underlying Unicycle that is appropriate for the usage. In CDI, the “real” underlying Unicycle in this usage scenario will always be a contextual instance definitionally managed by a Context partially implementing the normal scope in question. We have seen how a Context supplies contextual instances, so we can see a glimmer of a way that this might be implemented generally. Then the method in question that the user invoked must arrange for this “real” Unicycle contextual instance that was found to receive the same method invocation in such a way that the user is none the wiser.

This is a classic example of proxying. A proxy in this general sense is a wrapper that (a) behaves as if it were a “real” object itself but actually (b) forwards all calls it receives to the “real” object it notionally wraps. Typically embedded within its innards is a strategy for finding the “real” object just-in-time (or perhaps it requires the real object to be supplied to it initially) and re-invoking on the “real” object whatever method the user invoked on it. Here, our user has taken delivery of a proxy that behaves just like a Unicycle but looks up the “real” Unicycle contextual instance just-in-time under the covers when a method is called on the proxy.

Client Proxies and Contextual References

A proxy object like this that implements these rules of CDI’s normal scopes is a very specific kind of proxy. It is known in CDI as a client proxy. A user who takes delivery of objects from upper-level CDI machinery receives client proxies when the lower-level machinery recognizes the Bean in play is affiliated with a normal scope. (There are some other edge cases where client proxies might also be involved, but we won’t cover them here.)

A client proxy always wraps, or delegates to, a contextual instance served up (definitionally) by a Context. A client proxy never wraps, or delegates to, another client proxy. As we’ve seen earlier, a Bean never creates a client proxy. We haven’t yet talked about what does create a client proxy, where exactly they come from, how long they live, how you might use one or detect that you are using one, or any of that, but we will soon.

A client proxy is one kind of two kinds of contextual reference. A contextual reference is either a client proxy or a contextual instance stored and supplied by a Context implementing a pseudo scope. Ordinary users doing ordinary things always interact with contextual references and ideally don’t know whether they are client proxies or contextual instances.

A caller can ask a BeanManager for a contextual reference. The getReference(Bean,Type,CreationalContext) method accepts:

  • a Bean (so that Bean can ultimately be supplied to a Context when contextual instances need to be retrieved)
  • a Type representing how that contextual reference will be used and a type that the contextual reference must implement
  • a CreationalContext which is not relevant for our purposes yet

(Additionally, the Type needs to designate a kind of contextual instance the Bean in question can create.)

The BeanManager will ensure that the right kind of contextual reference—properly implementing at least the supplied Type—will be returned. If the scope governing the lifecycle of the kind of reference to be returned is a normal scope, then the contextual reference that is returned will be a client proxy (that knows how to get the right kind of contextual instance when needed, and that forwards method calls to it transparently). If instead the scope is a pseudo scope, then the contextual reference that is returned will be a contextual instance. (Recall from earlier that the contextual instance may still be another kind of proxy, e.g. to handle interception and decoration, but it will never be a client proxy.)

Client Proxy Creation and Rules

When the contextual reference that is returned is a client proxy, CDI does not dictate how a CDI implementation must implement it, but does dictate precisely how such a client proxy must behave. It also imposes some restrictions on proxiable types to make it easier for CDI implementations to build it.

First, any Type that the client proxy must implement can’t denote a final class. This makes sense because one common way of implementing a proxy for a type is to extend the type in question and override its methods to find the “real” object and dispatch the relevant method invocation to it. Obviously if the methods of a given class are final, a CDI implementation cannot build a client proxy that overrides them.

Second, any Type that the client proxy must implement that is a Class (as opposed to an interface) must have a zero-argument constructor. This is so that a CDI implementation can actually create an instance of the class to be proxied, so that it can return it to the caller, and so that it is at least possible to do using something as simple as Class::getDeclaredConstructor. Remember, the client proxy itself is not a contextual instance. Rather, it is an object that wraps the machinery to find one and to delegate method invocations to it. It is not therefore managed by a Context. It is created “by hand” by the CDI implementation when needed. The constructor is allowed to be public, protected or package-level.

Some CDI implementations use proprietary methods to avoid even this restriction, but that behavior is not portable.

The most common strategy employed by CDI implementations to create client proxy classes is to use just-in-time bytecode generation. With this strategy, a CDI implementation typically has a repeatable algorithm to generate a proxy class name given the name of a “real” class and Bean information. If that generated class is not already found, a bytecode generation library such as ASM is used to generate a class with that name that extends the “real” class (or that implements the “real” interface) and plays by very specific rules.

Once the client proxy class is generated, it is instantiated in whatever way the CDI implementation wants to instantiate it. After all, it generated the code! Weld chooses to do this in a very flexible manner that I’ve written about previously. (The simplest way for a CDI implementation to do this is of course to simply invoke a zero-argument, non-private constructor reflectively, as noted above.)

Once this client proxy extending or implementing at least this particular Type is created, it is basically reusable, so most implementations choose to stash it away somewhere so that all this code generation and instantiation doesn’t have to happen again.

The client proxy so created now has to obey the rules that govern all client proxies. Specifically, it must, for every business method invoked on it:

  • Get the relevant contextual instance (the “real” object). We’ve seen how that works in general.
    • This can work here because at the time that the BeanManager::getReference call is made, and the client proxy class is generated, the Bean in question is supplied, so the generated client proxy code has access to the Bean‘s information and can save it away (Beans are immutable). This means it can find the proper Context from which to acquire the right kind of contextual instance because it can ask the Bean for its scope annotation, and can then ask a BeanManager for a corresponding active Context.
    • We also know that it is the responsibility of a Bean to perform dependency injection, interception and decoration (if applicable), though we still haven’t delved into those topics yet. So when the contextual instance is “loaded” by the innards of the client proxy, it is fully ready for business.
  • Invoke the method in question on the contextual instance. There’s no further processing that happens.

You can see from these (very simple) rules that any logic related to interception, decoration and dependency injection must be located in the Bean‘s create method, as noted several times above. That is, client proxies are quite straightforward and just dispatch method calls. They doesn’t do any interception or further proxying themselves.

So much for contextual references and one of their subtypes (client proxies). The main takeaway is: If a user calls BeanManager::getReference, all the hard stuff is handled for her.

Acquiring Contextual References in Beans

Let’s circle back all the way down to the lowest level again and look, again, at the lowly Bean and, this time, at its create method. We’ll do this because, recall, dependency injection must be performed (if it is to be performed at all) in this method, and to “do” dependency injection in CDI a Bean will need to acquire contextual references (the dependencies to inject), which we’ve just learned about.

As we’ve seen, the Bean::create method can do whatever it wants to make a contextual instance. We’ve also seen that there is a method on BeanManager that allows the acquisition of contextual references for a given Bean and Type.

It doesn’t matter who is authoring a Bean implementation. It could be an ordinary user, who is using CDI’s portable extension facilities to install that Bean into the CDI implementation, or it could be the CDI implementation itself, when everything starts up and it inspects annotated classes for relevant annotations and creates Bean objects to represent them. All Beans in the system use their create methods to create contextual instances.

Usually a Bean can get access to a BeanManager from within its create method. For example, when the CDI implementation itself is creating a Bean object, often it installs a BeanManager into its Bean implementation. Or in a custom Bean put together in a portable extension, often a BeanManager is available as a “reachable” object in the enclosing portable extension method. Regardless, what is important is that it is relatively trivial for a Bean to get its hands on a BeanManager inside its create method.

Let’s consider a Bean implementation that a CDI implementation builds, i.e. not a custom Bean installed via an end-user portable extension, but one built as part of the CDI implementation itself to represent an annotated class. Let’s say the Bean in question is the one that was creating Unicycle instances in our earlier example.

With just the tools covered above, even if the Unicycle class has a constructor that takes a Wheel object, you can see—perhaps faintly—that using the BeanManager the CDI implementation vendor employee implementing this Bean create method can:

  • first call BeanManager::getReference on the available BeanManager and pass in Wheel.class as the Type. Assuming she can also use that BeanManager to find a Bean that makes Wheel-typed contextual instances, she can pass that Bean too to the getReference call, and she’ll get back a Wheel-typed contextual reference as we’ve discussed above, that she knows will be suitable for this usage point, regardless of what scope the Wheel may belong to
  • then call the ordinary constructor on the Unicycle class that takes a Wheel object and supply it with the contextual reference she just acquired
  • then return the plain simple Unicycle object as-is (assuming, for simplicity, that interception and decoration are no applicable here).

Note that the only slightly odd thing this user has to do is acquire a contextual reference to a Wheel so that she can then stuff it in the Unicycle constructor. She didn’t have to generate any bytecode or do anything magic with proxies or any of that. If some other user now asks a BeanManager for a contextual reference implementing the Unicycle type, they’ll get a contextual reference to this Unicycle contextual instance. If the scope in question for the Bean in question is a normal scope, then automatically the contextual reference they receive will be a client proxy.

More to come.

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.