EJB - Enterprise Java Beans
Arquitetura RMI - Remote Method Invocation
Antes de começar a falar sobre EJB é importante apresentar o conceito de invocação remota de métodos. Afinal este é o motivador principal para o surgimento dos EJBs. Pois quando utilizamos EJB no desenho de nossa arquitetura estamos a pensar em uma programação distribuída, onde os elementos da arquitetura deverão estar "espalhados" em diversos servidores, permitindo assim a distribuição e escalabilidade de processamento. Então a melhor forma de ser apresentado a este novo ambiente é através do RMI, pois esta será a tecnologia base para os EJBs.
Stubs e Skeletons
Não há como falar de RMI sem falar em stub e skeletons. Estes dois elementos são a base da cominicação dos objetos remotos do RMI.
Os stubs são as interfaces entre uma aplicação e os objetos remotos da arquitetura. Eles server como uma espécie de roteador de chamadas remotas, pois a execução de métodos existentes nos stubs corresponde a chamadas dos respetivos métodos nos objetos remotos.
Ao executarmos um método "localmente", na prática estamos a fazer uma conexão com um objeto em algum servidor, estamos a transmitir parâmetros e a fazer com que o respetivo método seja processado no ambiente do servidor. Se o método executado retornar alguma informação, esta também será retransmitida de volta para o stub e por sua vez disponibilizada para o programa "local" que originou o processamento.
Observe que neste contexto, a carga de processamento deixa de ser no computador local e passa para o computador onde o servidor estiver a executar.
Vamos agora inverter o ponto de vista da arquitetura, estavamos a observar o processo a partir do programa "local" que utiliza objetos remotos. Então, vamos agora observar as coisas do lado do servidor. Ou seja, dos objetos remotos.
Neste contexto surgem os skeletons, que assim como os stubs, servem de roteadores para os objetos remotos. Então os stubs estabelecem a conexão com os skeletons e trocam informações. Estas informações, como vimos, são chamadas a métodos que podem agregar parâmetros e necessitar de respostas. Logo, os skeletons são responsáveis por receber estas chamadas, interpretar os parâmetros, identificar e executar o método no objeto desejado e retransmitir eventuais respostas.
De maneira rápida, podemos colocar a criação e utilização de RMI em 7 etapas: crição da interface remota; implementação da interface remota; compilação das classes criadas; geração de stubs e skeletons; execução da ferramenta de registo RMI; criação e execução da aplicação remota; criação e execução da aplicação cliente.
Vamos então ao exemplo tradicional "Olá mundo"...
1) Remote Interface: nesta interface devem constar a assinatura de todos os métodos remotos a serem disponibilizados.
ex.:
import java.rmi.*;
public interface Hello extends Remote{
public void digaOla() throws RemoteException;
}
2) Implementação da Remote Interface: no contexto RMI a implementação da interface remota deve extender a classe "UnicastRemoteObject" ou utilizar o método "exportObject()" desta classe. A classe de implementação deve prover código para todos os métodos existentes na interface remota.
ex:
import java.rmi.*;
import java.rmi.server.*;
public class HelloRemote extends UnicastRemoteObject implements Hello {
HelloRemote() throws RemoteException{
super();
}
public void digaOla
(){
System.out.println("Ola mundo remoto!!!");
}
}
Esta classe corresponde ao skeleton dentro da arquitetura. A seguir iremos gerar o respetivo stub e publicá-lo no RMI Registry.
3) Compile as classes e interfaces geradas até então.
4) Gere os stubs: para gerar estes elementos precisamos utilizar a ferramenta java "rmic".
ex.:
rmic HelloRemote
Ao correr este comando podemos observar que será criada uma classe chamada "HelloRemote_Stub". Esta classe corresponde exatamente ao stub da arquitetura RMI.
Se decompilarmos esta classe teremos algo semelhante a isto:
ex.:
public final class HelloRemote_Stub
extends RemoteStub
implements Hello, Remote
{
private static final long serialVersionUID = 2L;
private static Method $method_digaOla_0;
static
{
try
{
$method_digaOla_0 = Hello.class.getMethod("digaOla", new Class[0]);
}
catch (NoSuchMethodException localNoSuchMethodException)
{
throw new NoSuchMethodError("stub class initialization failed");
}
}
public HelloRemote_Stub(RemoteRef paramRemoteRef)
{
super(paramRemoteRef);
}
public void digaOla()
throws RemoteException
{
try
{
ref.invoke(this, $method_digaOla_0, null, 362195600199132750L);
}
catch (RuntimeException localRuntimeException)
{
throw localRuntimeException;
}
catch (RemoteException localRemoteException)
{
throw localRemoteException;
}
catch (Exception localException)
{
throw new UnexpectedException("undeclared checked exception", localException);
}
}
}
Vale destacar o método construtor, pois é possivel verificar que este recebe como parametro uma referência. Esta referencia corresponde ao endereço do skeleton. Esta referencia é gerada e atribuída em tempo de execução, através da própria arquitetura RMI, quando se esta a registar o objeto no RMI Registry. Ou seja, ao executarmos o comando "bind" ou "rebind", como veremos mais adiante.
5) Executar o servidor de registo do RMI: para isto basta executar o comenado rmiregistry e informar a porta em que o serviço estará disponível.
ex.:
rmiregistry 5000
6) Criar e executar a aplicação remota: através da classe "Naming" podemos registar nossos elementos, bem como efetuar outras operações como "lookup", "bind", "unbind" e "list".
ex.:
import java.rmi.*;
import java.rmi.registry.*;
public class HelloServer{
public static void main (String args[]) {
try{
Hello stub = new HelloRemote();
Naming.rebind("rmi://localhost:5000/hello", stub);
} catch (Exception e) {
System.out.println(e);
}
}
}
7) Criar e executar a aplicação cliente: na aplicação cliente também utilizaremos a classe "Naming" para obter a referência para nosso objeto remoto. Ambas as aplicações serão executadas no mesmo computador. Entretanto, isto não é necessário. Bastando para isto alterar os endereções utilizados como local do RMI Register.
ex:
import java.rmi.*;
public class HelloClient {
public static void main (String args[]) {
try{
Hello stub = (Hello) Naming.lookup ("rmi://localhost:5000/hello");
stub.digaOla
();
} catch (Exception e) {
}
}
}
Para executar o exemplo compile todas as classes com o comando "javac *.java", gere os stubs e skeletons com o comando "rmic HelloRemote"
, execute o RMI register com o comando "rmiregistry 5000", inicie a aplicação servidora com o comando "java HelloServer" e execute a aplicação cliente com o comando "java HelloClient".
Observe que cada comando deve ser executado em uma consola diferente, ou seja, cada comando executa em uma JVM disferente. O Servidor reside em uma JVM. Este se regista em no RMI Registry que corre em uma outra JVM. E por fim o cliente corre em uma terceira JVM e consulta o RMI Registry para obter a referência para o servidor, que por sua vez esta a executar em outra JVM.
EJB Container
O EJB Container é um repositório de EJBs. Entende-se por repositório, uma camada que intercepta as chamadas e utilização de recursos, permitindo que se gerencie e controle a forma como estes recursos devem ser utilizados. Por exemplo, quando um programa tenta obter um objeto que esteja dentro do container, podemos exigir que este programa esteja previamente autenticado. Caso contrário o acesso ao objeto será negado.
Outra funcionalidade do container é otimizar a performance das aplicações. Os objetos de uma aplicação podem ser inseridos no container para que estes sejm colocados em "pool" na memória, fazendo com que o acesso e utilização destes elementos seja ágil e eficiente. Pois já se encontram instanciados e pronto para utilização. Ou seja, os elementos encontram-se em memória.
Também podemos delegar ao container a gestão transacional da execução de métodos dos objetos. Através de configuração, podemos determinar os métodos que devem ser executados em contextos de transação, bem como outras características da própria transação.
Os objetos adicionados ao container, após serem criados, são disponibilizados através da JNDI. Então, quando uma aplicação deseja utilizar um destes elementos, esta deverá busca-lo na JNDI. Esta é uma forma simplificada de descrever o processo, pois o que se passa na realidade é algo semelhante ao que foi apresentado anteriormente para RMI. É feito uma busca pela interface HOME, através desta é possível estabelecer uma conexão com a interface REMOTE, para que então, os métodos e funcionalidades do objeto possam ser utilizados.
Antes da versão EJB 3.0, os containers eram configurados obrigatóriamente através do ficheiro "ejb-jar.xml". A partir da versão EJB 3.0, a configuração também passou a ser efetuada através de anotações Java, nas próprias classes dos objetos.
Session, Entity e Message Driven Bean
Assim como na arquitetura apresentada anteriormente, a arquitetura EJB também foi concebida para permitir a programação para soluções em ambientes distribuídos. Na prática, o que se passa no contexto de comunicação é a mesma coisa apresentada para o RMI. Sendo que alguns elementos ganham novas nomenclaturas. Ao invés de RMI Registry, temos a JNDI. Temos a Home interface e a Remote interface, que abstraem os stubs e skeletons. E temos a implementation class que representa a implementação dos métodos do skeleton (Remote interface).
A arquitetura EJB é muito mais robusta, Introduz o conceito de container, que nos permite maior controle sobre os elementos disponibilizados para utilização. Veremos em mais detalhe algumas vantagens e recursos agregados com a introdução do container.
Session Beans
Estes elementos devem possuir a lógica de negócio da aplicação. A principal característica destes objetos é o fato de não persistirem informações. Mesmo em situações onde estes elementos possuam propriedades, os valores armazenados nestas propriedades são transiente e não serão armazenados.
Existem 3 tipos de Session Beans: statefull, stateless e singleton.
Os Beans Stateless são objetos que não armazenam estado. Possuem apenas lógica de negócio. Qualquer variável instanciada durante a execução de uma funcionalidade, só estará disponível enquanto a funcionalidade estiver em execução. Estes objetos são compartilhados por diversos clientes, ou seja, diversas aplicações podem utilizar o mesmo elemento existente no container. Logo, o container não faz nenhum tipo de associação do Bean com o cliente que o utiliza, por isto não podemos garantir que um estado (atributo/propriedade) definido por uma aplicação cliente, estará disponível e até mesmo associado a todas as requisiões que este cliente venha a solicitar junto ao servidor (EJB container).
Os Beans Statufull são elementos que persistem estado, ou seja, as propriedades/atributos definidos por uma aplicação cliente estarão disponíveis para esta aplicação enquanto ela estiver a iteragir com o servidor (EJB container). Ou seja, uma vez estabelecida a conexão de um cliente com um objeto statefull, este objeto fica "retido" pelo cliente e seus dados não serão compartilhados com outros clientes. É estabelecida uma sessão de utilização. Quando o cliente remove o Bean, ou seja, deixa de utiliza-lo, seu estado é perdido e o Bean volta para o "pool" para ser utilizado por outro cliente.
Os Beans Singleton são elementos únicos no container. Possuem as mesmas características dos Beans Session, sendo que não são criados em quantidade "pool", sendo apenas uma instancia por aplicação. Suas propriedades/atributos são compartilhados por inúmeros clientes.
Estes elementos podem ser utilizados para fazer tarefas de inicialização, bem como de limpeza da aplicação.
Entity Beans
Estes Beans são um mapeamento direto de linhas e registos em uma base de dados. O EJB Containaer irá gerenciar estes elementos, permitindo que seja configurado o comportamento destes, ao serem agregados ao container.
Os entities Beans são divididos em dois grupos, os BMP e CMP.
O primeiro grupo estão os Beans do tipo Entity em que a lógica da persistencia dos dados deve ser programada. Ou seja, o programador será o responsável por escrever as rotinas de persistencia dos dados. Neste contexto, o programador tem total liberdade sobre as regras de gestão de base dedados. Estes Beans são conhecidos como Bean Managed Persistent, como o nome indica, o Bean possui a lógica de persistência.
Já o outro grupo de Beans caracteriza-se pelo fato desta lógica ser delegada ao container, são os Contaner Managed Persistent Beans. Neste tipo de Bean, precisamos relaizar diversas configurações prévias de modo a "configurar" o container para que este saiba como cada Bean deve se comportar. Este modelo permite uma independência aplicacional, da base de dados. Pois as "coisas" estão definidas atraves de ficheiros de configurações, o que permite uma codificação independente do backend de base de dados.
Em ambos os casos, o container agrega vantagens como: gerência de transações, suporte a transações distribuídas, pool de objetos, clustering, portabilidade entre container EJB, escalabilidade, disponibilidade, mantenibilidade etc. Para o caso dos Beans CMP, podemos somar a estas vantagens o fato de podermos garantir o acesso concorrente aos Beans, a possibilidade de colocá-los em cache e a inicialização inteligente (lazy ou eager).