There are several web resources out there that describe how to get a CDI bean (usually ApplicationScoped
) to be instantiated when the CDI container comes up. Here’s an arbitrarily selected one from Dan Allen:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@ApplicationScoped | |
@Startup | |
public class StartupBean | |
{ | |
@PostConstruct | |
public void onStartup() | |
{ | |
System.out.println("Application starting up."); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class StartupBeanExtension implements Extension | |
{ | |
private final Set<Bean<?>> startupBeans = new LinkedHashSet<Bean<?>>(); | |
<X> void processBean(@Observes ProcessBean<X> event) | |
{ | |
if (event.getAnnotated().isAnnotationPresent(Startup.class) && | |
event.getAnnotated().isAnnotationPresent(ApplicationScoped.class)) | |
{ | |
startupBeans.add(event.getBean()); | |
} | |
} | |
void afterDeploymentValidation(@Observes AfterDeploymentValidation event, BeanManager manager) | |
{ | |
for (Bean<?> bean : startupBeans) | |
{ | |
// the call to toString() is a cheat to force the bean to be initialized | |
manager.getReference(bean, bean.getBeanClass(), manager.createCreationalContext(bean)).toString(); | |
} | |
} | |
} |
Let’s look at the extension immediately above.
The AfterDeploymentValidation
event is the only event a portable extension can observe in a portable manner that indicates that the container is open for business. It is guaranteed to fire last in the dance that the container performs as it starts up.
In the extension above, then, you can see that it has saved off the beans it has discovered that have been annotated with a Startup
annotation (which isn’t defined in the gist, so it could be javax.ejb.Startup
or a Startup
annotation that Dan has defined himself). Then, for these beans, it uses the BeanManager
‘s getReference()
method to get an actual Java object representing those beans.
So what’s the toString()
call for?
The short answer is: this is the only method that could conceivably cause the container-managed client proxy that is the object reference here to call the actual constructor of the actual underlying object that the user and everyone else is actually interested in.
For more detail, let’s look at the specification, section 6.5.3, which concerns contextual references—the return value of the getReference()
method above—and which reads in part:
If the bean has a normal scope [e.g. not
Dependent
orSingleton
], then the contextual reference for the bean is a client proxy, as defined in Client proxies, created by the container, that implements the given bean type and all bean types of the bean which are Java interfaces.
OK, so whatever the getReference()
method returns is a client proxy. That means it’s going to forward any method invocation it receives to the object that it’s proxying (known as the bean’s contextual instance. CDI containers try to be efficient, so that object may not have been created yet—the proxy, in other words, might be lazy. So here, the contextual reference returned is a contextual reference (a client proxy) of type Object
, and the toString()
invocation causes the contextual reference (client proxy) to realize that oh, hey, the underlying contextual instance of whatever bean this is doesn’t yet exist, and to therefore invoke the “real” constructor, so that the “real” object (the contextual instance) can return something from its toString()
method.
As a happy side effect, the container is of course the one causing the instantiation of the “real” object, so it is going to perform dependency injection, invoke initializer methods, invoke any PostConstruct
-annotated methods, and so on. Presto: you have a portable way for your bean to be instantiated when the container comes up.
(Side note: toString()
in particular is used because there is a little nugget at the bottom of the Client Proxies section of the specification that says, innocuously (emphasis mine):
The behavior of all methods declared by
java.lang.Object
, except fortoString()
, is undefined for a client proxy. Portable applications should not invoke any method declared byjava.lang.Object
, except fortoString()
, on a client proxy.
File that one away: among other things that might mean don’t put your CDI-managed objects in Map
s or Collection
s, since doing so will use client proxy equals(Object)
and hashCode()
methods!)
So. If you’re feeling like this whole thing is a bit of a steaming hack—you call toString()
, and a whole lot of very sophisticated magic happens (!)—I can’t disagree.
Fortunately, there’s a better way.
Instead of the toString()
hack above, you can do the same thing in the extension in a more sanctioned, more explicit manner.
The first thing to understand is: who is doing the actual creation? If we can understand that, then we can understand how to do it eagerly and idiomatically.
The ultimate answer is: the bean itself, via the create()
method it implements from the Contextual
interface. So if you ever get a Bean
in your hand (or any other Contextual
), you can call create()
on it all day long and get new instances (this should sound scary, because it is). For example, suppose you have an ApplicationScoped
-annotated bean in your hand in the form of a Bean
object. If you call create()
on it three times, you will create three instances of this object, hopefully surprising and gently horrifying you. Those instances may be wrapped in proxies (for interception and such), but they won’t be wrapped in client proxies. Don’t do this!
Still, scary or not, we’ve found the method that some part of the container somewhere at some point will invoke when a contextual instance is needed (to further wrap in a contextual reference). But obviously when you inject an ApplicationScoped
-annotated bean into, say, three different locations in your code somewhere, everything behaves as though there’s only one instance, not three. So clearly the container is not running around rampantly calling create()
every time it needs an object. It’s acquiring them from somewhere, and having them created if necessary.
The container is actually calling a particular get()
method on the bean’s associated Context
, the machinery that implements its scope. This method’s contract basically says that the Context
implementation should decide whether to return an existing contextual instance, or a new one. So you can see that the Context
underlying application scope will (hopefully!) return the One True Instance™ of the bean in question, whereas the Context
implementation underlying some other scope may create new instances each time.
OK, so what do we know? Let’s step back.
- We know that a contextual reference is almost always a lazy client proxy, so merely obtaining one of these (via
BeanManager
‘sgetReference()
method) is not enough (the existence of a client proxy says nothing about the existence of its underlying contextual instance). This is why Dan’s example invokestoString()
on the client proxy. - We know how to talk to a
Context
and have it ensure that the right contextual instance (potentially newly-created) is returned if we ask for one. That is, we know that if we talk to it right, we won’t end up creating too many instances of a bean, thus disregarding its scope.
So to eagerly instantiate beans at startup while respecting their scopes, we should follow this approach in the extension as well—we’ll do basically what a lazy contextual reference (client proxy) does, in other words, when a toString()
method is invoked on it. We’ll need to effectively ask the right Context
for a contextual instance of the bean in question, creating it if necessary. This also would allow for custom-scoped beans to be instantiated eagerly, provided of course that the Context
backing the custom scope is actually active.
If you have a BeanManager
handy, and a Bean
object of a particular type (say Bean<Object>
), then you can get the relevant Context
quite easily:
final Context context = beanManager.getContext(bean.getScope());
Then you need a CreationalContext
, the somewhat opaque object that assists the Context
with creation, should the Context
decide that creation is necessary:
final CreationalContext<Object> cc = beanManager.createCreationalContext(bean);
Finally, we can ask the Context
for a contextual instance directly:
final Object contextualInstanceNotContextualReference = context.get(bean, cc);
Note, as the variable name makes clear, this is a contextual instance, not a contextual reference! This is not a client proxy! So you really don’t want to use it after this point.
So if you replace Dan’s line 19 above with these code snippets, you will eagerly instantiate beans at startup without relying on magic side effects.
These articles about CDI are so cool 🙂 Could you please share more with us?
Thanks