quarta-feira, 23 de abril de 2008

Implementação de Stateless Session Beans


Autor: Gleydson Lima <gleydson at j2eebrasil.com.br> e Ulisses Telemaco <ulisses at j2eebrasil.com.br>


O desenvolvimento de aplicações EJB não passa simplesmente pelo processo clássico de aplicações Java: compilação e execução. Desenvolver um componente EJB, seja ele do tipo Session Bean ou Entity Bean, envolve alguns passos adicionais. O nosso foco durante esse tutorial é explicar quais são os passos no desenvolvimento de um componente Session Bean do tipo Stateless. Vale a pena salientar que o processo de criação de um componente Stateless Session Bean é muito semelhante para outros tipo de componentes (Stateful Session Bean ou Entity Bean).


Uma aplicação EJB possui o seguinte ciclo de desenvolvimento:



A primeira etapa do desenvolvimento de EJBs é a codificação do componente. Neste processo, escrevemos as interfaces Home e Remote e a classe que de fato implementa o Bean. Após a codificação destes, passamos por um processo chamado de build, que consiste em empacotar em um JAR (Java Application Archive) as classes e interfaces que compõem o componente e os seus deployment descriptors (arquivos de configurações).


Em aplicações J2SE temos também os processos de coidificação e build. Por exemplo, um sistema de controle de estoque é formando por um conjunto de classes e interfaces que quando compiladas podem ser empacotadas em um único arquivo JAR. Esse arquivo pode ser distribuído entre várias plataformas.


Em aplicações J2EE há um novo passo: o deployment da aplicação. O container precisa realizar uma série de processos administrativos para tornar o componente pronto para utilização. A geração de objetos EJBObject, a configuração do componente de acordo com seu deployment descriptor, o binding do componente no JNDI são alguns dos processos realizados durante o processo de deployment.

1. Codificação do Componente

No desenvolvimento de um Session Bean é preciso codificar duas interfaces:


  • A interface Remote que publica para os clientes quais serão os métodos capazes de serem chamados remotamente. Os clientes devem apenas conhecer essa interface Remota para se comunicar com o componente remotamente. Como veremos a seguir, nenhuma chamada é feita ao Bean de forma direta.
  • A interface Home publica o método de criação do EJB. Os métodos de criação de um EJB tem a assinatura "create()". Uma interface Home pode sobrecarregar vários métodos de criação e assim fornecer diversas formas de se criar um objeto (assim como fazemos com sobrecarga de construtores). Para Stateless Session Bean, no entanto, a interface Home deve conter apenas um método create() sem argumentos. Uma vez que objetos Stateless não mantêm estado, não faz sentido passar argumentos na sua criação.

Ao longo desse tutorial, desenvolveremos um componente que é chamado remotamente para efetuar o saque de uma conta corrente.


Convencionalmente, as classes que compõem o componente têm os seguintes nomes: Saque, SaqueHome e SaqueBean. Essas classes representam, respectivamente, a interface remote, a interface home e a classe bean.


Abaixo segue a implementação da classe Home do componente saque.


package br.com.j2eebrasil.artigos.ejb;

/**
* A interface Home controla o ciclo de vida do componente. É através dessa interface
* que criamos e destruímos o componente. No processo de lookup, sempre se procura pela

* interface Home para a partir dela criarmos um objeto remoto.
*
* @author Gleydson Lima
*/

import javax.ejb.EJBHome;
import java.rmi.RemoteException;
import javax.ejb.CreateException;


public interface SaqueHome extends EJBHome {

public Saque create() throws RemoteException, CreateException;

}


A interface Home controla o ciclo de vida do componente. Note que a implementação da interface do componente SaqueHome herda a interface EJBHome. Para cada método create() da interface Home, deve existir um método ejbCreate() correspondente na classe Bean. Assim, quando o método create() da interface Home é acionado pelo cliente o Container chama o método ejbCreate() da implementação do componente. No nosso exemplo, quando o cliente chama o metodo create() da classe SaqueHome, o container invoca o método ejbCreate() da classe SaqueBean.


O método create da interface Home retorna um objeto que implementa a interface Remota. O diagrama abaixo ilustra o uso da interface Home:



O Cliente faz o lookup do nome do componente no servidor de nomes (JNDI), o servidor de nomes procura pelo nome requisitado e retorna para o cliente um Stub para realizar chamadas remotas ao Home. O cliente chama o método create() no HomeStub, que chama o create no Home Remoto.


No processo de criação a implementação do Bean é notificado através do callback method ejbCreate().


Quando o objeto do Bean for criado, é retornado para o cliente um Saque_Stub que permite ao cliente fazer chamadas remotas ao SaqueBean.


De posse do Stub do componente Remoto, o cliente pode realizar chamadas ao componente. O código abaixo codifica a interface Remote do componente Saque:


package br.com.j2eebrasil.artigos.ejb;

/**
* A interface é o contrato de comunicação com o componente remoto.
* Somente os métodos declarados na interface remota podem ser chamados pelos clientes

* através de uma chamada remota.
*
* Essa interface deverá ser do conhecimento de todos os clientes que desejem
* acessar o componente EJB Saque.
*
@author Gleydson Lima
*/

import javax.ejb.EJBObject
;
import java.rmi.RemoteException;

public interface Saque extends EJBObject {

/* Método remoto que realiza o saque da conta do cliente */
public String sacar(double valor) throws RemoteException;


}


A interface Remota define quais métodos serão passíveis de chamada remota. A implementação do componente (SaqueBean) poderá ter quantos métodos desejar, mas passíveis de chamada remota, somente aqueles que estiverem na interface Remote. O diagrama de seqüência abaixo ilustra o processo de chamada:



O Saque_Stub é um objeto localizado no lado cliente, e através dele fazemos as chamadas remotas ao componente.


Uma das grandes vantagens do EJB, como dito em artigos anteriores, é o gerenciamento de diversos serviços de infra-estrutura por parte do Container. Ele gerencia transações, pooling de objetos (em especial conexões), persistência, recursos dentre diversos outros serviços. Para tal, o Application Server (ou Container) gera um conjunto de classes que implementam esses serviços (de acordo com a configuração definida nos deployment descriptors). Assim, para poder ter o controle da situação, o Application Server nunca permite a chamada direta à implementação do Bean. Ao invés disso, o cliente sempre chama um objeto Proxy gerado pelo Container que gerencia os serviços e realiza as chamadas desejadas na implementação do Bean.


É por isso, que a implementação do Bean não implementa a interface Remota, pois não há chamada direta àquela classe.


package br.com.j2eebrasil.artigos.ejb;

/**
* Implementação do Bean - Essa classe contém a implementação da lógica
* de negócio.
*
* @author Gleydson Lima
*/

import javax.ejb.SessionBean
;
import javax.ejb.SessionContext;

public class SaqueBean implements SessionBean {

SessionContext sc;

/** O método ejbCreate() será chamado quando for solicitado
* o método create na interface Home. Se existir necessidade

* de fazer algum codigo de inicialização do componente ele
* deverá estar no ejbCreate()
**/

public void ejbCreate() {
System.out.println("Objeto SaqueBean criado.");

}

/* Métodos da interface SessionBean */

public void ejbActivate() {
// Esse método nunca será chamado pois esse Bean será Stateless
}

public void ejbPassivate() {

// Esse método nunca será chamado pois esse Bean será Stateless
}

public void ejbRemove() {
System.out.println("Bean sendo removido");
}

public void setSessionContext(SessionContext sessionContext) {

sc = sessionContext;
}

/* Implementação do método da interface Remota */
public String sacar(double valor) {

// implementação do método de negocio
String retorno = "Foi solicitado um saque de " +

valor +
" ao componente SaqueBean";

return retorno;

}

}


O componente remoto Stateless Session Bean é uma classe que implementa a interface SessionBean. Como dito antes, a codificação do Bean não implementa a interface Remota Saque.


Os métodos ejbCreate(), ejbActivate(), ejbPassivate() e ejbRemove() são métodos callback. Eles são chamados pelo Container para notificar alguns eventos:


  • ejbCreate(): esse método é chamado pelo Container quando o cliente requisita a criação do componente através da interface Home.
  • ejbActivate() e ejbPassivate(): esses método são chamados apenas para os componentes do tipo Stateful Session Beans. Essas chamadas são feitas para notificar, respectivamente, os eventos de ativação e passivação do componente. Esses conceitos serão revistos e detalhados no tutorial sobre Stateful Session Beans.
  • ejbRemove(): Esse método é chamado pelo Container para notificar ao Bean que essa instância será removida.

2. Build do Componente

O próximo passo é o empacotamento do componente em um arquivo JAR. Esse processo envolve tipicamente a compilação do código Java criado anteriormente e a criação do arquivo JAR. Existem diversas ferramentas de automatização de build, uma das mais usadas atualmente é o ant (http://jakarta.apache.org/ant). Apesar de não ser o foco desse tutorial o uso da ferramenta Ant no desenvolvimento de aplicações J2EE, iremos disponibilizar aqui o arquivo build.xml usado para configurar essa ferramenta e iremos forcener instruções básicas para a sua utilização. Lembramos no entanto que é altamente aconselhável para o leitor conhecer essa ferramenta. O J2EEBrasil traz um tutoriail mais detalhado sobre essa ferramenta.


Para que possamos montar um pacote é necessário termos:


  • o código compilado (.class) das classes e interfaces que compõe os componentes
  • arquivos de configuração dos componentes (Deployment Descriptors).

3. Deployment Descritors

Os deployment descriptors são arquivos XML que descrevem os componentes. É através deles que podemos configurar o comportamento transacional, persistência, e diversos outros recursos providos automaticamente pelo Container.


Existem dois tipos de deployment descriptors, os portáveis e os específicos. Os deployment descriptors portáveis configuram genericamente o componente com comportamentos providos por todos os Application Servers. A mudança de Container não afeta a mudança desses descriptors.


A competição de mercado faz com que Application Servers diferentes possuam recursos e serviços variados. Não existe uma forma de prever quais são esses serviços, nem uma forma de configurá-los genericamente, uma vez que essa configuração varia de servidor para servidor. Dessa forma, existe também um deployment descriptors específico do Application Server.


O descriptor geral é armazenado em um arquivo no formato XML chamado ejb-jar.xml. É nesse descriptor que configuramos genericamente os componentes.


<?xml version="1.0" encoding="Cp1252"?>
<!DOCTYPE ejb-jar PUBLIC '-//Sun Microsystems, Inc.//DTD
Enterprise JavaBeans 2.0//EN' '
http://java.sun.com/dtd/ejb-jar_2_0.dtd'
>

<!-- Copyright 2002 Sun Microsystems, Inc. All rights reserved. -->

<ejb-jar>
<display-name>SaqueJAR</display-name>
<enterprise-beans>

<session>
<display-name>SaqueBean</display-name>
<ejb-name>SaqueBean</ejb-name>
<home>br.com.j2eebrasil.artigos.ejb.SaqueHome</home>
<remote>
com.j2eebrasil.artigos.ejb.Saque</remote>
<ejb-class> com.j2eebrasil.artigos.ejb.SaqueBean</ejb-class>
<session-type>Stateless</session-type>
<transaction-type>Container</transaction-type>

<security-identity>
<description></description>
<use-caller-identity></use-caller-identity>
</security-identity>
</session>
</enterprise-beans>

<assembly-descriptor>
<container-transaction>
<method>
<ejb-name>SaqueBean</ejb-name>
<method-intf>Remote</method-intf>
<method-name>*</method-name>

</method>
<trans-attribute>Required</trans-attribute>
</container-transaction>
</assembly-descriptor>
</ejb-jar>


O arquivo ejb-jar.xml acima traz várias configurações genéricas do componente. As <ejb-class>, tags <home> e <remote> informam onde estão a classe que representa o Bean e as interfaces Home e Remote desse componente.


Não há como especificar no código fonte, se o componente Session é do tipo Stateful ou Stateless, uma vez que os dois implementam a mesma interface. Essa configuração é feita no deployment descriptor através da tag <session-type>.


A tag <transaction-type> configura quem irá gerenciar o serviço de transação deste componente, podendo ser Bean ou Container. A primeira opção transfere para a implementação do Session Bean controlar a transação do componente através do uso da API JTA. Já a opção Container define que o Application Server irá gerenciar as transações deste componente, sem haver a necessidade do desenvolvedor se preocupar com esse serviço de infra-estrutura.


A tag <assembly-descriptors> configura os serviços providos por todos os Application Servers, como serviço de nomes, segurança, transações, persistência... No exemplo acima, essa tag descreve como será o comportamento das transações no Container.

O Container usado nesse exemplo é o JBoss (www.jboss.org). O deployment descriptor específico do JBoss é chamado jboss.xml. Mostrado abaixo:


<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE jboss PUBLIC "-//JBoss//DTD JBOSS 3.0//EN" "http://www.jboss.org/j2ee/dtd/jboss_3_0.dtd
">

<jboss>

<enterprise-beans>
<session>
<ejb-name>SaqueBean</ejb-name>
<jndi-name>J2EEBrasilSaque</jndi-name>
</session>


</enterprise-beans>

<resource-managers>
</resource-managers>

</jboss>


Construído os deployment descriptors, estamos aptos a executar o build de nossa aplicação. De acordo com a configuração da ferramenta Ant usada nesse exemplo, devemos ter a seguinte estrutura de diretórios:



Dentro do diretório "src" encontram-se os códigos fontes do componente. O diretório "src/META-INF" contém os descritores. O diretório "build" é gerado pelo Ant em durante o build da aplicação. O código abaixo representa o arquivo build.xml (arquivo de configuração do Ant) que deve estar no diretório raiz da aplicação.


<?xml version="1.0"?>

<!-- ======================================================================= -->
<!-- Template build file -->

<!-- ======================================================================= -->

<project name="jspbrasiltutorial" default="main" basedir=".">

<property name="Name" value="J2eebrasilSaque"/>

<property name="version" value="1.0"/>

<property name="jboss.home" value="C://java//jboss-3.2.1_tomcat-4.1.24" />
<property name="jboss.configuration
" value="default" />
<property name="jboss.lib" value="${jboss.home}/server/${jboss.configuration}/lib" />

<property name="src.dir" value="${basedir}/src"/>

<property name="build.dir" value="${basedir}/build"/>
<property name="deploy.dir" value="${jboss.home}/server/${jboss.configuration}/deploy" />

<path id="
classpath.lib">
<pathelement location="${jboss.lib}/jboss-j2ee.jar" />
</path>


<!-- =================================================================== -->

<!-- Compila o codigo fonte -->
<!-- =================================================================== -->

<target name="compile">

<mkdir dir="${build.dir}"/>

<javac
destdir="${build.dir}"
debug="on"
deprecation="off"
optimize="on"

classpathref="classpath.lib"
>
<src path="${src.dir}"/>
</javac>
</target>

<!-- =================================================================== -->

<!-- Executa o build -->
<!-- =================================================================== -->

<target name="build" depends="compile">

<mkdir dir="${build.dir}/META-INF" />
<copy todir="${build.dir}/META-INF">
<fileset dir="${src.dir}/META-INF" includes="**" />
</copy>

<jar
basedir="${build.dir}"
jarfile="${basedir}/${Name}.jar" />
</target>

<!-- =================================================================== -->

<!-- Deploy in JBoss -->
<!-- =================================================================== -->

<target name="deploy" depends="build">

<copy todir="${deploy.dir}">
<fileset dir="${basedir}" includes="*.jar">
</fileset>
</copy>
</target>


<!-- =================================================================== -->
<!-- Main Target -->
<!-- =================================================================== -->


<target name="main" depends="deploy" />


<!-- =================================================================== -->
<!-- Cleans up the current build -->

<!-- =================================================================== -->

<target name="clean">
<delete dir="${build.dir}"/>
<delete file="${basedir}/${Name}.jar" />

</target>

</project>


Para executar o build utilizando o Ant a partir desse arquivo de configuração basta digitar "ant build" a partir da linha de comando. O resultado dessa operação é a criação do diretório build contendo os arquivos Java compilados, os arquivos XML e o arquivo Jar.

4. Deployment

O processo de deployment consiste simplificadamente em instalar o componente no Application Server. Esse processo é específico de cada servidores. Alguns deles possuem ferramentas próprias para efetuarem o deployment, outros possuem esquemas bem simples para efetuar a instalação de um novo componente.


Durante a instalação de um componente, o Container avalia os seus descritores para ativar os serviços configurados para aquele componente (transação, persistências, binding de objetos, etc). Possíveis erros nos descritores são identificados nessa etapa. Se o desenvolvedor, por exemplo, não informar as classes Home ou Remote de forma correta, o container irá cancelar o deployment do componente e exibir o erro para o desenvolvedor.


O processo de deployment no JBoss é bem robusto, basta copiar o arquivo JAR para o diretório deploy da configuração desejada. Normalmente, $JBOSS_HOMEserverdefaultdeploy. Assim, para efetuarmos o depployment de nossa aplicação, a única coisa que temos que fazer é copiar o arquivo Jar gerado no processo de build para o diretório citado anteriormente. Se você preferir, pode utilizar a ferramenta Ant para executar essa operação através da chamada na linha de comando "ant deploy".


A figura acima ilustra a saída do JBoss após a inslação do componente. Observe que vários passos são executados durante esse processo. O componente está correto e foi instalado com sucesso.



Vamos agora simular um erro nos descritores do componente e ver qual o efeito durante o processo de deployment no JBoss. Para construirmos esse erro, substituímos a interface SaqueHome por SaqueHomeQueNaoExiste no arquivo ejb-jar.xml. Veja código e imagem abaixo:


...
<session>
<display-name>SaqueBean</display-name>
<ejb-name>SaqueBean</ejb-name>
<home>br.com.j2eebrasil.artigos.ejb.SaqueHomeQueNaoExiste</home>

<remote>com.j2eebrasil.artigos.ejb.Saque</remote>
<ejb-class> com.j2eebrasil.artigos.ejb.SaqueBean</ejb-class>
<session-type>Stateless</session-type>

<transaction-type>Container</transaction-type>
<security-identity>
<description></description>
<use-caller-identity></use-caller-identity>

</security-identity>
</session>
...



É portanto, durante o deployment que o Containner verifica os arquivos de configurações do componenente. Possíveis erros são identificados nessa fase.

5. Codificando um Cliente

O cliente de um EJB precisa conhecer apenas as interfaces Home e Remota para comunicação com o Bean. O código abaixo implementa o cliente:


package br.com.j2eebrasil.artigos.client;

/**
*
* @author Gleydson Lima
*/

import javax.rmi.PortableRemoteObject;
import javax.naming.Context;
import javax.naming.InitialContext;

import br.com.j2eebrasil.artigos.ejb.SaqueHome;
import br.com.j2eebrasil.artigos.ejb.Saque;
import java.util.Hashtable;


public class SaqueCliente {

public static void main(String[] args) throws Exception {


Hashtable t = new Hashtable();
t.put(Context.INITIAL_CONTEXT_FACTORY, "org.jnp.interfaces.NamingContextFactory");
t.put(Context.PROVIDER_URL, "localhost");



Context ic = new InitialContext(t);
SaqueHome saqueHome = (SaqueHome) PortableRemoteObject.narrow(
ic.lookup("J2EEBrasilSaque"),
SaqueHome.class );


Saque saque = saqueHome.create();
System.out.println(saque.sacar(100.0));

}

}


O bloco em vermelho constrói as propriedades necessárias para conectar-se com o servidor JNDI. O ponto de entrada em um serviço de nomes é um contexto inicial. Para podermos nos conectar ao JNDI do JBoss precisamos dizer quem é a fabrica de contextos inicias através da propriedade
Context.INITIAL_CONTEXT_FACTORY. A propriedade Context.PROVIDER_URL especifica onde se encontra o servidor de nomes.


O InitialContext é requisitado e usado para executar um lookup pelo nome J2EEBrasilSaque. O trecho em azul representa a criação do objeto home. No processo de lookup de componentes EJB sempre é retornado um objeto Home.


A partir do Home podemos criar um componente remoto (método create). O trecho em verde mostra como usar o bean.

5. Conclusão

O desenvolvimento de um Session Bean envolve tipicamente três etapas: codificação, build e deploy. No primeiro passo, três arquivos Java são codificados: Interfaces Home e Remote, além da classe Bean. Convencionalmente, essas classes seguem a seguinte nomenclatura: SaqueHome, Saque e SaqueBean. Além desses arquivos Java, dois arquivos XML descrevem a configuração desses componentes. Esses arquivos são conhecidos como Deployment Descriptors. O segundo passo, build, consiste em construir um pacote JAR contendo o código Java compilado e os descritores do componente. O processo de deployment varia entre os Application Server e consiste em instalar o pacote gerado no Container.

Nenhum comentário: