Singleton Session Bean dans Java EE

Singleton Session Bean dans Java EE

1. Vue d'ensemble

Chaque fois qu'une seule instance d'un Session Bean est requise pour un cas d'utilisation donné, nous pouvons utiliser un Singleton Session Bean.

Dans ce didacticiel, nous allons explorer cela à travers un exemple, avec une application Java EE.

2. Maven

Tout d'abord, nous devons définir les dépendances Maven requises dans lespom.xml.

Définissons les dépendances des API EJB et du conteneur EJB intégré pour le déploiement de l'EJB:


    javax
    javaee-api
    8.0
    provided



    org.apache.openejb
    tomee-embedded
    1.7.5

Les dernières versions sont disponibles sur Maven Central aux adressesJavaEE API ettomEE.

3. Types de Session Beans

Il existe trois types de session Beans. Avant d'explorer les beans session singleton, voyons quelle est la différence entre les cycles de vie des trois types.

3.1. Beans Stateful Session

Un bean session avec état maintient l'état conversationnel avec le client avec lequel il communique.

Chaque client crée une nouvelle instance de Stateful Bean et n'est pas partagé avec les autres clients.

Lorsque la communication entre le client et le bean se termine, le bean session se termine également.

3.2. Beans Session sans état

Un bean session sans état ne conserve aucun état conversationnel avec le client. Le bean contient l'état spécifique au client jusqu'à la durée d'invocation de la méthode.

Les invocations de méthodes consécutives sont indépendantes contrairement au bean Stateful Session.

Le conteneur maintient un pool de beans Stateless et ces instances peuvent être partagées entre plusieurs clients.

3.3. Haricots de session singleton

Un Singleton Session Bean conserve l'état du bean pendant tout le cycle de vie de l'application.

Les beans session singleton sont similaires aux beans session sans état, mais une seule instance du bean session singleton est créée dans toute l'application et ne se termine pas tant que l'application n'est pas fermée.

L'instance unique du bean est partagée entre plusieurs clients et peut être utilisée simultanément.

4. Création d'un bean de session singleton

Commençons par créer une interface pour cela.

Pour cet exemple, utilisons l'annotationjavax.ejb.Local pour définir l'interface:

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

L'utilisation de@Local signifie que le bean est accessible dans la même application. Nous avons également la possibilité d'utiliser l'annotationjavax.ejb.Remote qui nous permet d'appeler l'EJB à distance.

Nous allons maintenant définir la classe de bean EJB d'implémentation. Nous marquons la classe comme un bean de session singleton en utilisant l'annotation javax.ejb.Singleton.

De plus, marquons également le bean avec l'annotation javax.ejb.Startup pour informer le conteneur EJB d'initialiser le bean au démarrage:

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

Ceci s'appelle une initialisation rapide. Si nous n'utilisons pas@Startup, le conteneur EJB détermine quand initialiser le bean.

Nous pouvons également définir plusieurs Session Beans pour initialiser les données et charger les beans dans l’ordre spécifique. Par conséquent, nous utiliserons l'annotationjavax.ejb.DependsOn pour définir la dépendance de notre bean vis-à-vis des autres Session Beans.

La valeur de l'annotation@DependsOn est un tableau des noms des noms de classe Bean dont dépend notre Bean:

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

Nous allons définir une méthodeinitialize() qui initialise le bean et en fait une méthode de rappel du cycle de vie en utilisant l'annotationjavax.annotation.PostConstruct.

Avec cette annotation, il sera appelé par le conteneur lors de l'instanciation du bean:

@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. Simultanéité

Ensuite, nous allons concevoir la gestion de la concurrence de Singleton Session Bean. EJB fournit deux méthodes pour implémenter un accès simultané au Singleton Session Bean: accès simultané géré par le conteneur et accès concurrentiel géré par Bean.

L'annotationjavax.ejb.ConcurrencyManagement définit la politique de concurrence pour une méthode. Par défaut, le conteneur EJB utilise la simultanéité gérée par le conteneur.

L'annotation@ConcurrencyManagement prend une valeurjavax.ejb.ConcurrencyManagementType. Les options sont:

  • ConcurrencyManagementType.CONTAINER pour la concurrence gérée par le conteneur.

  • ConcurrencyManagementType.BEAN pour la concurrence gérée par bean.

5.1. Accès concurrentiel géré par conteneur

En termes simples, dans la simultanéité gérée par le conteneur, le conteneur contrôle la manière dont les clients accèdent aux méthodes.

Utilisons l'annotation@ConcurrencyManagement avec la valeurjavax.ejb.ConcurrencyManagementType.CONTAINER:

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

Pour spécifier le niveau d'accès à chacune des méthodes métier du singleton, nous utiliserons l'annotationjavax.ejb.Lock. javax.ejb.LockType contient les valeurs de l'annotation@Lock. javax.ejb.LockType définit deux valeurs:

  • LockType.WRITE - Cette valeur fournit un verrou exclusif au client appelant et empêche tous les autres clients d'accéder à toutes les méthodes du bean. Utilisez cette option pour les méthodes qui modifient l'état du bean singleton.

  • *LockType.READ* – Cette valeur fournit des verrous simultanés à plusieurs clients pour accéder à une méthode. Utilisez cette option pour les méthodes qui lisent uniquement les données du bean.

Dans cet esprit, nous allons définir la méthodesetStates() avec l'annotation@Lock(LockType.WRITE), pour empêcher la mise à jour simultanée de l'état par les clients.

Pour permettre aux clients de lire les données simultanément, nous annotonsgetStates() avec@Lock(LockType.READ):

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

Pour arrêter l'exécution des méthodes pendant une longue période et bloquer les autres clients indéfiniment, nous utiliserons l'annotationjavax.ejb.AccessTimeout pour expirer les appels en attente de longue durée.

Utilisez l'annotation@AccessTimeout pour définir le nombre de millisecondes d'expiration de la méthode. Après l'expiration du délai, le conteneur lève unjavax.ejb.ConcurrentAccessTimeoutException et l'exécution de la méthode se termine.

5.2. Concurrence gérée par Bean

Dans la concurrence gérée par Bean, le conteneur ne contrôle pas l'accès simultané à Singleton Session Bean par les clients. Le développeur est tenu de mettre en œuvre la simultanéité par lui-même.

À moins que le développeur n'implémente la simultanéité, toutes les méthodes sont accessibles simultanément à tous les clients. Java fournit les primitivessynchronization etvolatile pour l'implémentation de la concurrence.

Pour en savoir plus sur la concurrence, lisez à propos dejava.util.concurrenthere et des variables atomiqueshere.

Pour la concurrence gérée par le bean, définissons l'annotation@ConcurrencyManagement avec la valeurjavax.ejb.ConcurrencyManagementType.BEAN pour la classe Singleton Session Bean:

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

Ensuite, nous allons écrire la méthodesetStates() qui change l'état du bean en utilisant le mot clésynchronized:

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

Le mot clésynchronized rend la méthode accessible par un seul thread à la fois.

La méthodegetStates() ne change pas l’état du Bean et n’a donc pas besoin d’utiliser le mot-clésynchronized.

6. Client

Nous pouvons maintenant écrire au client pour accéder à notre Singleton Session Bean.

Nous pouvons déployer le Session Bean sur des serveurs de conteneur d'applications tels que JBoss, Glassfish, etc. Pour simplifier les choses, nous utiliserons la classe javax.ejb.embedded.EJBContainer. EJBContainer s'exécute dans la même JVM que le client et fournit la plupart des services d'un conteneur de bean entreprise.

Tout d'abord, nous allons créer une instance deEJBContainer. Cette instance de conteneur recherchera et initialisera tous les modules EJB présents dans le classpath:

public class CountryStateCacheBeanTest {

    private EJBContainer ejbContainer = null;

    private Context context = null;

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

Ensuite, nous obtiendrons l'objetjavax.naming.Context à partir de l'objet conteneur initialisé. En utilisant l'instanceContext, nous pouvons obtenir la référence àCountryStateContainerManagedBean et appeler les méthodes:

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

De même, nous pouvons utiliser l'instanceContext pour obtenir la référence pour Bean-Managed Singleton Bean et appeler les méthodes respectives:

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

Terminez nos tests en fermant lesEJBContainer dans la méthodeclose():

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

7. Conclusion

Les Session Beans Singleton sont tout aussi flexibles et puissants que n'importe quel Session Bean standard, mais nous permettent d'appliquer un modèle Singleton pour partager l'état entre les clients de notre application.

La gestion de la simultanéité du bean Singleton peut être facilement mise en œuvre à l'aide de la simultanéité gérée par le conteneur, le conteneur prenant en charge l'accès simultané par plusieurs clients. Vous pouvez également implémenter votre propre gestion de la concurrence simultanée à l'aide de la simultanéité gérée par le bean.

Le code source de ce tutoriel peut être trouvéover on GitHub.