Singleton Session Bean in Java EE

Singleton Session Bean in Java EE

1. Überblick

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 Java EE-Anwendung untersuchen.

2. Maven

Zunächst müssen wir die erforderlichen Maven-Abhängigkeiten inpom.xmldefinieren.

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 unterJavaEE API undtomEE.

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, mit dem sie kommuniziert.

Jeder Client erstellt eine neue Instanz von Stateful Bean und wird nicht für andere Clients freigegeben.

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

3.2. Zustandslose Session Beans

Eine zustandslose Session 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 behält den Status der Bean für den gesamten Lebenszyklus der Anwendung bei.

Singleton Session Beans ähneln Stateless Session Beans, aber nur eine Instanz des Singleton Session Beans wird in der gesamten Anwendung erstellt und nicht beendet, bis die Anwendung beendet wird.

Die einzelne Instanz der Bean wird von mehreren Clients gemeinsam genutzt und es kann gleichzeitig auf sie zugegriffen werden.

4. Erstellen einer Singleton Session Bean

Beginnen wir mit der Erstellung einer Schnittstelle dafür.

In diesem Beispiel verwenden wir die Annotationjavax.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 Annotationjavax.ejb.Remotezu 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 angegebenen Reihenfolge zu laden. Daher verwenden wir die Annotationjavax.ejb.DependsOn, um die Abhängigkeit unserer Bean von anderen Session Beans zu definieren.

Der Wert für die Annotation@DependsOnist 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 eineinitialize()-Methode, die die Bean initialisiert und sie mithilfe derjavax.annotation.PostConstruct-Anmerkung zu einer Lifecycle-Callback-Methode macht.

Mit dieser Anmerkung 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-Sitzungs-Bean: Container-verwaltete Parallelität und Bean-verwaltete Parallelität.

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

Die Annotation@ConcurrencyManagementnimmt den Wertjavax.ejb.ConcurrencyManagementTypean. 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, steuert der Container in der vom Container verwalteten Parallelität, wie Clients auf Methoden zugreifen.

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

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

Um die Zugriffsebene für jede der Geschäftsmethoden des Singletons anzugeben, verwenden wir die Annotationjavax.ejb.Lock. javax.ejb.LockType enthält die Werte für die Annotation@Lock. javax.ejb.LockType definiert zwei Werte:

  • LockType.WRITE - Dieser Wert bietet dem aufrufenden Client eine exklusive Sperre und verhindert, dass alle anderen Clients auf alle Methoden der Bean zugreifen. Verwenden Sie dies für Methoden, die den Status der Singleton-Bean ändern.

  • *LockType.READ* – Dieser Wert bietet mehreren Clients gleichzeitige Sperren für den Zugriff auf eine Methode. Verwenden Sie dies für Methoden, die nur Daten von der Bean lesen.

Vor diesem Hintergrund definieren wir die MethodesetStates()mit der Annotation@Lock(LockType.WRITE), um eine gleichzeitige Aktualisierung des Status durch Clients zu verhindern.

Damit Clients die Daten gleichzeitig lesen können, werdengetStates() mit@Lock(LockType.READ) versehen:

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

    private final Map countryStatesMap = new HashMap<>();

    @Lock(LockType.READ)
    public List getStates(String country) {
        return countryStatesMap.get(country);
    }

    @Lock(LockType.WRITE)
    public void setStates(String country, List states) {
        countryStatesMap.put(country, states);
    }
}

Um die Ausführung der Methoden für eine lange Zeit zu stoppen und die anderen Clients auf unbestimmte Zeit zu blockieren, verwenden wir die Annotationjavax.ejb.AccessTimeout, um lange wartende Aufrufe zu beenden.

Verwenden Sie die Annotation@AccessTimeout, um das Zeitlimit für die Millisekundenmethode zu definieren. Nach dem Timeout löst der Container einjavax.ejb.ConcurrentAccessTimeoutException aus und die Methodenausführung wird beendet.

5.2. Bean-Managed Concurrency

In der von Bean verwalteten Parallelität steuert der Container nicht den gleichzeitigen Zugriff von Clients auf Singleton Session Bean. Der Entwickler muss die Parallelität selbst implementieren.

Sofern der Entwickler keine Parallelität implementiert, können alle Clients gleichzeitig auf alle Methoden zugreifen. Java stellt die Grundelementesynchronization undvolatile für die Implementierung der Parallelität bereit.

Um mehr über die Parallelität zu erfahren, lesen Siejava.util.concurrenthere und Atomic Variableshere.

Definieren Sie für die Bean-verwaltete Parallelität die Annotation@ConcurrencyManagementmit dem Wertjavax.ejb.ConcurrencyManagementType.BEANfür die Singleton Session Bean-Klasse:

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

Als Nächstes schreiben wir die MethodesetStates(), mit der der Status der Bean mithilfe des Schlüsselwortssynchronizedgeändert wird:

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

Das Schlüsselwortsynchronized macht die Methode nur für jeweils einen Thread zugänglich.

Die MethodegetStates()ändert den Status der Bean nicht und muss daher nicht das Schlüsselwortsynchronizedverwenden.

6. Klient

Jetzt können wir den Client schreiben, um auf unsere Singleton Session Bean zuzugreifen.

Wir können das Session Bean auf Anwendungscontainerservern wie JBoss, Glassfish usw. bereitstellen. Um die Dinge einfach zu halten, verwenden wir die Klasse javax.ejb.embedded.EJBContainer. EJBContainer wird in derselben JVM wie der Client ausgeführt und bietet die meisten Dienste eines Enterprise-Bean-Containers.

Zunächst erstellen wir eine Instanz vonEJBContainer. Diese Containerinstanz durchsucht und initialisiert alle im Klassenpfad vorhandenen EJB-Module:

public class CountryStateCacheBeanTest {

    private EJBContainer ejbContainer = null;

    private Context context = null;

    @Before
    public void init() {
        ejbContainer = EJBContainer.createEJBContainer();
        context = ejbContainer.getContext();
    }
}

Als Nächstes erhalten wir dasjavax.naming.Context-Objekt vom initialisierten Containerobjekt. Mit der InstanzContext können wir den Verweis aufCountryStateContainerManagedBean abrufen und die folgenden Methoden aufrufen:

@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());
}

In ähnlicher Weise können wir die InstanzContextverwenden, um die Referenz für Bean-Managed Singleton Bean abzurufen und die entsprechenden Methoden aufzurufen:

@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());
}

Beenden Sie unsere Tests, indem SieEJBContainer in der Methodeclose() schließen:

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

7. Fazit

Singleton-Session-Beans sind genauso flexibel und leistungsstark wie alle Standard-Session-Beans, ermöglichen es uns jedoch, ein Singleton-Muster anzuwenden, um den Status für die Clients unserer Anwendung freizugeben.

Die Parallelitätsverwaltung der Singleton-Bean kann problemlos mithilfe der containergesteuerten Parallelität implementiert werden, bei der der Container den gleichzeitigen Zugriff mehrerer Clients übernimmt, oder Sie können mithilfe der beangesteuerten Parallelität Ihre eigene benutzerdefinierte Parallelitätsverwaltung implementieren.

Der Quellcode dieses Tutorials befindet sich inover on GitHub.