Speaking on the CDI list about CDI 2.0 standalone container API Jozef Hartinger sent an answer which is quite obvious when you know it but which can change your life if you don’t: how CDI 1.1 introduced @Startup without the need of EJBs.
@Startup or link a bean lifecycle on the container
As a quick reminder @Startup in EJB API allows to force an EJB instance to be created eagerly when the container starts. Combining it with @PostContruct you can add some application initialization logic with its deployment and combining it with @PreDestroy you get an application clean up hook:
@Startup @Singleton public class ProvisioningDataForApplicationLifecycle { @PostConstruct private void init() { // when app is deployed } @PreDestroy private void cleanUp() { // when app is undeployed } }
CDI 1.0 didn’t have such an annotation and in several cases it was causing some headaches to have this feature. The most known workaround was to write a CDI Extension observing the AfterDeploymentValidation container event and retrieve the “startup” bean in this observer to call any method on it (most of the time toString() to not do anything important). Not very user friendly isn’t it? (I skip few super tricky cases where this hack was not working since we can suppose you do it with a bean you designed to work with this extension).
CDI 1.1 answer
CDI 1.1 introduces a standard way to observe lifecycle of built-in normal scopes. You just need to write an observer with the qualifier @Initialized(scopeclass) for its activation and @Destroyed(scopeclass) for its destruction. How does it solve our issue? We’d need a scope activated for the application lifetime…like application one. Using @ApplicatonScoped with these observer qualifiers allows you to use an observer to replace the EJB of the previous part. The obsever doesn’t have to be @ApplicationScoped but if you really need the EJB feature because you store anything in your bean you can scope your observer @ApplicationScoped as well.
Here is a sample:
@ApplicationScoped public class ProvisioningDataForApplicationLifecycle { private final Map<String, User> users = new HashMap<>(); // + getter public void init(@Observes @Initialized(ApplicationScoped.class) Object init) { users.put("cdi", new User("cdi", "1.1")); users.put("deltaspike", new User("deltaspike", "1.3")); } public void destroy(@Observes @Destroyed(ApplicationScoped.class) Object init) { users.clear(); } }
Note: payload of the observer can be ServletContext for application scope as well when the application is a webapp.
What is funny with this sample is you can replace the destroy method by a standard @PreDestroy because the scope of the bean is the observed scope. You gain one annotation so it is tempting right? Issue is then if other application scoped beans rely on this particular bean then you are not sure they will be destroyed in the right order where using the observer you know ProvisioningDataForApplicationLifecycle instance will be the last one destroyed. But in real applications where this pattern is used this case is relatively rare so using the observer is just more sexy because it makes the bean symetric.
Want more content ? Keep up-to-date on my new blog
Or stay in touch on twitter @rmannibucau
Thanks, nice post
nice thx.
Hi Romain, please i not understand how works the CDI 1.1 @Startup .
The observer listen for :
@Initialized(scopeclass)
——————————————
nut the cdi when fire the event?
When scan all beans annotated with @ApplicationScoped ????????????
And how it know that the beans to be initialized ????
I don’t understand the mechanism
Tank you
Mauro
it is triggered when a standard normal scope (request, session, application) is created. This means scanning is already done and beans are ready to listen it.
Pratically when after the web app is deployed cdi scan all class and found a class annotated with @ApplicationScoped ?
There is no link with webapp but after cdi is ready yes
Your code into ProvisioningDataForApplicationLifecycle is not clear typo .
it containt many caracter like "cdi"
private final Map<String, User> users = new HashMap<>(); // + getter
public void init(@Observes @Initialized(ApplicationScoped.class) Object init) {
users.put("cdi", new User("cdi", "1.1"));
users.put("deltaspike", new User("deltaspike", "1.3"));
}
Fixed thanks!
Hi Romain. Thanks for the post. If I understand it, this can solve a real headache.
About your note “payload of the observer can be ServletContext for application scope as well when the application is a webapp.” Is there anything else one would need to do to be able to use properties of that ServletContext? In TomEE 7.0.0-M1, whenever I cast the payload to ServletContext and then try to use some of the methods (like getResourceAsStream), I get a “java.lang.IllegalStateException: Didnt find a web context for TomEEWebappClassLoader”.
In TomEE 7.0.0-M1 CDI is started before servlet container to be able to inject CDI beans in servlet instances. Said otherwise the ServletContext is not usable when AppContext is started but should be usable later.
BTW it has been fixed https://issues.apache.org/jira/browse/TOMEE-1687
Hi, I am trying to use this example replacing EJB annotations with CDI.
I @Inject back end EJB that performs some DB logic
In debugger I can see EJB reference but call to EJB just hangs.
Same thing works with EJB @Startup @Singleton
My environment is WildFly10, Cassandra back end.
I appreciate any ideas on this subject
Probably do a jstack at that poment but can be a wildfly bug/issue. Remiving the ejb and using @transactional an help
Hi Romain, I found out the Weld (in Payara Server) fires 2 events for @Initialized(ApplicationScoped.class) – first one of type org.jboss.weld.event.ContextEvent, the second one is of type ServletContext as prescribed by the spec. I think it is rather bug in Weld and it probably doesn’t happen in other CDI containers, but I recommend to always specify the ServletContext event type:
@Observes @Initialized(ApplicationScoped.class) ServletContext event
well yes but Object is way useful to be able to test without having a servlet container too so depends the app
Pingback: Blocking the CDI container politely | Blame Laird