CreationalContext Deep Dive

What is a CreationalContext in CDI?

A perfect example of how naming things is the hardest problem in software engineering, really. The only method that it exposes that anyone should really be concerned with, release(), is used at destruction time, and has nothing to do with creating anything.

Here’s how I would describe it:

A CreationalContext is a bean helper that automatically stores @Dependent-scoped contextual instances on behalf of some other bean that has references to them, and ensures that they are cleaned up when that bean goes out of scope.

That’s basically it.

Consider a bean, B, that has a reference to a @Dependent-scoped bean, D. In Java terms, it might have a field like this:

@Inject
private D d;

Now, if the Bean implementation that created this B contextual instance has its destroy(T, CreationalContext<T>) method called, then B will be destroyed. B, in other words, will be the first argument.

When B is destroyed, you want to make sure that its dependent objects are also destroyed. But you also don’t want to burden the programmer with this. That is, you don’t want to make the programmer have to jump through some hoops inside B‘s logic somewhere to say “oh, when I’m destroyed, make sure to arrange for destruction to happen on my d field”. That should just happen automatically.

To allow this to happen automatically, CDI locates this logic inside a CreationalContext (yes, a “creational” context, even though we’re doing destruction. Naming is hard.). At the moment a CreationalContext is released, it has “in” it:

  • A bean that it is helping (usually)
  • A set of dependent instances that belong to the bean that it is helping
  • For each of those dependent instances some way to tie it to the Contextual (the Bean) that created it

When release() is called, the CreationalContext iterates over that set of dependent instances and their associated Beans and calls destroy() on each of those Beans.

So the programmer didn’t have to do any of this work. She just @Injected a D into a field and even if D is some sort of custom object it gets destroyed properly.

(It all follows from this that release() implementations must be idempotent and pretty much should never be called except from within a destroy() method of a custom bean. They also must work on only the bean that is being helped, and not on every dependent object “known” to the CreationalContext.)

OK, that’s all fine, but how did all these objects get “into” the CreationalContext in the first place?

When B was created, via a Bean‘s create(CreationalContext<T>) method, the container supplied that method with a new, empty CreationalContext that is associated with the Bean doing the creating. That is, prior to the create call, the container called beanManager.createCreationalContext(beanThatIsCreatingBInstances), and the resulting CreationalContext is supplied to beanThatIsCreatingBInstances‘s create method as its sole argument.

What does a custom Bean author here need to do with this CreationalContext as she implements the create method? The answer is: ignore it completely. That’s easy.

(More to the point: push(Object) does not, as you might be tempted to believe, stuff a @Dependent-scoped object into the CreationalContext such that release() will have any effect on it. The two methods are completely orthogonal. Around this point you should start getting suspicious: how does a dependent object get “into” an arbitrary CreationalContext anyway? An excellent question.)

In the case of managed beans—ordinary CDI beans, with @Inject annotations and whatnot processed by the container without any special funny business—remember that the container will take care of satisfying the injection points. So in the case of B with a D-typed d field injection point, the container will arrange for a D-type-producing bean to be invoked and then will automatically arrange for that dependent object to be stuffed into the CreationalContext.

That’s a lot to take in. Let’s try to break it down.

Recall that the container created a brand new CreationalContext to serve as the bean helper for B when it is about to call B‘s create method.

In order to “make” a B, the container is going to have to satisfy its D-typed injection point (the d field in our example).

To satisfy the D-typed injection point, the container will need to find out what scope is in effect. It will discover that the scope is @Dependent (since our example says so; presumably D is annotated with @Dependent which allows the container to call BeanManager#getContext(Class<? extends Annotation>)).

With the right Context in hand, the container will ask it for an appropriate D instance. The method that the container uses here is Context#get(Contextual<T>, CreationalContext<T>). Here, the container does not create a new CreationalContext. It passes the CreationalContext it has made for creating the B instance. That’s important.

A Context is responsible for doing basically whatever it wants to return an instance, so long as if a new instance is required it results (ultimately) from the return value of Contextual#create(CreationalContext<T>).

The @Dependent-scoped Context is obliged to create a new instance with every request, so it will dutifully invoke Contextual#create(CreationalContext<T>) and get back a new D instance. (If we pretend for just a moment that D is made by some kind of custom bean, the custom bean author never had to touch the CreationalContext when she implemented the create method. She probably just returned new D() or something similar.)

OK, so now the Context has possession of a new D object. But before it hands it back to the caller, it is going to stuff it in the supplied CreationalContext as a dependent instance. After all, the @Dependent-scoped Context always produces dependent objects that are tied to some “higher-order” bean, and we know that this is what CreationalContext instances are for: to store such dependent objects together with their referencing bean.

So it’s fine to say all this, but how does the @Dependent-scoped Context implementation actually add a dependent object to the CreationalContext? We’ve already seen the push(Object) method is not for this purpose.

It does it via proprietary means.

Weld, for example, does it via casting. This can get interesting since a user can supply her own @Dependent-scoped Context implementation: in order to add dependent objects herself she must tie her Context implementation to Weld.

You might think you could have your own @Dependent-scoped Context implementation that arranges for a CreationalContext to be used as a key into a Map of this kind of state. Then you wouldn’t be bound to a particular implementation of CDI. But of course if someone calls release() on a CreationalContext, you would have to somehow arrange to be notified of such a call, and that’s impossible.

So the upshot is that the CDI vendor, who returns CreationalContext implementations from BeanManager#createCreationalContext(Contextual<T>), is the only one who can supply any @Dependent-scoped Context implementations, no matter what the specification says.

Returning back to what the end user should “do” with a CreationalContext: the answer is basically ignore it. If you are writing a custom Bean implementation, then as the last operation in your delete implementation you can do this:

if (cc != null) {
  cc.release();
}

Otherwise, just leave the thing alone.

Advertisement

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.

%d bloggers like this: