Neste último artigo da série de desenvolvimento com Java EE, vamos concluir a aplicação de controle de matrículas que temos construído nas últimas três edições. Até o momento, desenvolvemos a camada de negócios e de persistência com JPA, criamos a camada de componentes web com JSF e incrementamos a interface web com AJAX.

Agora introduziremos a tecnologia EJB 3 para fornecer mais robustez à aplicação. Os requisitos não-funcionais do projeto deixam claro que não deve existir uma dependência forte com essa tecnologia, para que possamos executar a aplicação em duas arquiteturas. Relembramos aqui os requisitos relevantes para esta parte:

  • A aplicação deverá ser facilmente escalável, para que depois possa acomodar os componentes de negócio em um container à parte.
  • No futuro, a aplicação poderá ser fornecida como um framework a diversas instituições educacionais, que podem ou não utilizar um container de componentes de negócio em sua infra-estrutura.

Ao adaptar a aplicação ao uso do EJB 3.0, vamos considerar estratégias de desenvolvimento para um alto nível de reaproveitamento de código. Aplicaremos também alguns design patterns que promoverão a independência solicitada.

Decisões de projeto

Vamos começar discutindo algumas alternativas de projetos para atender os requisitos restantes.

Escalabilidade

A escalabilidade é a capacidade de um sistema de atender um volume crescente de carga de processamento sem comprometer seriamente a performance ou o tempo de resposta para os usuários. A versão atual da nossa aplicação funciona em um container web, conforme a Figura 1. Essa arquitetura é totalmente funcional, mas atualmente concentra todo o processamento em uma única JVM. Se o volume de trabalho sobre o servidor aumentar e quisermos manter um tempo de resposta aceitável para os usuários, temos algumas possibilidades:

  • Aumentar a capacidade do servidor, adicionando mais memória e mais processadores. Neste caso, estaríamos limitados às possibilidades de expansão do servidor.
  • Criar um cluster de containers web coordenado por um balanceador de carga (por exemplo, utilizando três servidores, teríamos um balanceador de carga e dois servidores para os containers web). Com essa solução, no entanto, acabaríamos prendendo a arquitetura a algum produto específico, pois o balanceamento de carga de containers web ainda não é padronizado pela especificação Java EE.
  • Distribuir o processamento em camadas físicas de responsabilidade. Uma abordagem totalmente coerente com a especificação Java EE é alocar um container para processamento web e um segundo container para o processamento de regras de negócio. Vamos seguir esta direção para proporcionar mais escalabilidade à aplicação.
Arquitetura 1: implementação em container web com classes comuns e JPA
Figura 1. Arquitetura 1: implementação em container web com classes comuns e JPA.

Queremos poder distribuir uma parte da carga de processamento para um container de componentes de negócio, que funciona numa outra JVM em outro servidor corporativo. Há muito tempo, a especificação Java EE propõe a utilização de componentes de negócio distribuídos, capazes de funcionar em containers especializados e independentes da tecnologia de apresentação utilizada. Esses são os conhecidos componentes EJB ou Enterprise JavaBeans.

Um componente EJB pode oferecer uma interface de acesso remoto, através da qual clientes da rede local invocam os métodos de negócio disponíveis no componente, externamente ao servidor. O uso de EJBs com interface remota nos permitirá implantar a arquitetura da Figura 2.

Arquitetura 2: componentes web + componentes EJB 3 + JPA
Figura 2. Arquitetura 2: componentes web + componentes EJB 3 + JPA.

Mas não é só a questão da distribuição de processamento que deve ser levada em conta para adotar o uso de EJBs. No quadro “EJBs de sessão” apresentamos conceitos básicos e discutimos outras vantagens dessa tecnologia.

Equivalência de regras de negócio

Os requisitos da nossa aplicação exigiram que começássemos criando uma versão web e que só depois criássemos uma versão mais escalável (baseada em containers de negócio). Isso nos leva a conviver com as duas arquiteturas mostradas nas Figuras 1 e 2.

Os componentes de negócio devem ser equivalentes em ambas as arquiteturas. Ou seja, as regras de negócio implementadas nas classes comuns do pacote jm.matriculas.business.impl.comum devem ser implementadas de modo equivalente nos Session Beans que iremos criar. Vamos estudar três abordagens para resolver a questão da equivalência das regras de negócio entre as duas arquiteturas.

Abordagem 1: Famílias paralelas de componentes

A Figura 3 ilustra uma primeira abordagem, que consiste em manter (trabalhosamente) duas famílias de componentes, uma para cada arquitetura. Neste caso, qualquer alteração de regras de negócio vai exigir esforço duplicado de manutenção e de testes nas duas famílias de componentes.

Essa abordagem é frágil e não reaproveita os componentes criados até aqui (ou seja, os da Arquitetura 1). Deveria ser considerada no caso de não existir controle sobre o código-fonte das classes comuns de negócio. Também seria uma abordagem válida se essas classes realizassem operações proibidas dentro de um container EJB, ou se usassem recursos transacionais (como conexões com bancos de dados) de maneira inadequada.

Famílias de componentes paralelas e independentes
Figura 3. Famílias de componentes paralelas e independentes.

Abordagem 2: Reutilização por dependência

Numa segunda abordagem, poderíamos criar componentes EJB de sessão encapsulando as classes da Arquitetura 1, como mostrado na Figura 4. Basicamente, os métodos de negócio das classes comuns seriam invocados pelos métodos disponibilizados pelos Session Beans através de suas interfaces remotas de acesso.

Reutilização por dependência (pattern Session Façade)
Figura 4. Reutilização por dependência (pattern Session Façade).

Com essa abordagem, uma alteração nas regras de negócio iria concentrar o esforço de manutenção nos componentes da Arquitetura 1. Note que estamos utilizando o design pattern Java EE conhecido como Session Façade. Seguindo este pattern, os Session Beans funcionam como uma fachada (façade) baseada na tecnologia EJB, que coordena o acesso aos verdadeiros componentes de negócio.

Abordagem 3: Reutilização por herança

É possível utilizar herança para propagar os comportamentos dos componentes comuns da Arquitetura 1 para os componentes EJB da Arquitetura 2, conforme vemos na Figura 5. Como acontece na abordagem anterior, alterações nas regras de negócio implicam manutenção concentrada nas classes comuns da Arquitetura 1.

Reutilização por herança
Figura 5. Reutilização por herança.

Esta é a abordagem que exige menos esforço, mas ela só deve ser considerada quando temos controle sobre o código-fonte das classes comuns de negócio. Isso porque precisamos garantir que todos os parâmetros e retornos dos métodos sejam compatíveis com uma interface remota (mais especificamente, eles devem ser serializáveis). Vamos adotar essa terceira abordagem em nosso projeto.

Componentes multi-arquitetura

No intuito de conviver com os dois cenários (com e sem componentes EJB), o ideal é que os componentes web desenvolvidos sejam reutilizados sem modificação de código-fonte, quando alternarmos entre as Arquiteturas 1 e 2. Para que isso seja possível, os detalhes de comunicação remota com os EJBs de sessão da Arquitetura 2 devem ser resolvidos fora dos componentes web.

Business Delegate

A solução mais praticada nessa situação é proposta pelo design pattern Java EE Business Delegate. Dentre outros benefícios, um Business Delegate encapsula os detalhes de utilização dos componentes de negócio, desacoplando os clientes (no caso, os componentes web) das APIs e exceções típicas na manipulação de EJBs ou de outras tecnologias.

Embora inicialmente proposto para reduzir o acoplamento de clientes de EJBs o Business Delegate pode ser perfeitamente considerado para clientes de outras tecnologias como web services, CORBA etc.

Podemos pensar em criar uma classe Business Delegate para cada componente de negócio. E para cada método de negócio do componente haverá um método similar na classe Business Delegate (que passaremos a chamar de “delegate” para simplificar).

O método do delegate pode ter uma assinatura diferente do método de negócio para ocultar algum detalhe de tecnologia, principalmente exceções. Cada método de um delegate tem a função de invocar o método de negócios real, adequando parâmetros, convertendo exceções e convertendo tipos de retorno quando necessário.

Para promover a reutilização dos componentes web nas duas arquiteturas apresentadas, poderíamos ocultar dentro dos delegates a utilização dos componentes de negócio, de acordo com a arquitetura vigente – veja a Figura 6.

Business Delegate para duas arquiteturas
Figura 6. Business Delegate para duas arquiteturas.

Um parâmetro de configuração pode ser disponibilizado para os componentes web, por exemplo através do descritor web.xml, e transmitido para os delegates, indicando qual família de componentes de negócio deve ser utilizada.

Neste cenário de convivência com múltiplas arquiteturas o delegate vai encapsular, em cada método delegado, uma estrutura if-else. Essa estrutura decidirá qual família de componentes deverá ser invocada. Entretanto, se uma terceira arquitetura viesse a ser requisitada (por exemplo, uma baseada em Session Beans de interfaces locais, ou em web services), teríamos que visitar todos os métodos delegados e ampliar a estrutura if-else. Também teríamos o problema da quantidade de APIs combinadas dentro das classes delegate – por exemplo, um bloco if, manipulando a API JNDI e interfaces de acesso para EJBs, adjacente a um bloco else manipulando APIs como JAX-RPC ou JAX-WS para acessar web services.

Analisaremos, então, uma alternativa melhor, que garante a reutilização dos componentes web nas duas arquiteturas propostas, e que acomodará melhor futuras necessidades de outras arquiteturas.

Interfaces de negócio

Vamos nos valer de uma melhoria introduzida pela especificação EJB 3.0 – a manipulação de interfaces de acesso a Session Beans remotos não mais requer o tratamento de exceções do tipo RemoteException.

Anteriormente à especificação EJB 3.0, os métodos de negócio disponíveis na interface de acesso de um Session Bean remoto obrigatoriamente deveriam lançar a exceção RemoteException. Basicamente, uma RemoteException transportava para o cliente remoto uma exceção do tipo RuntimeException ocorrida durante a execução de um método de negócios dentro do container. Quando trabalhamos com EJB 2.x, o tratamento dessas exceções normalmente é feito no Business Delegate.

No quadro “EJBs de sessão”, vimos que os Session Beans invariavelmente necessitam de uma interface de acesso. Como há controle sobre todo o código produzido, nada impede o projetista de utilizar as interfaces de acesso dos Session Beans como interfaces de negócio para as duas arquiteturas. Dessa forma, os componentes web precisam apenas obter as implementações das interfaces de negócio, de acordo com a arquitetura utilizada – veja a Figura 7.

Uso de interfaces de negócio
Figura 7. Uso de interfaces de negócio.

Como reaproveitamos os componentes de negócio por herança, temos que uma interface definida para um componente da Arquitetura 1 será definida (por herança) para o componente correspondente da Arquitetura 2. Isso pode ser observado no diagrama mostrado na Figura 8.

Combinando a reutilização por herança com interfaces de negócio
Figura 8. Combinando a reutilização por herança com interfaces de negócio.

Vamos analisar o código fonte da Listagem 1. IGerenciadorAlunos é a interface de negócios, e GerenciadorAlunos é sua implementação para a Arquitetura 1. Repare nas declarações da classe GerenciadorAlunosEJb3 para a Arquitetura 2: a anotação javax.ejb.Stateless (@Stateless) define a classe como um EJB de sessão sem estado, e a anotação javax.ejb.Remote (@Remote) especifica para o container EJB qual é a interface de acesso remoto.

Na classe Java comum (POJO) GerenciadorAlunos, encontramos o método setEntityManager(). Através deste método, o componente de negócios receberá um objeto EntityManager para que possa realizar as operações de persistência. No caso das classes comuns da Arquitetura 1 (GerenciadorAlunos), este EntityManager deverá ser fornecido manualmente pela nossa aplicação.

Já na versão EJB para a Arquitetura 2 (subclasse GerenciadorAlunosEJb3), ocorrerá o processo de “injeção de dependência”. Ou seja, o container EJB reconhecerá a anotação @PersistenceContext presente no método e automaticamente fornecerá (injetará) um objeto EntityManager como parâmetro desse método.

A simplicidade introduzida na especificação EJB 3 salta aos olhos neste momento, para quem desenvolve componentes EJB 2.x. Não precisamos de descritores como ejb-jar.xml, nem de componentes Home (EJBHome) ou de interfaces de ciclo de vida (SessionBean).

Listagem 1. Interfaces e implementações de gerenciamento de alunos

            IGerenciadorAlunos

// ... package e imports

public interface IGerenciadorAlunos {

  public abstract Integer salvar(Aluno aluno)

     throws ControleMatriculaException;

  public abstract void excluir(Aluno aluno)

     throws ControleMatriculaException;

  public abstract Aluno getById(Integer id)

     throws ControleMatriculaException;

  public abstract Aluno getByCpf(String cpf)

     throws ControleMatriculaException;

  public abstract List getByNome(String nome)

     throws ControleMatriculaException;

}

 

GerenciadorAlunos

// ... package e imports

public class GerenciadorAlunos implements IGerenciadorAlunos {

  private EntityManager entityManager = null;

 

  @PersistenceContext

  public void setEntityManager(EntityManager em) {

    this.entityManager = em;

  }

  public Integer salvar(Aluno aluno)

     throws ControleMatriculaException

  {

    if (aluno.getNome() == null

    || aluno.getEmail() == null

    || aluno.getTelefone() == null)

    {

      throw new ControleMatriculaException(

        "Dados de aluno incompletos");

    }

    if (aluno.getId() == null) {

      entityManager.persist(aluno);

    } else {

      entityManager.merge(aluno);

    }

    return aluno.getId();

  }

  // ... demais métodos de negócio

}

 

GerenciadorAlunosEjb3

// ... package e imports

@Stateless

@Remote(IGerenciadorAlunos.class)

public class GerenciadorAlunosEjb3 extends GerenciadorAlunos {

}
        

Fabricando componentes de negócio

Para definir qual família de classes de negócio deverá ser utilizada, podemos contar com a ajuda de uma fábrica de componentes de negócio. Como as duas famílias podem ser manipuladas pelas mesmas interfaces, podemos considerar a utilização do clássico design pattern Abstract Factory.

Cada família de componentes será obtida por uma fábrica especifica; portanto teremos duas fábricas. E para que os componentes web não fiquem acoplados a nenhuma das duas fábricas, criamos uma fábrica abstrata, com a capacidade de decidir pela fábrica de componentes que deve ser utilizada – veja a Figura 9.

Fábricas de componentes de negócios
Figura 9. Fábricas de componentes de negócio.

O uso do pattern Abstract Factory permite acomodar melhor futuras famílias de componentes de negócio. Para cada nova família bastaria fornecer uma nova implementação de fábrica, e novas implementações das interfaces de negócios.

A Listagem 2 nos mostra a BusinessFactory abstrata e as fábricas de componentes para arquitetura com classes comuns e a baseada em componentes EJB 3.0.

Listagem 2. Fábricas de componentes de negócio

            BusinessFactory

package jm.matriculas.business;

public abstract class BusinessFactory {

  private static String businessFactoryClassName;

  public abstract IGerenciadorAlunos getGerenciadorAlunos();

  public abstract IGerenciadorTurmas getGerenciadorTurmas();

  public abstract IGerenciadorMatriculas getGerenciadorMatriculas();

  public abstract void beginTransaction();

  public abstract void commitTransaction(boolean releaseResources);

  public abstract void rollbackTransaction(boolean releaseResources);


  public static BusinessFactory getInstance() {

    try {

      Class _class = Class.forName(businessFactoryClassName);

      return (BusinessFactory) _class.newInstance();

    } catch (Exception e) {

      throw new IllegalArgumentException("businessFactoryClassName invalido");

    }

  }

  // ... getters e setters

}


CommonBusinessFactory

// ... package e imports

public class CommonBusinessFactory extends BusinessFactory {

  private static EntityManagerFactory entityManagerFactory;

  private EntityManager entityManager;

 
  public IGerenciadorAlunos getGerenciadorAlunos() {

    GerenciadorAlunos ga = new GerenciadorAlunos();

    ga.setEntityManager(this.entityManager);

    return ga;

  }

 
  public void beginTransaction() {

    if (entityManager == null || ! entityManager.isOpen())

      entityManager = entityManagerFactory.createEntityManager();

    if (! entityManager.getTransaction().isActive())

      entityManager.getTransaction().begin();

  } 

  // ... demais métodos

}


Ejb3BusinessFactory

// ... package e imports

public class Ejb3BusinessFactory extends BusinessFactory {

        // ... constantes

        private static Properties jndiProperties;

        private InitialContext initCtx;

        private UserTransaction userTransaction;
 

  public IGerenciadorAlunos getGerenciadorAlunos() {

    try {

      String jndiName = jndiProperties.getProperty(JNDI_PROPERTY_GERENCIADOR_ALUNOS); 

      IGerenciadorAlunos ga = (IGerenciadorAlunos) initCtx.lookup(jndiName);

      return ga;

    } catch (NamingException e) {

      throw new RuntimeException(e);

    }

  }

 
  public void beginTransaction() {

    try {

      if (initCtx == null) {

        initCtx = new InitialContext(jndiProperties);

        String jndiName = jndiProperties.getProperty(JNDI_PROPERTY_USER_TRANSACTION);

        userTransaction = (UserTransaction) initCtx.lookup(jndiName);

      }

      if (userTransaction.getStatus() == Status.STATUS_NO_TRANSACTION)

        userTransaction.begin();

    } catch (Exception e) {

      initCtx = null;

      throw new RuntimeException(e);

    }

  }

}
        

Componentização e controle de transações

O último requisito do nosso projeto define um cenário de longo prazo, em que outras instituições poderiam se basear nesta aplicação para construir novas aplicações de gerenciamento educacional. Os componentes de negócio projetados para os requisitos definidos no início do projeto poderiam ser combinados para suportar processos não previstos até o momento.

Imagine um processo de transferência de matrícula entre turmas, no qual o aluno quer mudar da turma A para a turma B. Não basta alterar o identificador de turma no objeto da matrícula, pois a turma B precisa aceitar o aluno, sem que a quantidade de vagas seja ultrapassada. O processo de transferência pode ser suportado com o cancelamento da matrícula na turma A e a criação de uma nova matrícula na turma B. Este processo deve ocorrer dentro de uma única transação, pois se a turma B rejeitar a matrícula, o aluno deverá permanecer na turma A (ou seja, seria feito um rollback). Existem duas soluções:

  • Acrescentar um método de negócios de transferência em GerenciadorMatricula, que deve coordenar todo o processo.
  • Acionar em seqüência, a partir de um cliente, os métodos cancelar() e criar(), já disponíveis em GerenciadorMatricula.

A primeira solução é muito prática, pois com ela a operação ficará contida na camada de negócios, dentro de uma transação. Se o requisito de transferência estivesse definido desde o início, poderíamos facilmente incluir o método adicional. Mas para atender a um requisito não definido previamente, como essa da transferência, os futuros desenvolvedores/mantenedores da aplicação precisariam ter acesso ao código-fonte dos componentes de negócio. Ou então teriam que entender toda a proposta de reaproveitamento dos componentes em duas arquiteturas, antes de criar novos componentes de negócio.

Pensando nos futuros usos da aplicação, a segunda solução – a de acionamento em seqüência – poderá ser mais apropriada, conforme vemos na Figura 10. Toda a seqüência de chamadas deve acontecer dentro de uma única transação. Isso significa que as transações não podem ser delimitadas pelos métodos de negócio cancelar() e criar() (e por todos os demais); elas devem ser delimitadas pelos componentes web. Isso já vinha sendo feito nas outras versões da aplicação, apresentadas nas partes anteriores da série.

Diagrama de sequência para transferência de matrículas
Figura 10. Diagrama de seqüência para transferência de matrículas.

Mas há a questão do container EJB, que por default controla a transação automaticamente. Uma chamada feita por um cliente externo ao container EJB, a um método de negócios de um Session Bean, pode ser isolada em uma transação. Tal comportamento impede, na Arquitetura 2, o suporte a novos processos pelo acionamento em seqüência dos métodos de negócio disponíveis. A solução é controlar, via programação, nos componentes web, as transações do container EJB através da interface javax.transaction.UserTransaction.

Veja a Listagem 3, onde o processo de transferência está contido em uma única transação, com exemplos para as Arquiteturas 1 e 2. Na classe ClienteTransferecia, um objeto UserTransaction foi solicitado ao container EJB, para envolver as chamadas a métodos de negócio em uma única transação. O objeto UserTransaction representa uma transação dentro do container, e neste exemplo o cliente está forçando o container a obedecer ao controle manual de transações.

Listagem 3. Controle manual de transações em EJBs – classe ClienteTransferencia

            // ... package e imports

public class ClienteTransferencia {

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

                  Hashtable hashtable = new Hashtable();

                   hashtable.put(Context.INITIAL_CONTEXT_FACTORY,

     "org.jboss.naming.NamingContextFactory");

          hashtable.put(Context.PROVIDER_URL, "localhost:1099");

          InitialContext initialContext = new InitialContext(hashtable);

          UserTransaction userTransaction =

     (UserTransaction) initialContext.lookup("UserTransaction");

          userTransaction.begin();

 

          try {

                            IGerenciadorTurmas gt = (IGerenciadorTurmas) 

                            initialContext.lookup("GerenciadorTurmasEjb3/remote");

                            IGerenciadorMatriculas gm = (IGerenciadorMatriculas) 

                            initialContext.lookup("GerenciadorMatriculasEjb3/remote");

 

                            Turma t1 = gt.getById(new TurmaId("HTML", 1));

                            Matricula m1 = t1.getMatriculas().iterator().next();

                            gm.cancelar(m1);

                            Matricula m2 = gm.criar(new TurmaId("HTML", 2), m1.getAluno());

                            userTransaction.commit();

                   }

   catch (Exception e) {

                            userTransaction.rollback();

                             throw e;

                   }

   finally {

                             initialContext.close();

                   }

         }

}

        

O InitialContext utilizado para obter o UserTransaction deve localizar todos os componentes EJB de sessão cujos métodos serão envolvidos na mesma transação.

E como os componentes web poderão controlar as transações de negócio de forma transparente, sem criar acoplamento com os tipos javax.persistence.EntityTransaction da JPA ou com javax.transaction.UserTransacion da JTA? Podemos utilizar as fábricas de componentes para manipulação da API de transações adequada, como vemos nos métodos beginTransaction(), commitTransaction() e rollbackTransaction(), das classes mostradas na Listagem 2.

Adotando o controle de transação externo, podemos afirmar que chegamos a um modelo robusto de componentes de negócio multi-arquitetura, que poderá com segurança funcionar como um framework para futuras aplicações.

Construção (build), implantação e testes

No quadro “Checklist” apresentamos todos os elementos necessários para preparar o ambiente deste projeto. O arquivo script-ant/build.xml, disponibilizado no site da Java Magazine junto com o código-fonte completo, é capaz de montar rapidamente arquivos de deployment para as duas arquiteturas estudadas. Além disso, o script Ant é capaz de fazer o deployment em um servidor JBoss local.

É interessante verificar o diretório etc do projeto, onde é possível encontrar arquivos de configurações importantes para as duas arquiteturas. Também vale a pena examinar os pacotes gerados no diretório archive. Este diretório é criado após a execução dos targets de build.

Comece executando o target hsqldb.start para inicializar o banco de dados.

Testando a Arquitetura 1

  1. Realize um primeiro teste com o target teste.business.common para verificar o funcionamento dos componentes de negócio na Arquitetura 1. Em seguida, inicialize o servidor de aplicações executando o target jboss.start.
  2. Aguarde alguns instantes a inicialização completa do servidor e execute deploy.war.arquitetura1. Este target cria o arquivo matriculas.war, que é a aplicação web baseada em componentes de negócio comuns, e depois implanta esse WAR no servidor.
  3. Você já poderá acessar a aplicação via browser com a URL http://localhost:8080/matriculas.

Testando a Arquitetura 2

  1. Execute o target undeploy.arquitetura1 para remover a aplicação web implantada anteriormente (a remoção é necessária porque a aplicação web da Arquitetura 1 não está configurada para trabalhar com os EJBs).
  2. Execute o target deploy.ejb.jar.arquitetura2. Será criado e implantado o arquivo matriculas-ejb.jar, que contém os componentes de negócio na forma de EJBs de sessão. Este arquivo contém também as definições para o JBoss de um pool de conexões JDBC para a segunda arquitetura – o arquivo matriculas-ds.xml.
  3. Execute um teste preliminar para esta arquitetura, disparando o target teste.business.ejb3.
  4. Execute o target deploy.war.arquitetura2. Isso cria a aplicação web (matriculas.war) baseada em componentes EJB remotos.
  5. Acesse novamente a aplicação via browser com a mesma URL (http://localhost:8080/matriculas).

Os testes que descreveremos para a Arquitetura 2 estão previstos para funcionar em um único servidor JBoss. Para testar a aplicação com containers em servidores separados, são necessárias algumas configurações adicionais, que apresentamos a seguir.

Por comodidade, utilize a máquina onde os testes estão sendo feitos como o servidor hospedeiro de EJBs (digamos, com IP 192.168.1.73), e escolha uma outra máquina na rede local, também com o servidor JBoss, para hospedar os componentes web (digamos, 192.168.1.84).

  1. Edite o arquivo etc/matriculas.jndi.properties, e na propriedade java.naming.provider.url informe o endereço IP do servidor de EJBs, por exemplo: java.naming.provider.url=jnp://192.168.1.73:1099
  2. Execute o target build.war.arquitetura2 para gerar o arquivo archive/matriculas.war, configurado para acessar o servidor de EJBs.
  3. Copie matriculas.war para o servidor web escolhido. O arquivo deve ser copiado para a pasta [instalação-do-jboss]/server/default/deploy deste servidor.
  4. Inicialize o servidor web ([instalação-do-jboss]/bin/run.sh ou run.bat) e acesse a aplicação web pelo browser, por exemplo usando http://192.168.1.84:8080/matriculas.

Conclusões

Ao longo desta série, trabalhamos com um conjunto de requisitos realista do ponto de vista de funcionalidades, e desafiador da perspectiva de reaproveitamento de componentes de software. Também consideramos várias decisões de projeto e suas conseqüências. Pudemos ainda explorar as vantagens da API de persistência Java (JPA) e o modelo de programação JavaServer Faces, além de introduzir o uso de AJAX.

Nesta última parte, verificamos conceitos fundamentais da tecnologia EJB 3.0 e os seus benefícios. Estudamos o conceito de interfaces de acesso remoto e apresentamos os mecanismos de robustez para uma aplicação corporativa, garantidos por um container de EJBs. Vimos como tirar proveito das facilidades da tecnologia EJB 3.0 para transformar componentes de negócio comuns em componentes Java EE. Verificamos também estratégias de desenvolvimento para alternar rapidamente entre duas arquiteturas, reaproveitando os componentes Java EE e gerenciando transparentemente as transações, nos dois cenários.

Esperamos poder ter acelerado o processo de assimilação desse poderoso conjunto de tecnologias. O projeto desenvolvido ao longo da série, e disponibilizado no site desta publicação, pode ser usado como fonte de estudo e de experiências para o leitor que quiser consolidar seus conhecimentos, criar seus próprios projetos Java EE e ficar em sintonia com o mercado de desenvolvimento corporativo.

Checklist

Para experimentar com o projeto criado nesta série, será necessário configurar um ambiente com os softwares a seguir.

  • JDK 5.0 ou 6.0 – Você precisará de um Java Development Kit instalado e de ter a variável de ambiente JAVA_HOME configurada, apontado para o diretório de instalação do JDK.
  • Ant 1.6 ou superior – O Ant será usado para compilar o projeto, instalá-lo no servidor e realizar várias outras operações.
  • JBoss Application Server – O provedor JPA Hibernate Entity Manager, o banco de dados HSQLDB, a implementação do JSF Apache MyFaces e o Tomcat estão disponíveis no JBoss AS 4.0.5 (e em versões posteriores). Faça o download em labs.jboss.com/portal/jbossas/download. Nesta página localize a versão (estamos usando a 4.0.5 para esta série, mas versões mais recentes devem funcionar sem problemas), e escolha a opção Download Installer. O arquivo jems-installer-1.2.0.jar será obtido. Este instalador deve ser executado via linha de comando: java -jar jems-installer-1.2.0.jar. Durante o processo de instalação, é necessário escolher o profile “ejb3” (necessário apenas para a versão 4.0.5).
  • JBoss Ajax4Jsf e RichFaces – Para incluir no projeto o uso da biblioteca RichFaces, devemos fazer o download do arquivo jboss-richfaces-3.0.0.zip. Realizado o download, descompacte o arquivo, pois ele será utilizado como referência no script Ant do projeto. Veja mais sobre estas bibliotecas na parte anterior (Edição 46).

Download e compilação do exemplo

O pacote de download para este artigo contém todo o código do projeto, além do script Ant para compilação e execução. Descompacte o ZIP e localize o build.xml no diretório script_ants. Utilize um editor de texto para configurar neste arquivo as seguintes propriedades:

  1. jboss.home – indicando o diretório onde o JBoss foi instalado.
  2. richfaces.home – indicando o diretório onde a distribuição da biblioteca RichFaces foi descompactada.

Faça um teste de compilação via linha de comando, digitando o comando ant compile a partir do diretório script_ant. Se o comando compilar todos os códigos sem gerar erros (warnings de compilação são esperados), isso significa que o ambiente está configurado corretamente.

EJBs de sessão

Os EJBs, ou Enterprise JavaBeans, são componentes de negócio que ficam hospedados em um container de EJBs. Um dos tipos de componentes definidos pela especificação EJB é o Session Bean, ou “EJB de sessão”.

Finalidade

Um Session Bean tem como finalidade representar uma sessão interativa de um processo de negócios, do ponto de vista de um cliente da aplicação. Por exemplo, um processo de controle de contas a receber pode ser disponibilizado para um cliente através de um Session Bean.

Cada método de uma classe de Session Bean que pode ser acessado por um cliente é chamado de método de negócio.

Existem dois tipos de Session Beans:

  • O tipo Stateless, mais leve, é indicado para processos que não necessitam manter estado (valores de atributos) entre chamadas de métodos.
  • O tipo Stateful, mais pesado, garante a preservação de estado entre chamadas de métodos de um mesmo cliente.

Vantagens

Por que usar Session Beans no lugar de classes Java comuns? Há diversas vantagens, que são oferecidas pelo container de EJBs.

Sendo hospedado em um container de EJBs, um Session Bean pode funcionar como um componente de negócio distribuído, que disponibiliza o mesmo processo e regras de negócio para vários tipos de clientes. Existem três formas de acessar um Session Bean:

  • Através de uma interface remota, para clientes da rede local.
  • Através de uma interface local, para clientes localizados na mesma JVM onde está rodando o container de EJBs.
  • Através de uma interface Service End Point, para clientes que visualizam o Session Bean como um web service.

Além disso, o container oferece uma série de serviços de infra-estrutura, como:

  • Controle de segurança e transacional em nível de métodos.
  • Gerenciamento de ciclo de vida de instâncias de EJBs, proporcionando escalabilidade através do uso racional da memória do servidor.
  • APIs de acesso a recursos corporativos (JNDI, JMS, JCA etc.).
  • API de temporização (Timer).

Com toda essa infra-estrutura disponível, o programador da classe de Session Bean pode ficar focado nas regras de negócio, e se desprende de várias responsabilidades técnicas da construção de uma aplicação corporativa robusta.

Desenvolvendo EJBs de sessão

Para que possa assumir as responsabilidades oferecidas como infra-estrutura, o container de EJBs deve mediar todas as chamadas a métodos de negócio de um componente EJB. Conseqüentemente, uma instância de um Session Bean não pode ser manipulada diretamente por um cliente. Um cliente manipula uma “representação” do objeto session bean – um proxy – através de uma interface de acesso (local, remota ou service end point). Acompanhe o processo passo a passo:

  1. O cliente aciona um método da interface de acesso, no proxy.
  2. O proxy se comunica com o container, solicitando a invocação dp método de negócio correspondente em um objeto Session Bean.
  3. O container, nesse momento, cuida de serviços como controle de segurança, controle transacional (iniciando uma transação) e gerenciamento de escalabilidade. (Veja a Figura Q1.)
  4. O método é invocado no Session Bean.
  5. Antes de retornar para o proxy solicitante, o container pode concluir a transação (fazendo commit ou rollback).
  6. O proxy recebe o retorno e o repassa para o cliente.

É responsabilidade do desenvolvedor disponibilizar as interfaces de acesso de um Session Bean, além da classe Session Bean propriamente dita (que implementa o bean).

Existem algumas regras que devem ser seguidas pelo “hóspede”, o Session Bean, para não prejudicar a qualidade da infra-estrutura oferecida pelo “hospedeiro” (o container de EJBs). Algumas delas:

  • Não abrir ou manipular threads.
  • Não abrir servidores de sockets.
  • Não manipular o sistema de arquivos local do container.
Intermediação do container de EJBs
Figura Q1. Intermediação do container de EJBs.

Exemplo

Apresentamos na Listagem Q1 uma interface de acesso, e a classe do Session Bean que mantém estado (@Stateful), seguindo a especificação EJB 3. A interface é registrada como uma interface remota (@Remote). O último método do Session Bean é marcado como o método de inicialização (@PostConstruct), que deve ser executado pelo container quando uma instância é requisitada por um cliente.

Compactamos a interface e a classe em um módulo EJB – um arquivo JAR – e implantamos no container EJB. Apresentamos na Listagem Q2 o cliente remoto.

O container deve fornecer um “catálogo” de Session Beans hospedados, para que os clientes possam localizá-los através de um serviço de nomes. No JBoss, por default, um Session Bean com interface remota é registrado no catálogo de nomes seguindo o modelo “nome-simplificado-da-classe/remote” – neste exemplo o bean será registrado como “ManipuladorNumerosBean/remote”.

Temos que nos conectar ao serviço de nomes do container através de um objeto InitialContext da API JNDI (Java Naming and Directory Interface). Solicitamos um Session Bean ao container via o método lookup(). Lembrando que o que recebemos, na verdade, é um proxy (um objeto que representa o componente), gerado dinamicamente pelo container.

Listagem Q1. Interface de acesso e implementação de um Session Bean

            ManipuladorNumeros

package jm.exemplo.ejb;


public interface ManipuladorNumeros {

  public void addNumero(int i);

  public int getMaiorNumero();

  public double getMedia();

  public void resetNumeros();

}


ManipuladorNumerosBean

package jm.exemplo.ejb;

 

import java.util.ArrayList;

import javax.annotation.PostConstruct;

import javax.ejb.*;


@Stateful @Remote(ManipuladorNumeros.class)

public class ManipuladorNumerosBean

    implements ManipuladorNumeros

{

  private ArrayList numeros;

   

  public void addNumero(int i) {

    numeros.add(i);

  }


  public int getMaiorNumero() {

    if (numeros == null || numeros.size() == 0) return 0;

    int maior = Integer.MIN_VALUE;

    for(Integer n : numeros) {

      if (n > maior) maior = n;

    }

    return maior;

  }


  public double getMedia() {

    if (numeros == null || numeros.size() == 0) return 0;

      double media = 0;

      for(Integer n : numeros) {

        media += n;

      }

      return media / numeros.size();

  }
 
  @PostConstruct

  public void resetNumeros() {

    numeros = new ArrayList();

  }

}

Listagem Q2. Cliente remoto para o Session Bean

package jm.exemplo.client;


import java.util.Hashtable;

import javax.naming.*;

import jm.exemplo.ejb.ManipuladorNumeros;

 
public class ClienteSessionBean {

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

    // configurando o serviço de nomes do JBoss

    Hashtable hashtable = new Hashtable();

    hashtable.put(Context.INITIAL_CONTEXT_FACTORY,

       "org.jboss.naming.NamingContextFactory");

    hashtable.put(Context.PROVIDER_URL, "localhost:1099");

 
    // conectando ao serviço de nomes

    InitialContext initialContext = new InitialContext(hashtable);


    // obtendo o proxy

    ManipuladorNumeros manipuladorNumeros =

        (ManipuladorNumeros) initialContext.lookup(

             "ManipuladorNumerosBean/remote");


    // usando a funcionalidade do EJB

    manipuladorNumeros.addNumero(14);

    manipuladorNumeros.addNumero(56);

    manipuladorNumeros.addNumero(-11);

    System.out.println("maior " + manipuladorNumeros.getMaiorNumero());

    System.out.println("media " + manipuladorNumeros.getMedia());

    manipuladorNumeros.resetNumeros();

    System.out.println("maior " + manipuladorNumeros.getMaiorNumero());

  }

} 
        
Links: