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 aDependsOn
relationship have been initialized before the singleton’sPostConstruct
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
andSingletonB
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 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
SingletonA
andSingletonB
are initialized. - Highlighted in orange is the message from the
firstTest
method. - Highlighted in green is the message from the
secondTest
method whenSingletonC
is 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.