How do I force an application-scoped bean to instantiate at application startup?
Asked Answered
M

4

43

I can't seem to find a way to force an application-scoped managed bean to be instantiated/initialized when the web app is started. It seems that application-scoped beans get lazy-instantiated the first time the bean is accessed, not when the web app is started up. For my web app this happens when the first user opens a page in the web app for the first time.

The reason I want to avoid this is because a number of time-consuming database operations happen during the initialization of my application-scoped bean. It has to retrieve a bunch of data from persistent storage and then cache some of it that will be frequently displayed to the user in the form of ListItem elements, etc. I don't want all that to happen when the first user connects and thus cause a long delay.

My first thought was to use an old style ServletContextListener contextInitialized() method and from there use an ELResolver to manually request the instance of my managed bean (thus forcing the initialization to happen). Unfortunately, I can't use an ELResolver to trigger the initialization at this stage because the ELResolver needs a FacesContext and the FacesContext only exists during the lifespan of a request.

Does anyone know of an alternate way to accomplish this?

I am using MyFaces 1.2 as the JSF implementation and cannot upgrade to 2.x at this time.

Mobilize answered 30/8, 2010 at 12:57 Comment(0)
S
58

My first thought was to use an old style ServletContextListener contextInitialized() method and from there use an ELResolver to manually request the instance of my managed bean (thus forcing the initialization to happen). Unfortunately, I can't use an ELResolver to trigger the initialization at this stage because the ELResolver needs a FacesContext and the FacesContext only exists during the lifespan of a request.

It doesn't need to be that complicated. Just instantiate the bean and put it in the application scope with the same managed bean name as key. JSF will just reuse the bean when already present in the scope. With JSF on top of Servlet API, the ServletContext represents the application scope (as HttpSession represents the session scope and HttpServletRequest represents the request scope, each with setAttribute() and getAttribute() methods).

This should do,

public void contextInitialized(ServletContextEvent event) {
    event.getServletContext().setAttribute("bean", new Bean());
}

where "bean" should be the same as the <managed-bean-name> of the application scoped bean in faces-config.xml.


Just for the record, on JSF 2.x all you need to do is to add eager=true to @ManagedBean on an @ApplicationScoped bean.

@ManagedBean(eager=true)
@ApplicationScoped
public class Bean {
    // ...
}

It will then be auto-instantiated at application startup.

Or, when you're managing backing beans by CDI @Named, then grab OmniFaces @Eager:

@Named
@Eager
@ApplicationScoped
public class Bean {
    // ...
}
Stover answered 30/8, 2010 at 13:24 Comment(9)
+1 for an effective solution. One little question: is it officially ok to do that as per the spec, or does it rely on some JSF implementation details? I mean, a JSF implementation could decide to keep track of whether an application bean was instantiated in a completely non-obvious way and would then recreate the bean, for instance.Grenade
@Stover That was so simple and it works. I had avoided using the setAttribute() method in the ServletContext because I thought it would interfere with JSF, but apparently not. PS: Love your page at blogspot.com - your old article on using DataTables was helpful.Mobilize
@Jim: you're welcome. @ewernli: the spec doesn't explicitly allow that, but it does also not expliticly disallow that. The spec however describes that a managed bean must be created when not present in the scope.Stover
@BalusC, what if application scoped bean has properties that is either JSF managed bean or Spring bean, how to initialize it with fully flagged dependencies?Chemist
@org: you would need to take care over them yourself. As a hacky alternative, you can also create a simple view with the application scoped bean attached and call URL#openStream() on the view's local URL during contextInitialized().Stover
@Stover , That is what i had suggested, to maintain transitive dependencies.Chemist
One problem with eager. While bean instantiating, it can't be accessed, otherwise jsf call creation of another instance.Infusionism
When the JSF 2.x is loaded, could it access to other inyected beans? I´m getting null when trying to access to a Managed Property. I suppose that it isn´t instantiated yet in the container...Brazilein
@Stover can @ViewScoped and @Named (non-deprecated annotations) be pre-loaded, means tagged with @Eager? My point here is that these annotated backing-beans have a javax.cache.Cache which is pre-filled in a @PostConstruct method. Now, only on first request it takes some (considerable) time to see a response from it but if I could eager-load it (e.g. when deployed on web container?) then the cache is prepared and all requests are served by it with full speed.Supereminent
R
25

Romain Manni-Bucau posted a neat solution to this that uses CDI 1.1 on his blog.

The trick is to let the bean observe the initialization of the built-in lifecycle scopes, i.e. ApplicationScoped in this case. This can also be used for shutdown cleanup. So an example looks like this:

@ApplicationScoped
public class ApplicationScopedStartupInitializedBean {
    public void init( @Observes @Initialized( ApplicationScoped.class ) Object init ) {
        // perform some initialization logic
    }

    public void destroy( @Observes @Destroyed( ApplicationScoped.class ) Object init ) {
        // perform some shutdown logic
    }
}
Rosefish answered 11/9, 2015 at 9:34 Comment(4)
When migrating from GlassFish 4.1 to Payara 4.1.1.164, I encountered strange bugs where a @PersistenceContext field in an @ApplicationScoped bean that uses this pattern for eager initialization wasn't injected properly. There were errors like this: "No valid EE environment for injection of ApplicationScopedStartupInitializedBean". Turns out the parameter must be of type ServletContext to fix this, i.e. public void init( @Observes @Initialized( ApplicationScoped.class ) ServletContext init )Bos
I'm about to file this as a bug. Did you ever find a bug report about this or have you filed it yourself? There was github.com/payara/Payara/issues/299 which either is about something else or wasn't sufficient.Levkas
@KarlRichter No, I didn't investigate this any further. I thought the change of the parameter type might have been enforced by a more strict application of some specification or something like that.Bos
My worry would be that there is still chance for @Observes @Initialized not to be complete before @ApplicationScoped bean is used somewhere else. I would suggest not to put any initialization logic in the @Observes at all, and still leave that up to the @PostCreate. Same for @Observes @Destroyed.Laughable
M
2

As far as I know, you can't force a managed bean to be instantiated at application startup.

Maybe you could use a ServletContextListener which, instead of instantiating your managed bean, will perform all the database operations itself?


Another solution might be to instantiate your bean manually at application startup, and then set the bean as an attribute of your ServletContext.

Here is a code sample:

public class MyServletListener extends ServletContextListener {

    public void contextInitialized(ServletContextEvent sce) {
        ServletContext ctx = sce.getServletContext();
        MyManagedBean myBean = new MyManagedBean();
        ctx.setAttribute("myManagedBean", myManagedBean);
    }

}

In my opinion, this is far from clean code, but it seems like it does the trick.

Marijane answered 30/8, 2010 at 13:6 Comment(0)
A
-1

Additionally to BalusC's answer above you could use @Startup and @Singleton (CDI), e.g.

//@Named    // javax.inject.Named:       only needed for UI publishing
//@Eager    // org.omnifaces.cdi.Eager:  seems non-standard like taken @Startup below
@Startup    // javax.ejb.Startup:        like Eager, but more standard
@Singleton  // javax.ejb.Singleton:      maybe not needed if Startup is there
//@Singleton( name = "myBean" )  //      useful for providing it with a defined name
@ApplicationScoped
public class Bean {
    // ...
}

which is nicely explained here. Works in JPA 2.1 at least.

Almoner answered 19/1, 2016 at 8:30 Comment(6)
That's an EJB not a managed bean, which is quite different. EJBs run in backend and managed beans in frontend. EJBs run in transactional context too. The statement "works in JPA" is strange. EJBs don't require JPA at all to run.Stover
@Stover I think you are not quite correct and overstating the meaning/usage and it looks debatable to me. en.wikipedia.org/wiki/Enterprise_JavaBeans . works in JPA should mean I tested it (only) in an environment based on JPA 2.1. In a lot of real-life scenarios/setups I would say it's hard to definitely say - from an abstract/modelling point of view - whether something is an EJB or an application-scoped managed bean. So it would be interesting to know, why it is bad to do what I suggested although it works for me technically and from a modelling point of view.Almoner
It isn't bad in its own, on the contrary. It's just that you're technically not answering the question in its current form at all. You just confused enterprise beans with managed beans and I just pointed out that.Stover
? :-) the heading fits exactly, and the question details more or less exactly? Here is my answer to my same problem that solves what is asked there for me. With a rather slightly different setup maybe (PrimeFaces 5.3 for me instead of MyFaces 1.2 which should not matter for this)Almoner
@BalusC: let's not get off topic or too opinion based and let's track the arguments, where they seem more fitting: https://mcmap.net/q/18567/-what-is-the-difference-between-applicationscoped-and-singleton-scopes-in-cdi I'd really like to profit from your knowledge, but can't validate your answers so far :-/Almoner
@Stover will we end up with one instance of Bean or two one for CDI and one EJB?Tallyman

© 2022 - 2024 — McMap. All rights reserved.