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:
@SingletonComponent-defining annotation for a singleton session bean.@StartupMarks a singleton bean for eager initialization during the application startup sequence.@DependsOnUsed to express an initialization dependency between singleton components. The container ensures that all singleton beans with which a singleton has aDependsOnrelationship have been initialized before the singleton’sPostConstructmethod is called.@PostConstructUsed 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
SingletonAandSingletonBare annotated with@Startupwhich means they are going to be initialized upon application startup.SingletonCwill not be initialized until the bean is invoked in the application.SingletonBis annotated with@DependsOn("SingletonA")to enforce an initialization order with respect toSingletonA.
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
SingletonAandSingletonBare initialized. - Highlighted in orange is the message from the
firstTestmethod. - Highlighted in green is the message from the
secondTestmethod whenSingletonCis initialized, the output from theSingletonC.hello()method and the status ofSupervisor.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.






