Algumas vezes os clientes precisam emitir múltiplas requisições para um serviço e ter cada requisição capaz de acessar ou considerar os resultados de requisições anteriores. Beans com estado de sessão são projetados para manusear esse tipo de situação provendo um serviço dedicado para um cliente que se inicia quando o cliente obtém uma referência para o bean e finaliza apenas quando o cliente escolhe finalizar a conversação.

Um exemplo dessa situação é um carrinho de um shopping numa aplicação e-commerce. O cliente obtém uma referência para o carrinho, iniciando uma conversação. Enquanto isso o cliente adiciona e remove itens do carrinho, o que mantém um estado específico para o cliente. Então, quando a sessão está completa, o cliente completa a compra, causando a remoção do carrinho.

No restante do artigo trataremos dos beans com estado de sessão, muito utilizados na maioria das aplicações corporativas.

Definindo um Bean com estado de Sessão

Um Bean com estado de sessão é composto de uma ou mais interfaces de negócio implementadas por uma classe. Um exemplo de uma interface de negócio local para uma carrinho de compras é demonstrado abaixo:

Listagem 1: Interface de Negócio para um carrinho de compras

public interface CarrinhoDeCompras {
	public void adicionaItem(String id, int quantidade);
	public void removerItem(String id, int quantidade);
	public Map<String,Integer> getItems();
	public void cancelar();
}

Abaixo segue a implementação para a interface definida acima. A anotação @Stateful indica para o servidor que a classe é um bean com estado de sessão.

Listagem 2: Implementação da Interface de Negócio acima

@Stateful
public class CarrinhoDeComprasBean implements CarrinhoDeCompras {
	private HashMap<String,Integer> items = new HashMap<String,Integer>();

	public void adicionaItem(String item, int quantidade) {
		Integer quantidadeTotal = items.get(item);

		if (quantidadeTotal == null) {
			quantidadeTotal = 0;
		}
		quantidadeTotal += quantidade;
		items.put(item, quantidadeTotal);
	}

	// ...

	@Remove
	public void cancel() {
	}
}

As anotações marcadas com @Remove indicam que esses métodos são usados pelo cliente para finalizar uma conversação com o bean. Depois que um desses métodos são chamados, o servidor irá destruir a instância do bean, e a referência do cliente irá lançar uma exceção se qualquer tentativa a mais for feita para invocar métodos de negócio. Todo bean com estado de sessão deve definir pelo menos um método marcado com a anotação @Remove, mesmo se o método não fizer qualquer outra coisa do que servir como um finalizador para a conversação.

Ciclo de vida

Chamadas a métodos do ciclo de vida dos bean com estado de sessão são suportados para facilitar a inicialização e liberação de recursos. Entre eles temos o Passivation na qual o servidor serializa a instância do bean para que ele possa ou ser armazenado offline para liberar recursos ou replicado em outro servidor num cluster. Activation é o processo de desserializar uma instancia de um bean de sessão passivada e fazer esta instância ativa no servidor novamente mais uma vez. Devido o bean com estado de sessão segurar o estado de um cliente e não remover este estado até o cliente invocar um dos métodos de Remove no bean, o servidor não pode destruir uma instância do bean para liberar recursos.

A Passivação permite ao servidor recuperar recursos enquanto preserva o estado da sessão. Antes que um bean seja passivado, o servidor invocará o método do ciclo de vida PrePassivate. O bean usa esse método para preparar-se para a serialização, frequentemente para fechar qualquer conexão viva para outros recursos do servidor. PrePassivate é identificado pela anotação @PrePassivate. Depois que o bean é ativado, o servidor invocará PostActivate. Com a instância serializada restaurada, o bean deve então readquirir qualquer conexão com outros recursos que os métodos de negócio do bean poderiam estar dependendo. O método PostActivate é identificado pela anotação @PostActivate.

O exemplo abaixo mostra um bean de sessão usando os métodos do ciclo de vida para manter uma conexão com um JDBC. O servidor salva e restaura automaticamente o data source durante a passivação e a ativação.

Listagem 3: Usando métodos do ciclo de vida para Beans com Estado de Sessão.

@Stateful
public class TesteBean implements TesteInterface {
	DataSource ds;
	Connection conn;
	
	@PostConstruct
	public void init() {
		// adquire o data source
		// ...
		adquireConexao();
	}

	@PrePassivate
	public void passivate() { liberarConexao (); }

	@PostActivate
	public void activate() { adquireConexao(); }
	
	@PreDestroy
	public void shutdown() { liberarConexao(); }
		private void adquireConexao() {
		
		try {
			conn = ds.getConnection();
		} catch (SQLException e) {
			throw new EJBException(e);
		}

	}

	private void liberarConexao() {
		try {
			conn.close();
		} catch (SQLException e) {
		}

		conn = null;
	}

	public Collection<Order> listarPedidos() {
		// ...
	}
}

Interface de negócio remota

Até o momento discutimos apenas beans de sessão que usam interfaces de negócio local. Local neste caso significa que uma dependência no bean de sessão pode ser declarado apenas pelo componente Java EE que esta executando na mesma instância do servidor de aplicação. Não é possível usar um bean de sessão com uma interface local através de um cliente remoto, por exemplo.

Para acomodar clientes remotos, beans de sessão podem marcar suas interfaces de negócio com a anotação @Remote para declarar que isto deveria ser remotamente usável. O código abaixo demonstra um exemplo de uma interface remota para a interface HelloService. Declarar a interface como remota é o mesmo que estender a interface java.rmi.Remote.

Listagem 4: Uma Interface de Negócio Remota

@Remote
public interface HelloServiceRemote {
	public String sayHello(String name);
}

Fazer uma interface remota tem consequências principalmente em termos de performance. Interfaces de negócio remotas podem ser usadas localmente dentro de um servidor em execução, mas fazendo isso poderia ainda resultar em sobrecarga na rede se a chamada de método é roteada através de uma camada RMI. Outro problema são os argumentos dos métodos. Argumentos para métodos em interfaces remotas são passadas por valor ao invés de passadas por referência. Isto significa que o argumento é serializado mesmo quando o cliente é local para o bean de sessão. Interfaces locais para clientes locais são geralmente melhores. Interfaces locais preservam a semântica de chamadas a métodos e evita custos associados com rede e RMI.

Conclusão

Vimos neste artigo o que é são Beans com estado de sessão, como podemos definir um bean com estado de sessão e como se dá todo o seu ciclo de vida. EJB (a partir da versão 3) e os beans de sessão são muito utilizados em aplicações corporativas Java e fornecem diversos benefícios para as aplicações conforme pode ser verificado no artigo. Além disso, vimos as vantagens da utilização de interfaces locais em relação às interfaces remotas.

Referências

Leia também: