CDI and @Startup: SOLVED!


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

16 thoughts on “CDI and @Startup: SOLVED!

  1. mauro

    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

    Reply
    1. rmannibucau Post author

      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.

      Reply
  2. mauro

    Your code into ProvisioningDataForApplicationLifecycle is not clear typo .

    it containt many caracter like "cdi&quot
    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"));
    }

    Reply
  3. Dave G

    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”.

    Reply
    1. rmannibucau Post author

      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.

      Reply
      1. Alex

        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

      2. rmannibucau Post author

        Probably do a jstack at that poment but can be a wildfly bug/issue. Remiving the ejb and using @transactional an help

  4. ondrejmihalyi

    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

    Reply
  5. Pingback: Blocking the CDI container politely | Blame Laird

Leave a comment