EJB: Introdução ao novo Enterprise JavaBeans 3.2

Veja neste artigo uma introdução ao EJB 3.2, a nova especificação do EJB que já está sendo adotada em diversos projetos empresariais. Veremos as principais funcionalidades, melhorias e adições realizadas.

O Enterprise JavaBeans (EJB) está definido na JSR 345, e a especificação completa pode ser baixada através do site http://download.oracle.com/otndocs/jcp/ejb-3_2-fr-spec/index.html.

Enterprise JavaBeans são usados para o desenvolvimento e implantação de aplicações distribuídas baseadas em componentes que são escaláveis, transacionais, e seguros. Um EJB normalmente contém a lógica de negócio que atua sobre os dados de negócio. Essa afirmação não era verdadeira até a introdução dos POJOs que também estão disponíveis nessa nova versão do EJB.

O EJB 3.2 também permite que toda parte envolvendo segurança e transação possa ser especificados na forma de anotações de metadados, ou então separadamente no Deployment Descriptor que é o arquivo web.xml.

DevMedia Cursos Online de Java: O maior acervo digital de cursos online sobre Java

Uma instância de um bean é gerenciado em tempo de execução por um container. O bean é acessado no cliente e é intermediado pelo container que ele está implantado. O cliente deste bean também pode estar no servidor na forma de um Manage Bean, bean CDI, ou um Servlet. Em qualquer um dos casos, o container EJB fornece toda infra-estrutura necessária para essas aplicações empresariais. O container permite que o desenvolvedor de aplicações foque na lógica de negócio e não se preocupe com transações de baixo nível, detalhes de gerenciamento de status, comunicação remota, concorrência, multithreading, pools de conexão ou outras APIs complexas de baixo nível.

Existem dois tipos de beans, são eles: os Session beans e Message-driven beans. Eles formam o coração do EJB e serão mais detalhados no restante do artigo, assim como diversas outras funcionalidade providas pelo EJB 3.2.

Entity beans foram marcados como “anulados” na versão anterior (EJB 3.1) da especificação do EJB e agora está como opcional no EJB 3.2. Isso ocorre porque a Java Persistence API ganhou a sua própria especificação e pode ser executada fora de um container.

Beans com Estado de Sessão (Stateful Session Beans)

Um bean com estado de sessão (stateful session bean) tem como característica manter o estado conversacional para um cliente específico. O estado é armazenado nos valores das variáveis de instância do bean e nos interceptadores associados.

Podemos definir um simples bean com estado de sessão usando a anotação @Stateful. Segue na Listagem 1 um exemplo de como podemos definir um bean com estado de sessão.

Listagem 1. Exemplo de um bean com estado de sessão utilizando anotações.

package exemplo; @Stateful public class CarrinhoDeCompras { List<String> itens; public CarrinhoDeCompras () { itens = new ArrayList<>(); } public void adicionarItem(String item) { itens.add(item); } public void removerItem(String item) { itens.remove(item); } @Remove public void remover() { itens = null; } }

Acima temos um POJO marcado com a anotação @Stateful. Apenas anotando o nosso POJO já é o suficiente para convertermos um POJO para um bean com estado de sessão. Lembrando que os POJOs (Plain Old Java Objects ou Os Singelos Clássicos Objetos Java) são objetos Java que seguem um projeto simplificado, sem definições rígidas de estrutura como, por exemplo, obrigar o programador a implementar construtores com ou sem argumentos, definir métodos getters e setters para cada atributo, convenções para nomear métodos, etc.

Todos os métodos públicos do bean exemplificados acima podem ser invocados por um cliente.

No código acima também verificamos que o método "remover" é anotado com a anotação @Remove. Um cliente pode remover um bean com estado de sessão apenas invocando o método "remover". Chamando este método temos como resultado uma chamado do container ao método com a anotação @PreDestroy. Remover um bean com estado de sessão significa que o estado da instância específica para aquele cliente não existe mais.

Este estilo de declaração de bean é chamado de visualização sem interface. Esse bean é apenas localmente acessível aos clientes empacotados no mesmo arquivo de deploy (como um war). Se um bean precisa ser remotamente acessível, ele deve definir uma interface de negócio separada anotada com @Remote. Segue na Listagem 2 um exemplo de definição de uma interface remota.

Listagem 2. Criando uma interface remota para o bean com estado de sessão criado anteriormente.

@Remote public interface CarrinhoDeCompras { public void adicionarItem(String item); public void removerItem(String item); public void remover(); }

Agora o bean pode ser injetado através da sua interface, conforme mostra a Listagem 3.

Listagem 3. Injetando o bean através da interface.

@Inject CarrinhoDeCompras carrinhoDeCompras;

Um cliente deste bean com estado de sessão pode acessar este bean da mesma forma que aparece na Listagem 4.

Listagem 4. Exemplo de como o cliente pode acessar o bean.

@Inject CarrinhoDeCompras carrinhoDeCompras; carrinhoDeCompras.adicionaItem("Pen Drive"); carrinhoDeCompras.adicionaItem("Impressora"); carrinhoDeCompras.adicionaItem("HD Externo");

Podemos notar o quanto a injeção de dependência facilita a vida dos programadores que utilizam o EJB 3.2. A injeção de dependência é um padrão de projeto que visa desacoplar os componentes da aplicação. Dessa forma, os componentes são instanciados externamente à classe com um gerenciador controlando essas instâncias. Esse gerenciador, através de uma configuração, liga os componentes de forma a montar a aplicação. Isso facilita muito a vida dos desenvolvedores que não precisam instanciar e nem configurar esses beans, basta injetá-lo na classe.

O EJB 3.2 tornou menos rígida as regras default para designar a implementação de interfaces como local ou remota. A classe do bean deve implementar a interface ou a interface deve ser designada como uma interface de negócio local ou remota por meio da anotação @Local ou @Remote ou ainda podemos utilizar o deployment descriptor.

Se o bean está implementando duas interfaces que não possuem anotações, então eles estarão expostos como visualização local do bean. Segue na Listagem 5 um exemplo.

Listagem 5. Bean implementando duas interfaces sem anotações, tornando-as assim como de acesso local.

@Stateless public class CarrinhoDeComprasBean implements CarrinhoDeCompras, Pagamento { //. . . }

Neste código, se CarrinhoDeCompras e Pagamento não possuem anotações explicitas indicando se é local ou remota, então eles estão expostos como visualização local do bean. Porém, os beans também podem ser explicitamente marcados com @Local, conforme mostra o exemplo da Listagem 6.

Listagem 6. Definindo de forma explicita o bean como local.

@Local @Stateless public class CarrinhoDeComprasBean implements CarrinhoDeCompras, Pagamento { //. . . }

Os dois fragmentos de código são semanticamente equivalentes.

Também podemos marcar o bean como remoto através da anotação @Remote, conforme o exemplo a Listagem 7.

Listagem 7. Definindo o bean como remoto através da anotação @Remote.

@Remote @Stateless public class CarrinhoDeComprasBean implements CarrinhoDeCompras, Pagamento { //. . . }

Dessa forma, CarrinhoDeCompras e Pagamento são visualizações remotas.

Se uma das interfaces é marcada como @Local ou @Remote, então cada interface que precisar ser exposta deve ser marcada explicitamente; caso contrário, ele será ignorado. Segue na Listagem 8 um exemplo onde definimos a anotação em apenas uma das implementações.

Listagem 8. Exemplo definindo uma interface anotada.

@Remote public interface CarrinhoDeCompras { //. . . } @Stateless public class CarrinhoDeComprasBean implements Pagamento, CarrinhoDeCompras { //. . . }

Neste código o Bean apenas expõe uma interface remota que é "CarrinhoDeCompras".

Os beans com estado de sessão também possuem acesso aos métodos de callback do ciclo de vida, são eles: "PostConstruct" e "PreDestroy". Além disso, um container EJB pode decidir por passivar um bean com estado de sessão para alguma forma de armazenamento secundário e então ativa-lo novamente. O container se encarrega de salvar e restaurar o estado do bean. No entanto, se há objetos que não são serializáveis como soquetes abertos ou conexões JDBC, eles precisam ser explicitamente fechados e restaurados como parte desse processo. Assim, @PrePassivate é invocado para limpar os recursos antes do bean ser passivado, e @PostActivate que é invocado para restaurar os recursos.

Porém, uma das novidades do EJB 3.2 é a capacidade de optar por não passivação o que pode evitar exceções em tempo de execução e a degradação de desempenho do aplicativo. Segue na Listagem 9 um exemplo de como desabilitar essa funcionalidade.

Listagem 9. Desabilitando a passivação num determinado bean.

@Stateful(passivationCapable=false) public class CarrinhoDeCompras { List<String> itens; //. . . }

Neste código o bean com estado de sessão acima nunca será passivado.

Stateless Session Beans

Um bean sem estado de sessão não contém qualquer estado conversacional para um cliente específico.

Todas as instâncias de um bean sem estado de sessão são equivalentes, portanto o container pode escolher delegar um método invocado por um cliente para qualquer instância disponível no pool do container. Visto que os beans sem estado de sessão não possuem qualquer estado, eles não precisam ser passivados.

Podemos definir um simples bean sem estado de sessão utilizando a anotação @Stateless, conforme exemplificado no código da Listagem 10.

Listagem 10. Exemplo de um bean sem estado de sessão.

package exemplo; @Stateless public class ContaSessionBean { public float debitar(float quantidade) { //. . . } public void depositar(float quantidade) { //. . . } }

O código acima mostra um POJO anotado com @Stateless. Isto é tudo que precisamos para converter um POJO para um bean sem estado de sessão. Todos os métodos públicos do bean podem ser invocados por um cliente.

Podemos acessar este bean sem estado de sessão usando a anotação @EJB, conforme podemos verificar no exemplo da Listagem 11.

Listagem 11. Acessando um bean sem estado de sessão utilizando @EJB.

@EJB ContaSessionBean conta; conta.debitar(150);

Este estilo de declaração de bean também é chamado de uma visualização sem interface, conforme verificamos anteriormente também. Este bean é acessível apenas localmente para os clientes empacotados no mesmo arquivo de deploy. Se o bean precisa ser acessível remotamente, devemos definir uma interface de negócio separado com a anotação @Remote, conforme verificamos na Listagem 12.

Listagem 12. Criando uma interface remota para o bean sem estado de sessão.

@Remote public interface Conta { public float debitar(float quantidade); public void depositar(float quantidade); } @Stateless public class ContaSessionBean implements Conta { float total; public float debitar(float quantidade) { //. . . } public void depositar(float quantidade) { //. . . } }

Por fim, o cliente injeta o bean através da interface, conforme exemplificado na Listagem 13.

Listagem 13. Injetando o bean Conta através da anotação @EJB.

@EJB ContaSessionBean conta;

Os beans sem estado de sessão suportam dois métodos de callback do ciclo de vida, são eles: "PostConstruct" e "PreDestroy".

O método "PostConstruct" é invocado depois que o construtor sem argumentos é invocado e todas as dependências foram injetadas, e antes que o primeiro método de negócio é invocado no bean. Este método é geralmente onde todos os recursos necessários para o bean são inicializados.

O método de callback "PreDestroy" é chamado antes da instância ser removida pelo container. Este método é geralmente onde todos os recursos adquiridos durante o PostConstruct são liberados.

Como os beans sem estado de sessão não armazenam qualquer estado, o container pode fazer um pool de instâncias, e todos eles são tratados da mesma forma a partir de uma perspectiva do cliente. Qualquer instância do bean pode ser usada para atender a solicitação do cliente. Com essa simples afirmação podemos verificar que os beans sem estado de sessão são muito mais rápidos que os beans com estado de sessão.

Singleton Session Beans

Um bean de sessão Singleton é um componente que é instanciado uma única vez por aplicação e fornece um acesso bastante facilitado ao estado compartilhado. Se o container for distribuído em múltiplas JVM, cada aplicação terá uma instância do Singleton para cada JVM. Um bean de sessão Singleton é explicitamente projetado para ser compartilhado e suportar concorrência.

Podemos definir um simples bean de sessão Singleton usando a anotação @Singleton, conforme podemos verificar no exemplo da Listagem 14.

Listagem 14. Exemplo de um bean de sessão Singleton utilizando a anotação @Singleton

@Singleton public class MeuSingleton { //. . . }

O Container é responsável por quando inicializar uma instância de um bean de sessão Singleton. Contudo, podemos opcionalmente marcar o bean para inicializar

antes através da anotação @Startup, conforme exemplificado na Listagem 15.

Listagem 15. Exemplo de como inicializar um Singleton antes da aplicação estar disponível.

@Startup @Singleton public class MeuSingleton { //. . . }

O Container agora inicializará todos esses Singletons em tempo de inicialização, executando o código que estiver anotado com @PostConstruct, antes da aplicação tornar-se disponível e antes que qualquer solicitação do cliente seja atendida.

Podemos especificar uma inicialização explicita de beans de sessão Singleton usando @DependsOn. O exemplo da Listagem 16 ilustra melhor como podemos utilizar essa funcionalidade:

Listagem 16. Exemplo de como utilizar a anotação @DependsOn para criar dependências entre os beans.

@Singleton public class DesligaAlarme { //. . . } @DependsOn("DesligaAlarme ") @Singleton public class AbrePortaoGaragem { //. . . }

O container assegura que o bean DesligaAlarme é inicializado antes do bean AbrePortaoGaragem.

Um bean Singleton suporta os métodos de callback do ciclo de vida "PostConstruct" e "PreDestroy". Além disso, o Singleton também suporta acesso concorrente.

Por padrão, um bean Singleton é marcado para ter concorrência gerenciada pelo container, mas opcionalmente pode ser marcado para ter concorrência gerenciada por bean.

Concorrência gerenciada por Container é baseada em metadados com bloqueio a nível de método onde cada método é associado com um bloqueio do tipo Read (compartilhado) ou Write (exclusivo).

Um bloqueio de leitura (Read) permite chamadas simultâneas do método. Um bloqueio de leitura (Write) aguarda o processamento de uma invocação completar antes de permitir que a próxima invocação prossiga.

As anotações @Lock(LockType.READ) e @Lock(LockType.WRITE) são utilizadas para especificar o tipo de bloqueio. Mas, por padrão, um bloqueio Write é associado com cada método do bean.

Essas anotações podem ser especificadas na classe, um método de negócio da classe, ou ambos. Um valor especificado no método sobrescreve um valor especificado na classe. A concorrência gerenciada por bean requer que o desenvolvedor gerencie a concorrência utilizando primitivas de sincronização da linguagem Java como “synchronized” e “volatile”.

Eventos do Ciclo de Vida

Métodos interceptadores do ciclo de vida podem ser definidos tanto para beans de sessão quanto para beans de mensagens.

As anotações @AroundConstruct, @PostConstruct, @PreDestroy, @PostActivate, e @PrePassivate são utilizados para definir métodos interceptadores para eventos do ciclo de vida dos beans.

A anotação @AroundConstruct pode ser definida apenas numa classe interceptadora, enquanto que todas as outras anotações podem ser definidos em uma classe interceptadora e/ou diretamente numa classe bean.

Nas próximas seções veremos melhor cada uma dessas anotações.

Interceptador @AroundConstruct

A anotação @AroundConstruct designa um método interceptor que recebe uma chamada de retorno quando o construtor da classe de destino é invocado.

Este método interceptor pode ser definido apenas em classes de interceptoras e/ou superclasses de classes de interceptores e não pode ser definido na classe alvo.

Um interceptador pode ser definido como o da Listagem 17.

Listagem 17. Definindo um AroundConstructor.

@Inherited @InterceptorBinding @Retention(RUNTIME) @Target({CONSTRUCTOR, METHOD, TYPE}) public @interface MeuAroundConstruct { }

Um interceptador vinculado (Interceptor binding) pode ser definido como a Listagem 18.

Listagem 18. Exemplo de como definir um interceptor vinculado.

@MeuAroundConstruct @Interceptor public class MeuAroundConstructInterceptador { @AroundConstruct public void validateConstructor(InvocationContext context) { //. . . } }

E finalmente, o interceptador pode ser especificado no bean como a Listagem 19.

Listagem 19. Definindo um interceptor no bean.

@MeuAroundConstruct @Stateful public class MeuBean { //. . . }

O método validateConstructor é chamado toda vez que o construtor do bean MeuBean é chamado.

Interceptador @PostConstruct

A anotação @PostConstruct é utilizada em um método que precisa ser executado após a injeção de dependência ser realizada. Assim podemos executar qualquer inicialização, visto que utilizando o PostConstruct temos certeza que o método será executado antes da primeira invocação do método de negócio na instância do bean.

O método anotado com PostConstruct é invocado mesmo se a classe não solicitar quaisquer recursos a serem injetados. Apenas um método pode ser anotado com @PostConstruct. Segue na Listagem 20 um exemplo.

Listagem 20. Anotando um método do bean com @PostConstruct.

@Stateless public class MeuBean { @PostConstruct private void configuraRecursos() { //. . . } //. . . }

Neste código o construtor default do bean é chamado primeiro e após isso o método configuraRecursos() é chamado antes que qualquer método de negócio possa ser chamado.

Este método interceptador do ciclo de vida serve para diferentes tipos de beans de sessão e ocorre nos seguintes contextos de transação:

Interceptador @PreDestroy

A anotação @PreDestroy é utilizada como uma forma de notificação para sinalizar que a instância está no processo de ser removida do container. O método anotado com “PreDestroy” é normalmente utilizado para liberar recursos que tem sido utilizados na aplicação. Segue na Listagem 21 um exemplo de como utilizá-la.

Listagem 21. Anotando um método do bean com @PreDestroy.

@Stateless public class MeuBean { @PreDestroy private void liberaRecursos() { //. . . } //. . . }

Neste código, o método liberaRecursos() é chamado antes que a instância seja removida pelo container.

Este método interceptador é utilizado em diferentes tipos de beans de sessão e ocorre nos seguintes contextos de transação:

Interceptador @PrePassivate

A anotação @PrePassivate pode ser usada apenas em um bean com estado de sessão. Esta anotação designa um método para receber uma chamada de retorno antes de um bean com estado de sessão ser passivado. Segue na Listagem 22 um exemplo.

Listagem 22. Anotando um método do bean com @PrePassivate.

@Stateful public class MeuBeanComEstadoDeSessao { @PrePassivate private void antesPassivacao(InvocationContext context) { //. . . } //. . . }

Esses métodos são ignorados por beans sem estado de sessão ou Singleton.

Este método interceptador executa em um contexto de transação determinado pelo atributo de transação do método de retorno de chamada.

Interceptador @PostActivate

A anotação @PostActivate pode apenas ser usada em um bean com estado de sessão. Esta anotação designa um método para receber um retorno de chamada após um bean com estado de sessão ser ativado. Segue na Listagem 23 um exemplo.

Listagem 23. Anotando um método do bean com @PostActivate.

@Stateful public class MeuBeanComEstadoDeSessao { @PostActivate private void aposAtivacao(InvocationContext context) { //. . . } //. . . }

Esses métodos são ignorados por beans sem estado de sessão e Singleton.

Este método interceptador executa em um contexto de transação determinado pelo atributo de transação do método de retorno de chamada do ciclo de vida.

Métodos interceptadores do ciclo de vida podem lançar exceções em tempo de execução, mas não exceções de aplicação.

Message-Driven Beans

Um Message-Driven Bean (MDB) é um bean gerenciado por container que é usado para processar mensagens de forma assíncrona. Um MDB pode implementar qualquer tipo de mensagem, mas é mais comum usa-lo para processar mensagens Java Message Service (JMS). Esses beans são caracterizados por serem sem estado de sessão e são invocados pelo container quando uma mensagem JMS chega ao destino.

Um bean de sessão pode receber uma mensagem JMS de forma síncrona, mas um Message-Driven Bean pode receber uma mensagem de forma assíncrona.

Podemos converter um POJO para um Message-Driven Bean usando a anotação @MessageDriven. Segue na Listagem 24 um exemplo.

Listagem 24. Criando um MDB utilizando a anotação @MessageDriven.

@MessageDriven(mappedName = "meuDestino") public class MeuMessageBean implements MessageListener { @Override public void onMessage(Message message) { try { // processa a mensagem aqui } catch (JMSException ex) { // trata as exceções } } }

Neste código, @MessageDriven define o bean como sendo um Message-Driven Bean. O atributo mappedName especifica o nome do JNDI do JMS de destino para o qual o bean consumirá a mensagem. O bean deve implementar a interface MessageListener, na qual fornece apenas um método, "onMessage". Este método é chamado pelo Container sempre que a mensagem é recebida pelo Message-Driven Bean. Este método conterá a lógica de negócio específica de aplicação.

O código a Listagem 25 mostra como uma mensagem de texto é recebida pelo método onMessage e como o corpo da mensagem pode ser recuperada e exibida.

Listagem 25. Recebendo e recuperando uma mensagem de texto.

public void onMessage(Message message) { try { TextMessage tm = (TextMessage) message; System.out.println(tm.getText()); } catch (JMSException ex) { //. . . } }

Apesar de um Message-Driven Bean não poder ser invocado diretamente por um bean de sessão, ele ainda pode invocar outros beans de sessão. Um Message-Driven Bean também pode enviar mensagens JMS.

A anotação @MessageDriven também pode ter atributos adicionais para configurar o bean. Por exemplo, a propriedade “activationConfig” pode ter um array de ActivationConfigProperty que fornece informações para o publicador sobre as configurações do bean neste ambiente operacional.

A Tabela 1 define o conjunto padrão de propriedades de configuração que são suportados pelo @MessageDriven.

Nome da Propriedade

Descrição

acknowledgeMode

Especifica o modo de confirmação do JMS para a mensagem entregue quando a demarcação de transação do bean gerenciado é usada. Os valores suportados são Auto_acknowledge (default) ou Dups_ok_acknowledge.

messageSelector

Especifica o seletor de mensagem JMS para ser usado na determinação de quais mensagens um MDB recebe.

destinationType

Especifica se o MDB é para ser usado com um Queue ou Topic. Os valores suportados são javax.jms.Queue ou javax.jms.Topic.

subscriptionDurability

Se MDB é usado com um Topic, especifica se uma inscrição durável ou não durável é utilizada. Os valores suportados são Durable ou NonDurable.

Tabela 1. Propriedades suportadas pelo @MessageDriven.

Um Message-Driven Bean é capaz de processar mensagens de múltiplos clientes simultaneamente.

Assim como beans sem estado de sessão, o container pode fazer um pool de instâncias e alocar instâncias de bean suficiente para lidar com o número de mensagens em um determinado momento. Todas as instâncias do bean são tratadas igualmente.

Como uma mensagem é entregue para um Message-Driven Bean dentro de um contexto transacional, todas as operações do método "onMessage" fazem parte de uma única transação. O contexto de transação é propagado para os outros métodos invocados dentro do onMessage.

O MessageDrivenContext pode ser injetado em um Message-Driven Bean. Esta funcionalidade fornece acesso ao contexto do Message-Driven Bean em tempo de execução que está associado com a sua instância. Segue na Listagem 26 um exemplo de como fazer uma reversão (rollback) na transação usando o MessageDrivenContext.

Listagem 26. Revertendo uma transação com MessageDrivenContext.

@Resource MessageDrivenContext mdc; public void onMessage(Message mensagem) { try { TextMessage textMessage = (TextMessage)mensagem; System.out.println(textMessage.getText()); } catch (JMSException ex) { mdc.setRollbackOnly(); } }

O EJB 3.2 permite que um Message-Driven Bean implemente uma interface de escuta (listener) sem métodos. Um bean que implementa uma interface sem métodos expõe todos os métodos públicos da classe de bean e de quaisquer superclasses, exceto o java.lang.Object.

Artigos relacionados