Tomitribe support customers often use Java Singleton EJBs, and while powerful the sequence of startup and initialize can be confusing. This tutorial will help explain how Singleton EJB’s can be eagerly initialized at application startup and how we can define a priority during this process.

Review EJB Annotations

Before jumping into the code let’s do a recap of the following annotations used in this blog post:

  • @Singleton Component-defining annotation for a singleton session bean.
  • @Startup Marks a singleton bean for eager initialization during the application startup sequence.
  • @DependsOn Used to express an initialization dependency between singleton components. The container ensures that all singleton beans with which a singleton has a DependsOn relationship have been initialized before the singleton’s PostConstruct method is called.
  • @PostConstruct Used on a method that needs to be executed after dependency injection is done to perform any initialization. This method must be invoked before the class is put into service.
    ApplicationScoped Specifies that a bean is application scoped.

Life cycle of a singleton EJB

Now let’s refresh our memory with the life cycle of a Singleton before it gets ready to execute some business logic:

Figure 1

Figure 1 depicts how the EJB container checks for Dependency Injection and then for a @PostConstruct annotation in order to initialize the Singleton before its methods get invoked by a caller bean.

Singleton Session Bean Example

The Singleton Startup Ordering example has three Singleton beans: SingletonA, SingletonB, and SingletonC. Each singleton has a method called init annotated with @PostConstruct.

The init method stores the singleton class name in the List records from an ApplicationScoped Bean called Supervisor. Figure 2 depicts this process:

Figure 2

  • SingletonA and SingletonB are annotated with @Startup which means they are going to be initialized upon application startup.
  • SingletonC will not be initialized until the bean is invoked in the application.
  • SingletonB is annotated with @DependsOn("SingletonA")to enforce an initialization order with respect to SingletonA.

SingletonA.java

import javax.annotation.PostConstruct;
import javax.ejb.Singleton;
import javax.ejb.Startup;
import javax.inject.Inject;
import java.util.logging.Logger;

@Singleton
@Startup
public class SingletonA {

    @Inject
    Supervisor supervisor;

    private final static Logger LOGGER = Logger.getLogger(SingletonA.class.getName());


    @PostConstruct
    public void init() {
        LOGGER.info("Hi from init in class: " + this.getClass().getName());
        supervisor.addRecord(this.getClass().getSimpleName());
    }
}

SingletonB.java

import javax.annotation.PostConstruct;
import javax.ejb.DependsOn;
import javax.ejb.Singleton;
import javax.ejb.Startup;
import javax.inject.Inject;
import java.util.logging.Logger;

@Singleton
@Startup
@DependsOn("SingletonA")
public class SingletonB {

    @Inject
    Supervisor supervisor;

    private final static Logger LOGGER = Logger.getLogger(SingletonB.class.getName());

    @PostConstruct
    public void init() {
        LOGGER.info("Hi from init in class: " + this.getClass().getName());
        supervisor.addRecord(this.getClass().getSimpleName());
    }
}

SingletonC.java

import javax.annotation.PostConstruct;
import javax.ejb.Singleton;
import javax.inject.Inject;
import java.util.logging.Logger;

@Singleton
public class SingletonC {
    @Inject
    Supervisor supervisor;

    private final static Logger LOGGER = Logger.getLogger(SingletonC.class.getName());

    @PostConstruct
    public void init() {
        LOGGER.info("Hi from init in class: " + this.getClass().getName());
        supervisor.addRecord(this.getClass().getSimpleName());

    }

    public String hello() {
        return "Hello from SingletonC.class";
    }
}

Supervisor.java

import javax.enterprise.context.ApplicationScoped;
import java.util.ArrayList;
import java.util.List;

@ApplicationScoped
public class Supervisor {
    private final List records = new ArrayList<>();

    public void addRecord(String beanClass){
        records.add(beanClass);
    }

    public String getRecord(){
        return records.toString();
    }
}

Example test explained

The class TestSingletonStartupOrder.java contains two tests that are executed in alphabetical order via the annotation @FixMethodOrder(MethodSorters.NAME_ASCENDING). For the test, we are going to use TomEE Arquillian and JUnit as our test framework.

TestSingletonStartupOrder.java

import org.jboss.arquillian.container.test.api.Deployment;
import org.jboss.arquillian.junit.Arquillian;
import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.asset.StringAsset;
import org.jboss.shrinkwrap.api.spec.WebArchive;
import org.junit.FixMethodOrder;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.MethodSorters;
import org.foo.SingletonA;
import org.foo.SingletonB;
import org.foo.SingletonC;
import org.foo.Supervisor;

import java.util.logging.Logger;

import static junit.framework.TestCase.assertTrue;


@RunWith(Arquillian.class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class TestSingletonStartupOrder {
    private final static Logger LOGGER = Logger.getLogger(TestSingletonStartupOrder.class.getName());

    @Deployment()
    public static WebArchive createDeployment() {
        final WebArchive webArchive = ShrinkWrap.create(WebArchive.class, "test.war")
                                                .addClass(SingletonA.class)
                                                .addClass(SingletonB.class)
                                                .addClass(SingletonC.class)
                                                .addClass(Supervisor.class)
                                                .addAsWebInfResource(new StringAsset(""), "beans.xml");
        return webArchive;
    }


    @Test
    public void firstTest(Supervisor supervisor) {
        LOGGER.info("SUPERVISOR: [" + supervisor.getRecord() + "]");
        assertTrue(supervisor.getRecord().equals("[SingletonA, SingletonB]"));
    }

    @Test
    public void secondTest(Supervisor supervisor, SingletonC singletonC) {
        LOGGER.info(singletonC.hello());
        LOGGER.info("SUPERVISOR: [" + supervisor.getRecord() + "]");
        assertTrue(supervisor.getRecord().equals("[SingletonA, SingletonB, SingletonC]"));
    }
}

The assertion in the firstTest() will be true only if the records stored in the Supervisor.records are SingletonA and SingletonB. Notice that the order is validated too. In this test, we don’t expect to see SingletonC initialized since it’s not annotated with @Startup. Figure 3 depicts the order of records after the test is executed.

Figure 3

The secondTest injects SingletonC as a parameter in the tests, therefore it’s init() method will be executed and Supervisor.records will now have an entry for SingletonC. Figure 4 depicts the order of records after the test is executed.

Figure 4

The following sequence Diagram depicts the test workflow:

When you execute the tests, the following INFO log messages appear:

  • Highlighted in blue you can see how during server startup both SingletonA and SingletonB are initialized.
  • Highlighted in orange is the message from the firstTest method.
  • Highlighted in green is the message from the secondTest method when SingletonC is initialized, the output from the SingletonC.hello() method and the status of Supervisor.records.

..
INFO – OpenWebBeans Container is starting…
INFO – OpenWebBeans Container has started, it took 898 ms.
INFO – Created Ejb(deployment-id=SingletonA, ejb-name=SingletonA, container=Default Singleton Container)
INFO – Created Ejb(deployment-id=SingletonB, ejb-name=SingletonB, container=Default Singleton Container)
INFO – Created Ejb(deployment-id=SingletonC, ejb-name=SingletonC, container=Default Singleton Container)
INFO – Hi from init in class: org.foo.SingletonA
INFO – Started Ejb(deployment-id=SingletonA, ejb-name=SingletonA, container=Default Singleton Container)
INFO – Hi from init in class: org.foo.SingletonB
INFO – Started Ejb(deployment-id=SingletonB, ejb-name=SingletonB, container=Default Singleton Container)
INFO – Started Ejb(deployment-id=SingletonC, ejb-name=SingletonC, container=Default Singleton Container)
INFO – Deployed Application(path=/Users/cesar/git/tomee/examples/singleton-startup-ordering/target/arquillian/0/test)
INFO – Using org.apache.myfaces.ee.MyFacesContainerInitializer
INFO – Using InjectionProvider org.apache.myfaces.spi.impl.CDIAnnotationDelegateInjectionProvider
INFO – ServletContext initialized.
INFO – MyFaces Core has started, it took [866] ms.

INFO – SUPERVISOR: [[SingletonA, SingletonB]]


INFO – Hi from init in class: org.foo.SingletonC
INFO – Hello from SingletonC.class
INFO – SUPERVISOR: [[SingletonA, SingletonB, SingletonC]]

Conclusion

As you can see, the life cycle of a Singleton EJB is quite simple but can be instrumented with powerful annotations like @Startup to enforce eager initialization during the application startup sequence, @DependsOn to express initialization dependencies between singleton components and @PostConstruct to perform initialization operations.

Cesar Hernandez

Cesar Hernandez

César Hernández is a Senior Software Engineer at Tomitribe with experience in Enterprise Java Applications. He is a Java Champion, Duke's Choice Award winner, Eclipse Committer, Open Source advocate, teacher, and public speaker. When César is away from a computer, he enjoys spending time with his family, traveling and playing music with the Java Community Band, The Null Pointers. Follow Cesar on twitter @CesarHgt
CesarHgt

Leave a Reply