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.
At the bottom of CDI you have
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 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
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
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
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
create method’s job to perform it.
So much for
Beans and the basics of who calls them and what they do.
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
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.
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.
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 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.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.
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
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
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:
Bean (so that
Bean can ultimately be supplied to a
Context when contextual instances need to be retrieved)
Type representing how that contextual reference will be used and a type that the contextual reference must implement
CreationalContext which is not relevant for our purposes yet
Type needs to designate a kind of contextual instance the
Bean in question can create.)
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.
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.
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
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
- 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
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
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.
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
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
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.