Bean de sessão Singleton no Java EE

Bean de sessão Singleton no Java EE

1. Visão geral

Sempre que uma única instância de um Bean de sessão é necessária para um determinado caso de uso, podemos usar um Bean de sessão Singleton.

Neste tutorial, vamos explorar isso por meio de um exemplo, com um aplicativo Java EE.

2. Maven

Em primeiro lugar, precisamos definir as dependências necessárias do Maven empom.xml.

Vamos definir as dependências para as APIs EJB e o contêiner EJB incorporado para implantação do EJB:


    javax
    javaee-api
    8.0
    provided



    org.apache.openejb
    tomee-embedded
    1.7.5

As versões mais recentes podem ser encontradas no Maven Central emJavaEE APIetomEE.

3. Tipos de Session Beans

Existem três tipos de Session Beans. Antes de explorarmos os Singleton Session Beans, vamos ver qual é a diferença entre os ciclos de vida dos três tipos.

3.1. Stateful Session Beans

Um Bean de Sessão com Estado mantém o estado conversacional com o cliente que está se comunicando.

Cada cliente cria uma nova instância do Stateful Bean e não é compartilhado com outros clientes.

Quando a comunicação entre o cliente e o bean termina, o Bean de Sessão também termina.

3.2. Beans de sessão sem estado

Um Bean de sessão sem estado não mantém nenhum estado de conversação com o cliente. O bean contém o estado específico do cliente apenas até a duração da chamada do método.

As invocações consecutivas de métodos são independentes, diferentemente do Stateful Session Bean.

O contêiner mantém um conjunto de Stateless Beans e essas instâncias podem ser compartilhadas entre vários clientes.

3.3. Feijões de Sessão Singleton

Um Bean de sessão Singleton mantém o estado do bean durante todo o ciclo de vida do aplicativo.

Os Beans de sessão Singleton são semelhantes aos Beans de sessão sem estado, mas apenas uma instância do Bean de sessão Singleton é criada em todo o aplicativo e não termina até que o aplicativo seja encerrado.

A instância única do bean é compartilhada entre vários clientes e pode ser acessada simultaneamente.

4. Criando um Singleton Session Bean

Vamos começar criando uma interface para ele.

Para este exemplo, vamos usar a anotaçãojavax.ejb.Local para definir a interface:

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

Usar@Local significa que o bean é acessado dentro do mesmo aplicativo. Também temos a opção de usar a anotaçãojavax.ejb.Remote que nos permite chamar o EJB remotamente.

Agora, vamos definir a classe do bean EJB de implementação. Marcamos a classe como um Singleton Session Bean usando a anotação javax.ejb.Singleton.

Além disso, também vamos marcar o bean com a anotação javax.ejb.Startup para informar ao contêiner EJB para inicializar o bean na inicialização:

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

Isso é chamado de inicialização ansiosa. Se não usarmos@Startup, o contêiner EJB determina quando inicializar o bean.

Também podemos definir vários Session Beans para inicializar os dados e carregar os beans na ordem específica. Portanto, usaremos a anotaçãojavax.ejb.DependsOn para definir a dependência de nosso bean em outros Session Beans.

O valor da anotação@DependsOn é uma matriz dos nomes das classes Bean dos quais nosso Bean depende:

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

Definiremos um métodoinitialize() que inicializa o bean e o torna um método de retorno de chamada do ciclo de vida usando a anotaçãojavax.annotation.PostConstruct.

Com esta anotação, ele será chamado pelo contêiner na instanciação do 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. Concorrência

A seguir, projetaremos o gerenciamento de simultaneidade do Singleton Session Bean. O EJB fornece dois métodos para implementar o acesso simultâneo ao Bean de Sessão Singleton: simultaneidade gerenciada por contêiner e simultaneidade gerenciada por Bean.

A anotaçãojavax.ejb.ConcurrencyManagement define a política de concorrência para um método. Por padrão, o contêiner EJB usa simultaneidade gerenciada por contêiner.

A anotação@ConcurrencyManagement assume um valorjavax.ejb.ConcurrencyManagementType. As opções são:

  • ConcurrencyManagementType.CONTAINER para simultaneidade gerenciada por contêiner.

  • ConcurrencyManagementType.BEAN para simultaneidade gerenciada por bean.

5.1. Concorrência gerenciada por contêiner

Simplificando, na simultaneidade gerenciada por contêiner, o contêiner controla como o acesso dos clientes aos métodos.

Vamos usar a anotação@ConcurrencyManagement com o valorjavax.ejb.ConcurrencyManagementType.CONTAINER:

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

Para especificar o nível de acesso para cada um dos métodos de negócios do singleton, usaremos a anotaçãojavax.ejb.Lock. javax.ejb.LockType contém os valores para a anotação@Lock. javax.ejb.LockType define dois valores:

  • LockType.WRITE - Este valor fornece um bloqueio exclusivo para o cliente de chamada e evita que todos os outros clientes acessem todos os métodos do bean. Use isso para métodos que alteram o estado do bean singleton.

  • *LockType.READ* – Este valor fornece bloqueios simultâneos para vários clientes para acessar um método. Use isso para métodos que apenas lêem dados do bean.

Com isso em mente, vamos definir o métodosetStates() com a anotação@Lock(LockType.WRITE), para evitar a atualização simultânea do estado pelos clientes.

Para permitir que os clientes leiam os dados simultaneamente, anotaremosgetStates() com@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);
    }
}

Para interromper a execução dos métodos por um longo tempo e bloquear os outros clientes indefinidamente, usaremos a anotaçãojavax.ejb.AccessTimeout para encerrar as chamadas de longa espera.

Use a anotação@AccessTimeout para definir o número de tempos limite do método em milissegundos. Após o tempo limite, o contêiner lança umjavax.ejb.ConcurrentAccessTimeoutExceptione a execução do método termina.

5.2. Simultaneidade Bean-Managed

Na simultaneidade gerenciada do Bean, o contêiner não controla o acesso simultâneo do Singleton Session Bean pelos clientes. É necessário que o desenvolvedor implemente a simultaneidade.

A menos que a simultaneidade seja implementada pelo desenvolvedor, todos os métodos são acessíveis a todos os clientes simultaneamente. Java fornece os primitivossynchronizationevolatile para implementar a simultaneidade.

Para saber mais sobre simultaneidade, leia sobrejava.util.concurrentheree Variáveis ​​Atômicashere.

Para simultaneidade gerenciada por bean, vamos definir a anotação@ConcurrencyManagement com o valorjavax.ejb.ConcurrencyManagementType.BEAN para a classe Singleton Session Bean:

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

A seguir, vamos escrever o métodosetStates() que muda o estado do bean usando a palavra-chavesynchronized:

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

A palavra-chavesynchronized torna o método acessível por apenas um segmento de cada vez.

O métodogetStates() não altera o estado do Bean e, portanto, não precisa usar a palavra-chavesynchronized.

6. Cliente

Agora podemos escrever o cliente para acessar nosso Bean de Sessão Singleton.

Podemos implantar o Session Bean em servidores de contêiner de aplicativos como JBoss, Glassfish etc. Para manter as coisas simples, usaremos a classe javax.ejb.embedded.EJBContainer. EJBContainer é executado na mesma JVM que o cliente e fornece a maioria dos serviços de um contêiner de bean corporativo.

Primeiro, vamos criar uma instância deEJBContainer. Esta instância do contêiner procurará e inicializará todos os módulos EJB presentes no caminho de classe:

public class CountryStateCacheBeanTest {

    private EJBContainer ejbContainer = null;

    private Context context = null;

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

Em seguida, obteremos o objetojavax.naming.Context do objeto recipiente inicializado. Usando a instânciaContext, podemos obter a referência aCountryStateContainerManagedBeane chamar os métodos:

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

Da mesma forma, podemos usar a instânciaContext para obter a referência para Bean-Managed Singleton Bean e chamar os respectivos métodos:

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

Encerre nossos testes fechando oEJBContainer no métodoclose():

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

7. Conclusão

Beans de sessão singleton são tão flexíveis e poderosos quanto qualquer Bean de sessão padrão, mas nos permitem aplicar um padrão Singleton para compartilhar o estado entre os clientes de nosso aplicativo.

O gerenciamento de simultaneidade do Singleton Bean pode ser facilmente implementado usando a Concorrência gerenciada por contêiner, onde o contêiner cuida do acesso simultâneo de vários clientes, ou você também pode implementar seu próprio gerenciamento de simultaneidade personalizado usando a simultaneidade gerenciada pelo Bean.

O código-fonte deste tutorial pode ser encontradoover on GitHub.