Duas das críticas mais comuns dos beans sem estado de sessão foram a sobrecarga dos pools e a falta de compartilhamento de estados via campos estáticos. Os beans de sessão Singletons tentam prover uma solução para ambos os conceitos através de uma instância única de um bean compartilhado que pode ser acessada concorrentemente e ser usada como um mecanismo para estado compartilhado. Os recursos gerenciados pelo servidor, como contexto de persistência, se comportam da mesma como se fossem parte de um bean sem estado de sessão. Mas as similaridades terminam por aqui visto que os beans de sessão Singleton têm todo um ciclo de vida diferente do que os beans sem estado de sessão e possuem uma complexidade adicional de controle de concorrência por parte do desenvolvedor para sincronização.

Ao contrário de outros beans de sessão, o Singleton pode ser criado durante a inicialização do aplicativo e existe até o aplicativo ser fechado. Uma vez que ele tenha sido criado, ele continuará existindo até que o container o remova, independentemente de quaisquer exceções que ocorrem durante a execução do método de negócio. Esta é a diferença chave dos outros tipos de beans de sessão porque a instância do bean nunca será recriada no evento de uma exceção de sistema. A longa vida e as instâncias compartilhadas do bean de sessão Singleton fazem este o lugar ideal para armazenar o estado comum da aplicação, seja apenas leitura ou leitura e escrita.

Definindo um Bean de Sessão Singleton

Os beans de sessão Singleton são definidos usando a anotação @Singleton. Esses beans podem incluir interfaces de negócio local ou usar uma visão sem interface. O exemplo abaixo demonstra um simples bean de sessão Singleton com uma visão sem interface que devolve informações sobre visitas de usuário em uma página da web.

Listagem 1: Implementando um bean de sessão Singleton

@Singleton
public class HitCounter {
	int count;
	public void increment() { ++count; }
	public void getCount() { return count; }
	public void reset() { count = 0; }
}

Aqui se observa que não há a anotação @Remove como nos beans com estado de sessão. Por padrão o container gerenciará a sincronização dos métodos de negócio para assegurar que a informação não seja corrompida. Isso significa que todo acesso ao bean é serializado para que apenas um cliente esteja invocando um método de negócio na instância a qualquer momento.

O ciclo de vida do bean de sessão Singleton está ligado ao ciclo de vida da aplicação geral. O container determina o ponto quando a instância do Singleton será criada, a não ser que o bean inclua a anotação @Startup para forçar uma inicialização quando a aplicação inicia.

Quando múltiplos beans de sessão Singleton dependem um do outro, o container precisa ser informado da ordem na qual eles deverão ser instanciados. Isto é feito através da anotação @DependsOn na própria classe do bean, na qual lista-se os nomes dos outros bean de sessão Singleton que devem ser criados primeiro.

Ciclo de Vida

O ciclo de vida do bean de sessão Singleton é o mesmo que os bean sem estado de sessão, ou seja, também possui PostConstruct e PreDestroy. O container invocará PostConstruct depois que o servidor inicializar a instância do bean e por outro lado invoca PreDestroy antes de liberar a instância do bean. A diferença é que PreDestroy será chamado apenas quando a aplicação fechar. Portanto, será chamado apenas uma vez, ao passo que as instância do bean sem estado de sessão (stateless) são chamados frequentemente.

Concorrência

Beans de sessão Singleton podem usar concorrência gerenciada por container ou gerenciada por bean. Por padrão eles são gerenciados por container, na qual corresponde a colocar um bloqueio do tipo WRITE em todos os métodos de negócio. Todas as invocações a métodos de negócio são serializadas para que apenas um cliente possa acessar o bean a qualquer dado momento.

Claro que nem todos os métodos de negócio alteram o estado de um bean. Se não há perigo de corromper o estado do bean através de um acesso concorrente, a anotação @Lock(LockType.READ) pode ser usada para declarar que tal acesso é seguro e assim coloca-se um bloqueio de leitura no método. Quando a concorrência gerenciada pelo container está habilitada, desenvolvedores devem sempre usar a anotação @Lock para controlar o acesso e evitar primitivas java como a palavra-chave synchronized.

Embora os bloqueios sejam declarados nos métodos de negócio, conceitualmente o bean pode ser pensado como tendo um bloqueio único na instância. Métodos de negócio irão adquirir ou acesso de leitura ou de escrita dependendo da declaração @Lock ou então do valor default setado. Múltiplos leitores podem proceder concorrentemente, mas quando um bloqueio de escrita é adquirido, todos os outros clientes são bloqueados até que a operação de escrita seja completada.

Listagem 2: Bean de sessão Singleton com bloqueio explícito

@Singleton
public class HitCounter {
	int count;

	public void increment() { ++count; }

	@Lock(LockType.READ)
	public void getCount() { return count; }
	public void reset() { count = 0; }
}

No código acima sobrescrevemos o bloqueio padrão de um método de negócio, o método getCount() marcado com @Lock(LockType.READ) indicando que múltiplos clientes podem executar o método concorrentemente de forma segura. Os métodos remanescentes estão ainda com o bloqueio padrão que é @Lock(LockType.WRITE), e o container irá assegurar que a invocação a métodos de leitura e escrita são mutuamente exclusivas.

Se um desenvolvedor deseja ter controle sobre a concorrência do bean e tirar qualquer gerenciamento por parte do container usa-se a anotação @ConcurrencyManagement(ConcurrencyManagementType.BEAN) na classe do bean. Assim, a anotação @Lock não terá nenhum efeito e o desenvolvedor pode usar as primitivas de concorrência do Java para garantir a segurança da informação.

Existe um grande número de casos em que é preferível utilizar concorrência gerenciado por bean do que a concorrência gerenciada por container. Se o bean de sessão Singleton não tem estado, ou se os estados das operações são restritos para um pequeno conjunto de métodos, a concorrência gerenciada por bean terá um desempenho melhor.

Quando a concorrência gerenciada por container está habilitada, todos os métodos de negócio envolvem um bloqueio de algum tipo, se operações de estado estão envolvidas ou não.

Conclusão

Vimos neste artigo o que são Beans de sessão Singletons, como implementar um bean de sessão Singleton, como se dá o seu ciclo de vida, quais são os seus tipos. Além disso, verificamos a importância de cuidarmos com a concorrência em beans de sessão Singleton e as diferentes formas de tratá-las.

Referências