Singleton Session Bean in Jakarta EE

1. Übersicht

Wann immer eine einzelne Instanz einer Session Bean für einen bestimmten Anwendungsfall erforderlich ist, können wir eine Singleton Session Bean verwenden.

In diesem Tutorial werden wir dies anhand eines Beispiels mit einer Jakarta EE-Anwendung untersuchen.

2. Maven

Zunächst müssen wir die erforderlichen Maven-Abhängigkeiten in der Datei pom.xml definieren .

Definieren wir die Abhängigkeiten für die EJB-APIs und den eingebetteten EJB-Container für die Bereitstellung des EJB:

 javax javaee-api 8.0 provided   org.apache.openejb tomee-embedded 1.7.5 

Die neuesten Versionen finden Sie in Maven Central unter JavaEE API und tomEE.

3. Arten von Sitzungsbohnen

Es gibt drei Arten von Session Beans. Bevor wir uns mit Singleton Session Beans befassen, wollen wir uns ansehen, was der Unterschied zwischen den Lebenszyklen der drei Typen ist.

3.1. Stateful Session Beans

Eine Stateful Session Bean behält den Konversationsstatus mit dem Client bei, den sie kommuniziert.

Jeder Client erstellt eine neue Instanz von Stateful Bean und wird nicht mit anderen Clients geteilt.

Wenn die Kommunikation zwischen Client und Bean endet, wird auch die Session Bean beendet.

3.2. Zustandslose Sitzungsbohnen

Eine zustandslose Sitzungs-Bean behält keinen Konversationsstatus mit dem Client bei. Die Bean enthält den für den Client spezifischen Status nur bis zur Dauer des Methodenaufrufs.

Aufeinanderfolgende Methodenaufrufe sind im Gegensatz zur Stateful Session Bean unabhängig.

Der Container verwaltet einen Pool zustandsloser Beans, und diese Instanzen können von mehreren Clients gemeinsam genutzt werden.

3.3. Singleton Session Beans

Eine Singleton Session Bean verwaltet den Status der Bean für den gesamten Lebenszyklus der Anwendung.

Singleton Session Beans ähneln Stateless Session Beans, aber nur eine Instanz der Singleton Session Bean wird in der gesamten Anwendung erstellt und erst beendet, wenn die Anwendung heruntergefahren wird.

Die einzelne Instanz der Bean wird von mehreren Clients gemeinsam genutzt und kann gleichzeitig aufgerufen werden.

4. Erstellen einer Singleton Session Bean

Beginnen wir mit der Erstellung einer Schnittstelle dafür.

In diesem Beispiel verwenden wir die Annotation javax.ejb.Local , um die Schnittstelle zu definieren:

@Local public interface CountryState { List getStates(String country); void setStates(String country, List states); }

Die Verwendung von @Local bedeutet, dass auf die Bean in derselben Anwendung zugegriffen wird. Wir haben auch die Möglichkeit, die Annotation javax.ejb.Remote zu verwenden , mit der wir die EJB remote aufrufen können.

Jetzt definieren wir die Implementierungs-EJB-Bean-Klasse. Wir markieren die Klasse als Singleton Session Bean, indem wir die Annotation javax .ejb.Singleton verwenden .

Markieren Sie die Bean außerdem mit der Annotation javax .ejb.Startup , um den EJB-Container über die Initialisierung der Bean beim Start zu informieren:

@Singleton @Startup public class CountryStateContainerManagedBean implements CountryState { ... }

Dies wird als eifrige Initialisierung bezeichnet. Wenn wir @Startup nicht verwenden , bestimmt der EJB-Container, wann die Bean initialisiert werden soll.

Wir können auch mehrere Session Beans definieren, um die Daten zu initialisieren und die Beans in der spezifischen Reihenfolge zu laden. Daher verwenden wir die Annotation javax.ejb.DependsOn , um die Abhängigkeit unserer Bean von anderen Session Beans zu definieren.

Der Wert für die Annotation @DependsOn ist ein Array der Namen von Bean-Klassennamen, von denen unsere Bean abhängt:

@Singleton @Startup @DependsOn({"DependentBean1", "DependentBean2"}) public class CountryStateCacheBean implements CountryState { ... }

Wir definieren eine initialize () -Methode, die die Bean initialisiert und sie mithilfe der Annotation javax.annotation.PostConstruct zu einer Rückrufmethode für den Lebenszyklus macht .

Mit dieser Annotation wird sie vom Container beim Instanziieren der Bean aufgerufen:

@PostConstruct public void initialize() { List states = new ArrayList(); states.add("Texas"); states.add("Alabama"); states.add("Alaska"); states.add("Arizona"); states.add("Arkansas"); countryStatesMap.put("UnitedStates", states); }

5. Parallelität

Als Nächstes entwerfen wir das Parallelitätsmanagement von Singleton Session Bean. EJB bietet zwei Methoden zum Implementieren des gleichzeitigen Zugriffs auf die Singleton Session Bean: Container-verwaltete Parallelität und Bean-verwaltete Parallelität.

Die Anmerkung javax.ejb.ConcurrencyManagement definiert die Parallelitätsrichtlinie für eine Methode. Standardmäßig verwendet der EJB-Container die von Containern verwaltete Parallelität.

Die Annotation @ConcurrencyManagement nimmt den Wert javax.ejb.ConcurrencyManagementType an. Die Optionen sind:

  • ConcurrencyManagementType.CONTAINER für containergesteuerte Parallelität.
  • ConcurrencyManagementType.BEAN für Bean-verwaltete Parallelität.

5.1. Container-verwaltete Parallelität

Einfach ausgedrückt, in containergesteuerter Parallelität steuert der Container, wie Clients auf Methoden zugreifen.

Verwenden wir die Annotation @ConcurrencyManagement mit dem Wert javax.ejb.ConcurrencyManagementType.CONTAINER :

@Singleton @Startup @ConcurrencyManagement(ConcurrencyManagementType.CONTAINER) public class CountryStateContainerManagedBean implements CountryState { ... }

To specify the access level to each of the singleton’s business methods, we'll use javax.ejb.Lock annotation. javax.ejb.LockType contains the values for the @Lock annotation. javax.ejb.LockType defines two values:

  • LockType.WRITE – This value provides an exclusive lock to the calling client and prevents all other clients from accessing all methods of the bean. Use this for methods that change the state of the singleton bean.
  • LockType.READThis value provides concurrent locks to multiple clients to access a method.

    Use this for methods which only read data from the bean.

With this in mind, we'll define the setStates() method with @Lock(LockType.WRITE) annotation, to prevent simultaneous update of the state by clients.

To allow clients to read the data concurrently, we'll annotate getStates() with @Lock(LockType.READ):

@Singleton @Startup @ConcurrencyManagement(ConcurrencyManagementType.CONTAINER) public class CountryStateContainerManagedBean implements CountryState { private final Map
    

To stop the methods execute for a long time and blocking the other clients indefinitely, we'll use the javax.ejb.AccessTimeout annotation to timeout long-waiting calls.

Use the @AccessTimeout annotation to define the number of milliseconds method times-out. After the timeout, the container throws a javax.ejb.ConcurrentAccessTimeoutException and the method execution terminates.

5.2. Bean-Managed Concurrency

In Bean managed concurrency, the container doesn't control simultaneous access of Singleton Session Bean by clients. The developer is required to implement concurrency by themselves.

Unless concurrency is implemented by the developer, all methods are accessible to all clients simultaneously. Java provides the synchronization and volatile primitives for implementing concurrency.

To find out more about concurrency read about java.util.concurrent here and Atomic Variables here.

For bean-managed concurrency, let’s define the @ConcurrencyManagement annotation with the javax.ejb.ConcurrencyManagementType.BEAN value for the Singleton Session Bean class:

@Singleton @Startup @ConcurrencyManagement(ConcurrencyManagementType.BEAN) public class CountryStateBeanManagedBean implements CountryState { ... }

Next, we'll write the setStates() method which changes the state of the bean using synchronized keyword:

public synchronized void setStates(String country, List states) { countryStatesMap.put(country, states); }

The synchronized keyword makes the method accessible by only one thread at a time.

The getStates() method doesn't change the state of the Bean and so it doesn't need to use the synchronized keyword.

6. Client

Now we can write the client to access our Singleton Session Bean.

We can deploy the Session Bean on application container servers like JBoss, Glassfish etc. To keep things simple, we will use the javax.ejb.embedded.EJBContainer class. EJBContainer runs in the same JVM as the client and provides most of the services of an enterprise bean container.

First, we'll create an instance of EJBContainer. This container instance will search and initialize all the EJB modules present in the classpath:

public class CountryStateCacheBeanTest { private EJBContainer ejbContainer = null; private Context context = null; @Before public void init() { ejbContainer = EJBContainer.createEJBContainer(); context = ejbContainer.getContext(); } }

Next, we'll get the javax.naming.Context object from the initialized container object. Using the Context instance, we can get the reference to CountryStateContainerManagedBean and call the methods:

@Test public void whenCallGetStatesFromContainerManagedBean_ReturnsStatesForCountry() throws Exception { String[] expectedStates = {"Texas", "Alabama", "Alaska", "Arizona", "Arkansas"}; CountryState countryStateBean = (CountryState) context .lookup("java:global/singleton-ejb-bean/CountryStateContainerManagedBean"); List actualStates = countryStateBean.getStates("UnitedStates"); assertNotNull(actualStates); assertArrayEquals(expectedStates, actualStates.toArray()); } @Test public void whenCallSetStatesFromContainerManagedBean_SetsStatesForCountry() throws Exception { String[] expectedStates = { "California", "Florida", "Hawaii", "Pennsylvania", "Michigan" }; CountryState countryStateBean = (CountryState) context .lookup("java:global/singleton-ejb-bean/CountryStateContainerManagedBean"); countryStateBean.setStates( "UnitedStates", Arrays.asList(expectedStates)); List actualStates = countryStateBean.getStates("UnitedStates"); assertNotNull(actualStates); assertArrayEquals(expectedStates, actualStates.toArray()); }

Similarly, we can use the Context instance to get the reference for Bean-Managed Singleton Bean and call the respective methods:

@Test public void whenCallGetStatesFromBeanManagedBean_ReturnsStatesForCountry() throws Exception { String[] expectedStates = { "Texas", "Alabama", "Alaska", "Arizona", "Arkansas" }; CountryState countryStateBean = (CountryState) context .lookup("java:global/singleton-ejb-bean/CountryStateBeanManagedBean"); List actualStates = countryStateBean.getStates("UnitedStates"); assertNotNull(actualStates); assertArrayEquals(expectedStates, actualStates.toArray()); } @Test public void whenCallSetStatesFromBeanManagedBean_SetsStatesForCountry() throws Exception { String[] expectedStates = { "California", "Florida", "Hawaii", "Pennsylvania", "Michigan" }; CountryState countryStateBean = (CountryState) context .lookup("java:global/singleton-ejb-bean/CountryStateBeanManagedBean"); countryStateBean.setStates("UnitedStates", Arrays.asList(expectedStates)); List actualStates = countryStateBean.getStates("UnitedStates"); assertNotNull(actualStates); assertArrayEquals(expectedStates, actualStates.toArray()); }

End our tests by closing the EJBContainer in the close() method:

@After public void close() { if (ejbContainer != null) { ejbContainer.close(); } }

7. Conclusion

Singleton Session Beans are just as flexible and powerful as any standard Session Bean but allow us to apply a Singleton pattern to share state across our application's clients.

Concurrency management of the Singleton Bean could be easily implemented using Container-Managed Concurrency where the container takes care of concurrent access by multiple clients, or you could also implement your own custom concurrency management using Bean-Managed Concurrency.

The source code of this tutorial can be found over on GitHub.