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 Contextual
s. Contextual
s 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 Bean
s 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 Bean
s, stores them, and controls when they are destroyed by Bean
s.
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). Bean
s 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 Bean
s 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 Context
s. Sometimes people talk about scopes and Context
s as if they are the same thing. They are not, since a scope may be implemented by one or more Context
s. 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 Context
s 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 Context
s that also have that scope annotation type and thus collectively implement the corresponding scope (usually there’s just one). From the resulting set of Context
s, 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 Context
s and how they interact with Bean
s.
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 Bean
s 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 thatBean
can ultimately be supplied to aContext
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, theBean
in question is supplied, so the generated client proxy code has access to theBean
‘s information and can save it away (Bean
s are immutable). This means it can find the properContext
from which to acquire the right kind of contextual instance because it can ask theBean
for its scope annotation, and can then ask aBeanManager
for a corresponding activeContext
. - 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.
- This can work here because at the time that the
- 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 Bean
s 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 availableBeanManager
and pass inWheel.class
as theType
. Assuming she can also use thatBeanManager
to find aBean
that makesWheel
-typed contextual instances, she can pass thatBean
too to thegetReference
call, and she’ll get back aWheel
-typed contextual reference as we’ve discussed above, that she knows will be suitable for this usage point, regardless of what scope theWheel
may belong to - then call the ordinary constructor on the
Unicycle
class that takes aWheel
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.