mvn clean test
Singleton Startup Ordering
This examples shows in practice the @Startup and @DependsOn annotations on singleton EJB’s.
Run the tests
The scenario
-
The example is composed by three singleton beans:
SingletonA,SingletonB,SingletonC. -
The three EJB’s contains a
@PostConstructannotation for theinitmethod that is executed after dependency injection is done to perform any initialization. This method is invoked before the class is put into service. -
The
initmethod store the name of the bean class that is been initialized in theSupervisorbean. -
The
Supervisorbean is annotated with@ApplicationScopedto be able to share the list of bean names stored in therecordsattribute. -
SingletonAandSingletonBare annotated with@Startupwhich means they are going to initialized upon application startup.SingletonCwill be initialized until the bean is going to be used in later injection point. -
SingletonBis annotated with@DependsOn("SingletonA")to enforce a initialization order with respect toSingletonA.
SingletonA.java: Singleton EJB annotated with @Startup. It depends on the EJB Supervisor.
package org.foo;
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: Singleton EJB annotated with @Startup and DependsOn. It depends on the EJB Supervisor.
package org.foo;
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: Singleton EJB. It depends on the EJB Supervisor.
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: Applicaiton scoped Bean that keep track of a list of Bean Names.
import javax.enterprise.context.ApplicationScoped;
import java.util.ArrayList;
import java.util.List;
@ApplicationScoped
public class Supervisor {
private final List<String> records = new ArrayList<>();
public void addRecord(String beanClass){
records.add(beanClass);
}
public String getRecord(){
return records.toString();
}
}
The tests
-
The class
TestSingletonStartupOrder.javacontains two test that are executed in order via the annotation@FixMethodOrder(MethodSorters.NAME_ASCENDING) -
firstTest: assert true if and only if the records stored in theSupervisor.recordare equals to[SingletonA, SingletonB]. Notice that the order is validated too. In this test we don’t expect to seeSingletonCinitialized since it’s not annotated with@Startup. -
secondTest: This test injectSingletonCas a parameter in the tests, therefore it asserts to true if and only if the records stored in theSupervisor.recordare equals to[SingletonA, SingletonB, SingletonC]
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/>"), "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]"));
}
}
About the Test architecture
The test cases from this project are built using Arquillian and TomEE
Remote. The arquillian configuration can be found in
src/test/resources/arquillian.xml