14

As we all know that it is recommended to use annotations from javax.enterprise.context instead of javax.faces.bean as they are getting deprecated.

And we all found ManagedBeans with eager="true" annotated with @ApplicationScoped from javax.faces.bean and having a @PostConstruct method are very useful to do web application initialization e.g: read properties from file system, initialize database connections, etc...

Example :

import javax.faces.bean.ApplicationScoped;
import javax.faces.bean.ManagedBean;
import javax.annotation.PostConstruct;

@ApplicationScoped
@ManagedBean(eager=true)
public class someBean{

    @PostConstruct
    public void init(){
        //Do all needed application initialization.
    }
    ...
}

What I want to know is how can I get the same behavior if I used annotations from javax.enterprise.context.

Note: @Startup annotation from javax.ejb will help to run that code but only at the moment of deployment of the webapp when the application server Starts.

BalusC
  • 1,082,665
  • 372
  • 3,610
  • 3,555
Jalal Sordo
  • 1,605
  • 3
  • 41
  • 68

4 Answers4

15

This is not provided by CDI or JSF. You could homegrow your own with a custom CDI qualifier and a ServletContextListener to hook on webapp start.

@Qualifier
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Eager {
    //
}

@WebListener
public class EagerListener implements ServletContextListener{

    private static final AnnotationLiteral<Eager> EAGER_ANNOTATION = new AnnotationLiteral<Eager>() {
        private static final long serialVersionUID = 1L;
    };

    @Override
    public void contextInitialized(ServletContextEvent event) {
        CDI.current().select(EAGER_ANNOTATION).forEach(bean -> bean.toString());
    }

    @Override
    public void contextDestroyed(ServletContextEvent event) {
        // NOOP.
    }

}

(note: toString() triggers lazy instantiation)

import com.example.Eager;
import javax.enterprise.context.ApplicationScoped;

@Eager
@ApplicationScoped
public class YourEagerApplicationScopedBean {

    @PostConstruct
    public void init() {
        System.out.println("Application scoped init!");
    }
}

As to existing libraries, only JSF utility library OmniFaces offers @Eager out the box.

import org.omnifaces.cdi.Eager;
import javax.enterprise.context.ApplicationScoped;

@Eager
@ApplicationScoped
public class YourEagerApplicationScopedBean {

    @PostConstruct
    public void init() {
        System.out.println("Application scoped init!");
    }
}

It's also supported on @SessionScoped, @ViewScoped and @RequestScoped.

Regardless of the approach, the only disadvantage is that FacesContext isn't available at the moment the bean is constructed. But that shouldn't be a big problem, with CDI you can simply directly @Inject artifacts of interest such as ServletContext or HttpSession.

BalusC
  • 1,082,665
  • 372
  • 3,610
  • 3,555
  • One problem here, the qualifier `@Eager` should be added to injection point. Based on your answer, I posted an answer below that solves the issue. Thanks. – Eng.Fouad Dec 20 '19 at 22:04
2

CDI 1.1 also offers a standard way to observe scope lifecycle events, for instance:

public void processApplicationScopedInit(@Observes @Initialized(ApplicationScoped.class) ServletContext payload) {}
public void processApplicationScopedDestroyed(@Observes @Destroyed(ApplicationScoped.class) ServletContext payload) {}

For more information: http://www.next-presso.com/2014/06/you-think-you-know-everything-about-cdi-events-think-again/

Stéphane Appercel
  • 1,427
  • 2
  • 13
  • 21
2

As an alternative, you could use EJB instead of CDI. Then you can have a @Singleton with @Startup

import javax.annotation.PostConstruct;
import javax.ejb.Singleton;
import javax.ejb.Startup;

@Singleton
@Startup
public class SomeBean {

    @PostConstruct
    public void init(){
        //Do all needed application initialization.
    }
    ...
}
gmanjon
  • 1,483
  • 1
  • 12
  • 16
  • Why not use your proposed bean and inject the CDI beans that you need to be eagerly instatiated? Name the class EagerInstatiator. – LovaBill May 06 '20 at 08:42
  • That would be just briedging. `EagerInstatiator` could directly be the name of this EJB. ALthough if you need a more complex orchestration you could do that, and use this EJB to orchestrate the different Beans that take part on the initialization – gmanjon Dec 27 '21 at 15:23
0

Here is one approach I use:

import javax.annotation.PostConstruct;
import javax.ejb.Singleton;
import javax.ejb.Startup;
import javax.enterprise.context.ApplicationScoped;
import javax.enterprise.inject.Any;
import javax.enterprise.inject.spi.BeanManager;
import javax.enterprise.util.AnnotationLiteral;
import javax.inject.Inject;

@Startup
@Singleton
public class AppStartup
{
    @Inject
    private BeanManager beanManager;

    @PostConstruct
    public void init()
    {       
        // enforce initializing eager CDI beans
        var beans = beanManager.getBeans(Object.class, new AnnotationLiteral<Any>(){});
        for(var bean : beans)
        {
            if(bean.getBeanClass().getAnnotation(Eager.class) != null && bean.getBeanClass().getAnnotation(ApplicationScoped.class) != null)
            {
                var beanProxyInstance = beanManager.getReference(bean, bean.getBeanClass(), beanManager.createCreationalContext(bean));

                // invoking toString() on the proxy object will invoke the method annotated with @PostConstruct, if has not been invoked yet
                beanProxyInstance.toString();
            }
        }
    }
}
import java.lang.annotation.Retention;
import java.lang.annotation.Target;

import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

@Retention(RUNTIME)
@Target({TYPE})
public @interface Eager {}
import javax.enterprise.context.ApplicationScoped;

@Eager
@ApplicationScoped
public class SomeCdiBean {}

Now you can inject this CDI bean without any extra qualifiers:

@Inject
private SomeCdiBean someCdiBean;
Eng.Fouad
  • 115,165
  • 71
  • 313
  • 417