Skip to content

4. Aplicativo de exemplo – 02: rdvmedecins-jsf2-spring

Propomos agora portar a aplicação anterior para um ambiente Spring/Tomcat:

Trata-se, de fato, de uma portabilidade. Partiremos da aplicação anterior e a adaptaremos ao novo ambiente. Comentaremos apenas as modificações. Elas se dividem em três categorias:

  • o servidor não é mais o Glassfish, mas sim o Tomcat, um servidor leve que não possui um contêiner EJB,
  • para substituir o EJB, utilizaremos o Spring, o principal concorrente do EJB e do [http://www.springsource.com/],
  • a implementação JPA utilizada será o Hibernate, em vez do EclipseLink.

Como faremos muitas operações de copiar/colar entre o projeto antigo e o novo, mantemos os projetos anteriores abertos no NetBeans:

  

O uso do framework Spring requer certos conhecimentos que podem ser encontrados em [ref7] (ver página 166).

4.1. As camadas [DAO] e [JPA]

4.1.1. O projeto NetBeans

Estamos criando um projeto Maven do tipo [Java Application]:

  • em [1], o projeto criado,
  • em [2], o mesmo projeto sem os pacotes de [Source Packages] e [Test Packages] e sem a dependência [junit-3.8.1].

O mais difícil nos projetos Maven é encontrar as dependências corretas. Para este projeto Spring / JPA / Hibernate, elas são as seguintes:


<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>istia.st</groupId>
  <artifactId>mv-rdvmedecins-spring-dao-jpa</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>jar</packaging>

  <name>mv-rdvmedecins-spring-dao-jpa</name>
  <url>http://maven.apache.org</url>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  </properties>

    <dependencies>
    <dependency>
      <groupId>org.hibernate</groupId>
      <artifactId>hibernate-entitymanager</artifactId>
      <version>4.1.2</version>
      <type>jar</type>
    </dependency>
    <dependency>
      <groupId>org.hibernate.java-persistence</groupId>
      <artifactId>jpa-api</artifactId>
      <version>2.0.Beta-20090815</version>
      <type>jar</type>
    </dependency>
    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>5.1.6</version>
    </dependency>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.10</version>
      <scope>test</scope>
      <type>jar</type>
    </dependency>
    <dependency>
      <groupId>commons-dbcp</groupId>
      <artifactId>commons-dbcp</artifactId>
      <version>1.2.2</version>
    </dependency>
    <dependency>
      <groupId>commons-pool</groupId>
      <artifactId>commons-pool</artifactId>
      <version>1.6</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-tx</artifactId>
      <version>3.1.1.RELEASE</version>
      <type>jar</type>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-beans</artifactId>
      <version>3.1.1.RELEASE</version>
      <type>jar</type>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>3.1.1.RELEASE</version>
      <type>jar</type>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-orm</artifactId>
      <version>3.1.1.RELEASE</version>
      <type>jar</type>
    </dependency>
  </dependencies>

</project>
  • linhas 18-29: para o Hibernate,
  • linhas 30-34: para o driver JDBC do MySQL,
  • linhas 35-41: para o teste JUnit,
  • linhas 42-51: para o pool de conexões Apache Commons DBCP. Um pool de conexões é um conjunto de conexões abertas. Quando o aplicativo precisa de uma conexão, ele a solicita ao pool. Quando não precisa mais dela, ele a devolve. As conexões são abertas no início da aplicação e permanecem abertas durante toda a vida útil da aplicação. Isso evita o custo de aberturas e fechamentos repetidos das conexões. Esse tipo de pool já existia no Glassfish, mas seu uso era transparente para nós. Será o mesmo aqui, mas precisamos instalá-lo e configurá-lo,
  • linhas 52-75: para o Spring.

Vamos adicionar essas dependências e compilar o projeto:

  • em [1], compilamos o projeto, o que forçará o Maven a baixar as dependências,
  • em [2], elas aparecem então no ramo [Dependencies]. São muitas, já que os frameworks Hibernate e Spring, por si só, possuem inúmeras dependências. Mais uma vez, graças ao Maven, não precisamos nos preocupar com elas. Elas são baixadas automaticamente.

Agora que temos as dependências, copiamos o código do projeto EJB da camada [dao] para o projeto Spring da camada [dao]:

  • em [1], copiamos para o projeto de origem,
  • em [2], cola-se no projeto de destino,
  • em [3], o resultado.

Após a cópia, é preciso corrigir os erros.

4.1.2. O pacote [exceptions]

A classe [RdvMedecinsExceptions] [1] apresenta erros devido ao pacote [javax], linha 4, que não existe mais. Trata-se de um pacote específico do EJB. O erro da linha 6 decorre do erro da linha 4. Eliminamos essas duas linhas. Isso elimina os erros [2].

4.1.3. O pacote [jpa]

  • no [1], a classe [Creneau] apresenta erro devido à ausência do pacote de validação da linha [5]. Seria possível adicionar esse pacote às dependências do projeto. No entanto, durante os testes, o Hibernate lança uma exceção por causa dele. Como não é essencial para nossa aplicação, nós o removemos. Para corrigir a classe, basta excluir todas as linhas com erro [2]. Fazemos isso para todas as classes com erro.

4.1.4. O pacote [dao]

Chegamos ao ponto seguinte:

  • em [1], os dois pacotes corrigidos,
  • em [2], o pacote [dao]. Como não há mais o EJB, também não há mais o conceito de interface remota e local do EJB. Nós os removemos, [3].
  • no [1], os erros da classe [DaoJpa] têm duas origens:
  • a importação de um pacote vinculado a EJB (linhas 6-8);
  • o uso das interfaces local e remota que acabamos de excluir.

Excluímos as linhas com erros e utilizamos a interface [IDao] no lugar das interfaces local e remota [2].

No projeto EJB, a classe [DaoJpa] era um singleton e seus métodos eram executados dentro de uma transação. Veremos que a classe [DaoJpa] será um bean gerenciado pelo Spring. Por padrão, todo bean do Spring é um singleton. Isso quanto à primeira propriedade. A segunda é obtida com a anotação @Transactional do Spring [3]:

Feito isso, o projeto não apresenta mais erros [4].

4.1.5. Configuração da camada [JPA]

No projeto EJB, havíamos configurado a camada [JPA] com o arquivo [persistence.xml]. Aqui, temos uma camada [JPA] e, portanto, precisamos criar esse arquivo. No projeto EJB, nós o geramos com o Glassfish. Aqui, vamos criá-lo manualmente. O principal motivo é que parte da configuração do arquivo [persistence.xml] é migrada para o próprio arquivo de configuração do Spring.

Criamos o arquivo [persistence.xml]:

com o seguinte conteúdo:


<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.0" xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">
  <persistence-unit name="spring-dao-jpa-hibernate-mysqlPU" transaction-type="RESOURCE_LOCAL">
    <class>rdvmedecins.jpa.Client</class>
    <class>rdvmedecins.jpa.Creneau</class>
    <class>rdvmedecins.jpa.Medecin</class>
    <class>rdvmedecins.jpa.Rv</class>
  </persistence-unit>
</persistence>
  • linha 3: atribuímos um nome à unidade de persistência,
  • linha 3: o tipo das transações é RESOURCE_LOCAL. No projeto EJB, era JTA para indicar que as transações eram gerenciadas pelo contêiner EJB. O valor RESOURCE_LOCAL indica que a própria aplicação gerencia suas transações. Esse será o caso aqui por meio do Spring,
  • linhas 4-7: os nomes completos das quatro entidades JPA. Isso é opcional, pois o Hibernate as procura automaticamente no ClassPath do projeto.

É isso. O nome do provedor JPA, suas propriedades e as características JDBC da fonte de dados agora estão no arquivo de configuração do Spring.

4.1.6. O arquivo de configuração do Spring

Mencionamos que a classe [DaoJpa] é um bean gerenciado pelo Spring. Isso é feito por meio de um arquivo de configuração. Esse arquivo também incluirá a configuração do acesso ao banco de dados, bem como o gerenciamento de transações. Ele deve estar no diretório ClassPath do projeto. Nós o colocamos no ramo [Other sources]:

O arquivo [spring-config-dao.xml] é o seguinte:


<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd">

  <!-- camadas de aplicação -->
  <bean id="dao" class="    " />
  
  <!-- EntityManagerFactory -->
  <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
    <property name="dataSource" ref="dataSource" />
    <property name="jpaVendorAdapter">
      <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
        <property name="databasePlatform" value="org.hibernate.dialect.MySQL5InnoDBDialect" />
        <!--
        <property name="showSql" value="true" />
        <property name="generateDdl" value="true" />
        -->
      </bean>
    </property>
  </bean>

  <!-- a fonte de dados DBCP -->
  <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
    <property name="driverClassName" value="com.mysql.jdbc.Driver" />
    <property name="url" value="jdbc:mysql://localhost:3306/dbrdvmedecins2" />
    <property name="username" value="root" />
    <property name="password" value="" />
  </bean>

  <!-- gerenciador de transações -->
  <tx:annotation-driven transaction-manager="txManager" />
  <bean id="txManager" class="org.springframework.orm.jpa.JpaTransactionManager">
    <property name="entityManagerFactory" ref="entityManagerFactory" />
  </bean>

  <!-- tradução de exceções -->
  <bean class="org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor" />

  <!-- persistência -->
  <bean class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor" />

</beans>

Este é um arquivo compatível com o Spring 2.x. Não procuramos utilizar os novos recursos das versões 3.x.

  • linhas 2-4: a tag raiz <beans> do arquivo de configuração. Não comentaremos os diversos atributos dessa tag. É importante ter cuidado ao copiar e colar, pois um erro em qualquer um desses atributos pode causar erros que, às vezes, são difíceis de entender,
  • linha 7: o bean “dao” é uma referência a uma instância da classe [rdvmedecins.dao.DaoJpa]. Será criada uma única instância (singleton) que implementará a camada [dao] da aplicação,
  • linhas 24-29: é definida uma fonte de dados. Ela fornece o serviço de “pool de conexões” de que falamos. Aqui é utilizado o [DBCP] do projeto Apache Commons DBCP [http://jakarta.apache.org/commons/dbcp/],
  • linhas 25-28: para criar conexões com o banco de dados de destino, a fonte de dados precisa saber o driver JDBC utilizado (linha 25), o URL do banco de dados (linha 26), o usuário da conexão e sua senha (linhas 27-28),
  • linhas 10-21: configuram a camada JPA,
  • linha 10: define um bean do tipo [EntityManagerFactory] capaz de criar objetos do tipo [EntityManager] para gerenciar os contextos de persistência. A classe instanciada [LocalContainerEntityManagerFactoryBean] é fornecida pelo Spring. Ela precisa de alguns parâmetros para ser instanciada, definidos nas linhas 11-20,
  • linha 11: a fonte de dados a ser utilizada para obter conexões com o SGBD. Trata-se da fonte [DBCP] definida nas linhas 24 a 29,
  • linhas 12 a 20: a implementação JPA a ser utilizada,
  • linha 13: define o Hibernate como a implementação JPA a ser utilizada,
  • linha 14: o dialeto SQL que o Hibernate deve usar com o SGBD de destino, neste caso MySQL5,
  • linha 16 (comentada): solicita que os comandos SQL executados pelo Hibernate sejam registrados no console,
  • linha 17 (comentada): solicita que, ao iniciar o aplicativo, o banco de dados seja gerado (drop e create),
  • linha 32: indica que as transações são gerenciadas com anotações Java (elas também poderiam ter sido declaradas em spring-config.xml). Trata-se, especificamente, da anotação @Transactional encontrada na classe [DaoJpa],
  • linhas 33-35: definem o gerenciador de transações a ser utilizado,
  • linha 33: o gerenciador de transações é uma classe fornecida pelo Spring,
  • linha 34: o gerenciador de transações do Spring precisa conhecer a classe EntityManagerFactory, que gerencia a camada JPA. É a classe definida nas linhas 10 a 21,
  • linha 41: define a classe que gerencia as anotações de persistência do Spring,
  • linha 38: define a classe Spring que gerencia, entre outras coisas, a anotação @Repository, a qual torna uma classe assim anotada elegível para a conversão das exceções nativas do driver JDBC do SGBD em exceções genéricas do Spring do tipo [DataAccessException]. Essa conversão encapsula a exceção nativa JDBC em um tipo [DataAccessException] que possui várias subclasses:

Image

Essa tradução permite que o programa cliente gerencie as exceções de forma genérica, independentemente do SGBD de destino. Não utilizamos a anotação @Repository em nosso código Java. Portanto, a linha 38 é desnecessária. Nós a mantivemos apenas a título informativo.

Concluímos o arquivo de configuração do Spring. Ele foi extraído da documentação do Spring. Sua adaptação a diversas situações geralmente se resume a duas modificações:

  • a do banco de dados de destino: linhas 24-29,
  • a da implementação JPA: linhas 12 a 20.

Ao executar o código, todos os beans do arquivo de configuração serão instanciados. Veremos como.

4.1.7. A classe de teste JUnit

Tínhamos testado a camada [DAO] do projeto EJB com um teste JUnit. Fazemos o mesmo para a camada [DAO] do projeto Spring:

  • nos projetos [1] e [2], ao copiar e colar o teste JUnit entre os dois projetos,
  • em [3], o teste importado apresenta erros em seu novo ambiente.

O erro relatado [1] é o da interface remota do EJB, que não existe mais. Além disso, o código de inicialização do campo [dao] da linha 19 era uma chamada JNDI específica para o EJB (linhas 25-28). Para instanciar o campo [dao] da linha 19, precisamos utilizar o arquivo de configuração do Spring. Isso é feito da seguinte maneira:

  • linha 21: o tipo da interface passou a ser [IDao],
  • linha 28: instancia todos os beans declarados no arquivo [spring-config-dao.xml], incluindo este:

  <bean id="dao" class="rdvmedecins.dao.DaoJpa" />
  • a linha 29 solicita ao contexto Spring da linha 28 uma referência ao bean com id="dao". Obtém-se, então, uma referência ao singleton [DaoJpa] (class acima) que o Spring instanciou.

As linhas 28-29 constroem os seguintes blocos (linhas pontilhadas em rosa):

Quando os testes do cliente JUnit são executados, a camada [DAO] já foi instanciada. Portanto, é possível testar seus métodos. Observe que não há necessidade de servidor para realizar este teste, ao contrário do teste do EJB [DAO], que exigiu o servidor Glassfish. Aqui, tudo é executado no mesmo JVM.

Agora é possível executar o teste JUnit. É necessário que o servidor MySQL esteja em execução. Os resultados são os seguintes:

O teste JUnit foi bem-sucedido. Vamos examinar os logs do teste, assim como foi feito no teste do EJB:

mai 24, 2012 5:10:29 PM org.springframework.context.support.AbstractApplicationContext prepareRefresh
Infos: Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@67291453: startup date [Thu May 24 17:10:29 CEST 2012]; root of context hierarchy
mai 24, 2012 5:10:29 PM org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
...
mai 24, 2012 5:10:30 PM org.hibernate.annotations.common.Version <clinit>
INFO: HCANN000001: Hibernate Commons Annotations {4.0.1.Final}
mai 24, 2012 5:10:30 PM org.hibernate.Version logVersion
INFO: HHH000412: Hibernate Core {4.1.2}
mai 24, 2012 5:10:30 PM org.hibernate.cfg.Environment <clinit>
...
Infos: Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@6affe94b: defining beans [dao,entityManagerFactory,dataSource,org.springframework.aop.config.internalAutoProxyCreator,org.springframework.transaction.annotation.AnnotationTransactionAttributeSource#0,org.springframework.transaction.interceptor.TransactionInterceptor#0,org.springframework.transaction.config.internalTransactionAdvisor,txManager,org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor#0,org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor#0]; raiz da hierarquia da fábrica
Liste des clients :
Client[1,Mr,Jules,MARTIN]
Client[2,Mme,Christine,GERMAN]
Client[3,Mr,Jules,JACQUARD]
Client[4,Melle,Brigitte,BISTROU]
Liste des médecins :
Médecin[1,Mme,Marie,PELISSIER]
Médecin[2,Mr,Jacques,BROMARD]
Médecin[3,Mr,Philippe,JANDOT]
Médecin[4,Melle,Justine,JACQUEMOT]
Liste des créneaux du médecin Médecin[1,Mme,Marie,PELISSIER]
Creneau [1, 1, 8:0, 8:20,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [2, 1, 8:20, 8:40,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [3, 1, 8:40, 9:0,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [4, 1, 9:0, 9:20,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [5, 1, 9:20, 9:40,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [6, 1, 9:40, 10:0,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [7, 1, 10:0, 10:20,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [8, 1, 10:20, 10:40,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [9, 1, 10:40, 11:0,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [10, 1, 11:0, 11:20,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [11, 1, 11:20, 11:40,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [12, 1, 11:40, 12:0,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [13, 1, 14:0, 14:20,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [14, 1, 14:20, 14:40,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [15, 1, 14:40, 15:0,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [16, 1, 15:0, 15:20,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [17, 1, 15:20, 15:40,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [18, 1, 15:40, 16:0,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [19, 1, 16:0, 16:20,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [20, 1, 16:20, 16:40,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [21, 1, 16:40, 17:0,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [22, 1, 17:0, 17:20,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [23, 1, 17:20, 17:40,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [24, 1, 17:40, 18:0,Médecin[1,Mme,Marie,PELISSIER]]
Liste des créneaux du médecin Médecin[1,Mme,Marie,PELISSIER], le [Thu May 24 17:10:30 CEST 2012]
Rv[211, Creneau [1, 1, 8:0, 8:20,Médecin[1,Mme,Marie,PELISSIER]], Client[4,Melle,Brigitte,BISTROU]]
Ajout d'un Rv le [Thu May 24 17:10:30 CEST 2012] dans le créneau Creneau [3, 1, 8:40, 9:0,Médecin[1,Mme,Marie,PELISSIER]] pour le client Client[1,Mr,Jules,MARTIN]
avant persist : Rv[null, Creneau [3, 1, 8:40, 9:0,Médecin[1,Mme,Marie,PELISSIER]], Client[1,Mr,Jules,MARTIN]]
après persist : Rv[216, Creneau [3, 1, 8:40, 9:0,Médecin[1,Mme,Marie,PELISSIER]], Client[1,Mr,Jules,MARTIN]]
Rv ajouté
mai 24, 2012 5:10:31 PM org.hibernate.engine.jdbc.spi.SqlExceptionHelper logExceptions
Liste des Rv du médecin Médecin[1,Mme,Marie,PELISSIER], le [Thu May 24 17:10:30 CEST 2012]
WARN: SQL Error: 1062, SQLState: 23000
Rv[211, Creneau [1, 1, 8:0, 8:20,Médecin[1,Mme,Marie,PELISSIER]], Client[4,Melle,Brigitte,BISTROU]]
Rv[216, Creneau [3, 1, 8:40, 9:0,Médecin[1,Mme,Marie,PELISSIER]], Client[1,Mr,Jules,MARTIN]]
mai 24, 2012 5:10:31 PM org.hibernate.engine.jdbc.spi.SqlExceptionHelper logExceptions
Ajout d'un Rv le [Thu May 24 17:10:30 CEST 2012] dans le créneau Creneau [3, 1, 8:40, 9:0,Médecin[1,Mme,Marie,PELISSIER]] pour le client Client[1,Mr,Jules,MARTIN]
ERROR: Duplicate entry '2012-05-24-3' for key 'UNQ1_RV'
avant persist : Rv[null, Creneau [3, 1, 8:40, 9:0,Médecin[1,Mme,Marie,PELISSIER]], Client[1,Mr,Jules,MARTIN]]
javax.persistence.PersistenceException: org.hibernate.exception.ConstraintViolationException: Duplicate entry '2012-05-24-3' for key 'UNQ1_RV'
javax.persistence.PersistenceException: org.hibernate.exception.ConstraintViolationException: Duplicate entry '2012-05-24-3' for key 'UNQ1_RV'
javax.persistence.PersistenceException: org.hibernate.exception.ConstraintViolationException: Duplicate entry '2012-05-24-3' for key 'UNQ1_RV'
javax.persistence.PersistenceException: org.hibernate.exception.ConstraintViolationException: Duplicate entry '2012-05-24-3' for key 'UNQ1_RV'
Liste des Rv du médecin Médecin[1,Mme,Marie,PELISSIER], le [Thu May 24 17:10:30 CEST 2012]
Rv[211, Creneau [1, 1, 8:0, 8:20,Médecin[1,Mme,Marie,PELISSIER]], Client[4,Melle,Brigitte,BISTROU]]
Rv[216, Creneau [3, 1, 8:40, 9:0,Médecin[1,Mme,Marie,PELISSIER]], Client[1,Mr,Jules,MARTIN]]
Suppression du Rv ajouté
Rv supprimé
Liste des Rv du médecin Médecin[1,Mme,Marie,PELISSIER], le [Thu May 24 17:10:30 CEST 2012]
Rv[211, Creneau [1, 1, 8:0, 8:20,Médecin[1,Mme,Marie,PELISSIER]], Client[4,Melle,Brigitte,BISTROU]]
  • linhas 1-4: logs do Spring,
  • linhas 5-10: logs do Hibernate,
  • linha 11: o Spring lista todos os beans que instanciou. Em primeiro lugar, encontramos o bean [dao],
  • linhas 12 e seguintes: os logs do teste JUnit,
  • linhas 60-65: vemos claramente a exceção causada pela adição de um compromisso já existente no banco de dados. Vale lembrar que, com o EJB, não tivemos essa exceção devido a um problema de serialização.

A camada [dao] está operacional. Agora, estamos construindo a camada [métier].

4.2. A camada [métier]

Procedemos da mesma forma que para a camada [DAO], copiando e colando do projeto EJB para o projeto Spring.

4.2.1. O projeto NetBeans

Criamos um novo projeto Maven do tipo [Java Application], removendo tudo o que não queremos manter do [1]:

4.2.2. As dependências do projeto

Na arquitetura:

a camada [métier] depende da camada [dao]. Portanto, adicionamos uma dependência do projeto anterior:

  • em [1] e [2], adicionamos uma dependência do projeto da camada [dao],
  • no [3], essa dependência gerou outras dependências, as do projeto da camada [dao].
  • em [1] e [2], copiamos os códigos-fonte Java do projeto EJB para o projeto Spring,
  • para [3]; os códigos-fonte importados apresentam erros no novo ambiente.

Começamos removendo as interfaces remota e local da camada [métier], que não existem mais no [4]:

  • no [5], os erros da classe [Metier] têm várias causas:
    • o uso do pacote [javax.ejb], que não existe mais;
    • o uso da interface [IDaoLocal], que não existe mais;
    • o uso das interfaces [IMetierRemote] e [IMetierLocal], que não existem mais.

Nós

  • excluímos todas as linhas incorretas relacionadas ao pacote [javax.ejb],
  • substituímos a interface [IDaoLocal] pela interface [IDao],
  • substituímos as interfaces [IMetierRemote] e [IMetierLocal] pela interface [IMetier].
  • em [6], a classe assim corrigida,
  • No [7], não há mais erros.

Removemos as referências ao EJB, mas agora precisamos recuperar suas propriedades:

 
  • linha 22: tínhamos um singleton. Essa característica será obtida transformando a classe em um bean gerenciado pelo Spring,
  • linha 23: cada método era executado em uma transação. Isso será obtido com a anotação @Transactional do Spring,
  • linhas 27-28: a referência na camada [DAO] era obtida por injeção do contêiner EJB. Usaremos uma injeção do Spring.

O código da classe [Metier] do projeto Spring passa a ser o seguinte:

Isso é tudo quanto ao código Java. O restante ocorre no arquivo de configuração do Spring.

4.2.3. O arquivo de configuração do Spring

Copiamos o arquivo de configuração do Spring do projeto da camada [DAO] para o projeto da camada [métier]. Começamos criando o ramo [Other Resources] no projeto da camada [métier], caso ele ainda não exista:

  • no [1], na aba [Files], criamos uma subpasta na pasta [main],
  • em [2], ele deve se chamar [resources],
  • em [3], na aba [Projects], o ramo [Other Sources] foi criado.

Podemos prosseguir com o copiar/colar do arquivo de configuração do Spring:

  • em [1], copiamos o arquivo do projeto [DAO] para o projeto [métier] [2],
  • em [3], o arquivo copiado.

O arquivo de configuração que foi copiado configura a camada [DAO]. Adicionamos a ele um bean para configurar a camada [métier]:

1
2
3
4
5
   <!-- camadas de aplicação -->
  <bean id="dao" class="rdvmedecins.dao.DaoJpa" />
  <bean id="metier" class="rdvmedecins.metier.service.Metier">
    <property name="dao" ref="dao"/>
</bean>
  • linha 2: o bean da camada [DAO],
  • linhas 3-5: o bean da camada [métier],
  • linha 3: o bean se chama métier (atributo id) e é uma instância da classe [rdvmedecins.metier.service.Metier] (atributo class). Esse bean será instanciado como os demais ao iniciar o aplicativo.

Vamos relembrar o código do bean [rdvmedecins.metier.service.Metier]:


package rdvmedecins.metier.service;

...

public class Metier implements IMetier, Serializable {

  // camada DAO
  private IDao dao;

  public Metier() {
}
  • linha 8: o campo [dao] será instanciado pelo Spring ao mesmo tempo que o bean métier. Voltemos à definição desse bean no arquivo de configuração do Spring:
1
2
3
4
5
   <!-- camadas de aplicação -->
  <bean id="dao" class="rdvmedecins.dao.DaoJpa" />
  <bean id="metier" class="rdvmedecins.metier.service.Metier">
    <property name="dao" ref="dao"/>
</bean>
  • linha 4: a tag <property> serve para inicializar campos do bean instanciado. O nome do campo é definido pelo atributo name. Portanto, será instanciado o campo `dao` da classe [rdvmedecins.metier.service.Metier]. Isso ocorrerá por meio de um método setDao, que deve existir. O valor que lhe será atribuído é o do atributo `ref`. Esse valor, neste caso, é a referência do bean `dao` da linha 2.

Em termos mais simples, no código:


package rdvmedecins.metier.service;

...

public class Metier implements IMetier, Serializable {

  // camada DAO
  private IDao dao;

  public Metier() {
}

O campo dao da linha 19 será inicializado pelo Spring com uma referência à camada [dao]. Era isso que queríamos. O campo **dao* será inicializado pelo Spring por meio de um *setter, que precisamos adicionar:


  // setter

  public void setDao(IDao dao) {
    this.dao = dao;
}

Renomeamos o arquivo de configuração do Spring para refletir as alterações:

Agora estamos prontos para um teste. Retomamos o teste de console usado para testar o EJB [Metier].

4.2.4. Teste da camada [métier]

O teste será realizado com a seguinte arquitetura:

Copiamos o teste de console do projeto EJB para o projeto Spring:

  • em [1] e [2]; ao copiar e colar entre os dois projetos,
  • no [3], o código importado apresenta erros.
 

O código importado apresenta dois tipos de erro:

  • linha 13: a interface [IMetierRemote] foi substituída pela interface [IMetier],
  • linhas 24-27: a instanciação da camada [métier] não é mais feita por meio de uma chamada JNDI, mas sim pela instanciação dos beans do arquivo de configuração do Spring.

Corrigimos esses dois pontos:

  • linha 22: o arquivo [spring-config-metier-dao.xml] é utilizado. Todos os beans desse arquivo são, então, instanciados. Entre eles, estão os seguintes:

  <!-- camadas de aplicação -->
  <bean id="dao" class="rdvmedecins.dao.DaoJpa" />
  <bean id="metier" class="rdvmedecins.metier.service.Metier">
    <property name="dao" ref="dao"/>
</bean>

Esses dois beans representam as camadas [DAO] e [métier] da arquitetura do teste:

Feito isso, o teste pode ser executado:

  

Os logs do teste são, então, os seguintes:

mai 25, 2012 9:45:07 AM org.springframework.context.support.AbstractApplicationContext prepareRefresh
Infos: Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@22a92801: startup date [Fri May 25 09:45:07 CEST 2012]; root of context hierarchy
mai 25, 2012 9:45:07 AM org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
....
Infos: Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@38a0a058: defining beans [dao,metier,entityManagerFactory,dataSource,org.springframework.aop.config.internalAutoProxyCreator,org.springframework.transaction.annotation.AnnotationTransactionAttributeSource#0,org.springframework.transaction.interceptor.TransactionInterceptor#0,org.springframework.transaction.config.internalTransactionAdvisor,txManager,org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor#0,org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor#0]; raiz da hierarquia da fábrica
Liste des clients :
Client[1,Mr,Jules,MARTIN]
Client[2,Mme,Christine,GERMAN]
Client[3,Mr,Jules,JACQUARD]
Client[4,Melle,Brigitte,BISTROU]
Liste des médecins :
Médecin[1,Mme,Marie,PELISSIER]
Médecin[2,Mr,Jacques,BROMARD]
Médecin[3,Mr,Philippe,JANDOT]
Médecin[4,Melle,Justine,JACQUEMOT]
Liste des créneaux du médecin Médecin[1,Mme,Marie,PELISSIER]
Creneau [1, 1, 8:0, 8:20,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [2, 1, 8:20, 8:40,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [3, 1, 8:40, 9:0,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [4, 1, 9:0, 9:20,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [5, 1, 9:20, 9:40,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [6, 1, 9:40, 10:0,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [7, 1, 10:0, 10:20,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [8, 1, 10:20, 10:40,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [9, 1, 10:40, 11:0,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [10, 1, 11:0, 11:20,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [11, 1, 11:20, 11:40,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [12, 1, 11:40, 12:0,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [13, 1, 14:0, 14:20,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [14, 1, 14:20, 14:40,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [15, 1, 14:40, 15:0,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [16, 1, 15:0, 15:20,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [17, 1, 15:20, 15:40,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [18, 1, 15:40, 16:0,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [19, 1, 16:0, 16:20,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [20, 1, 16:20, 16:40,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [21, 1, 16:40, 17:0,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [22, 1, 17:0, 17:20,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [23, 1, 17:20, 17:40,Médecin[1,Mme,Marie,PELISSIER]]
Creneau [24, 1, 17:40, 18:0,Médecin[1,Mme,Marie,PELISSIER]]
Liste des rendez-vous du médecin Médecin[1,Mme,Marie,PELISSIER], le [Fri May 25 09:45:07 CEST 2012]
Agenda[Médecin[1,Mme,Marie,PELISSIER],25/05/2012, [Creneau [1, 1, 8:0, 8:20,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [2, 1, 8:20, 8:40,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [3, 1, 8:40, 9:0,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [4, 1, 9:0, 9:20,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [5, 1, 9:20, 9:40,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [6, 1, 9:40, 10:0,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [7, 1, 10:0, 10:20,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [8, 1, 10:20, 10:40,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [9, 1, 10:40, 11:0,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [10, 1, 11:0, 11:20,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [11, 1, 11:20, 11:40,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [12, 1, 11:40, 12:0,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [13, 1, 14:0, 14:20,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [14, 1, 14:20, 14:40,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [15, 1, 14:40, 15:0,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [16, 1, 15:0, 15:20,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [17, 1, 15:20, 15:40,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [18, 1, 15:40, 16:0,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [19, 1, 16:0, 16:20,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [20, 1, 16:20, 16:40,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [21, 1, 16:40, 17:0,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [22, 1, 17:0, 17:20,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [23, 1, 17:20, 17:40,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [24, 1, 17:40, 18:0,Médecin[1,Mme,Marie,PELISSIER]] null]]
Ajout d'un Rv le [Fri May 25 09:45:07 CEST 2012] dans le créneau Creneau [3, 1, 8:40, 9:0,Médecin[1,Mme,Marie,PELISSIER]] pour le client Client[1,Mr,Jules,MARTIN]
avant persist : Rv[null, Creneau [3, 1, 8:40, 9:0,Médecin[1,Mme,Marie,PELISSIER]], Client[1,Mr,Jules,MARTIN]]
après persist : Rv[220, Creneau [3, 1, 8:40, 9:0,Médecin[1,Mme,Marie,PELISSIER]], Client[1,Mr,Jules,MARTIN]]
Rv ajouté
Liste des Rv du médecin Médecin[1,Mme,Marie,PELISSIER], le [Fri May 25 09:45:07 CEST 2012]
Rv[220, Creneau [3, 1, 8:40, 9:0,Médecin[1,Mme,Marie,PELISSIER]], Client[1,Mr,Jules,MARTIN]]
Agenda[Médecin[1,Mme,Marie,PELISSIER],25/05/2012, [Creneau [1, 1, 8:0, 8:20,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [2, 1, 8:20, 8:40,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [3, 1, 8:40, 9:0,Médecin[1,Mme,Marie,PELISSIER]] Rv[220, Creneau [3, 1, 8:40, 9:0,Médecin[1,Mme,Marie,PELISSIER]], Client[1,Mr,Jules,MARTIN]]] [Creneau [4, 1, 9:0, 9:20,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [5, 1, 9:20, 9:40,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [6, 1, 9:40, 10:0,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [7, 1, 10:0, 10:20,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [8, 1, 10:20, 10:40,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [9, 1, 10:40, 11:0,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [10, 1, 11:0, 11:20,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [11, 1, 11:20, 11:40,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [12, 1, 11:40, 12:0,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [13, 1, 14:0, 14:20,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [14, 1, 14:20, 14:40,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [15, 1, 14:40, 15:0,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [16, 1, 15:0, 15:20,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [17, 1, 15:20, 15:40,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [18, 1, 15:40, 16:0,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [19, 1, 16:0, 16:20,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [20, 1, 16:20, 16:40,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [21, 1, 16:40, 17:0,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [22, 1, 17:0, 17:20,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [23, 1, 17:20, 17:40,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [24, 1, 17:40, 18:0,Médecin[1,Mme,Marie,PELISSIER]] null]]
Suppression du Rv ajouté
Rv supprimé
Liste des Rv du médecin Médecin[1,Mme,Marie,PELISSIER], le [Fri May 25 09:45:07 CEST 2012]
Agenda[Médecin[1,Mme,Marie,PELISSIER],25/05/2012, [Creneau [1, 1, 8:0, 8:20,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [2, 1, 8:20, 8:40,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [3, 1, 8:40, 9:0,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [4, 1, 9:0, 9:20,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [5, 1, 9:20, 9:40,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [6, 1, 9:40, 10:0,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [7, 1, 10:0, 10:20,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [8, 1, 10:20, 10:40,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [9, 1, 10:40, 11:0,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [10, 1, 11:0, 11:20,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [11, 1, 11:20, 11:40,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [12, 1, 11:40, 12:0,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [13, 1, 14:0, 14:20,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [14, 1, 14:20, 14:40,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [15, 1, 14:40, 15:0,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [16, 1, 15:0, 15:20,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [17, 1, 15:20, 15:40,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [18, 1, 15:40, 16:0,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [19, 1, 16:0, 16:20,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [20, 1, 16:20, 16:40,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [21, 1, 16:40, 17:0,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [22, 1, 17:0, 17:20,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [23, 1, 17:20, 17:40,Médecin[1,Mme,Marie,PELISSIER]] null] [Creneau [24, 1, 17:40, 18:0,Médecin[1,Mme,Marie,PELISSIER]] null]]
  • linhas 1-4: os registros do Spring e do Hibernate,
  • linha 5: os beans instanciados pelo Spring. Destacam-se os beans DAO e de lógica de negócio,
  • linhas 6-53: os logs do teste. Eles estão em conformidade com o que foi obtido no teste do projeto EJB. Remetemos o leitor aos comentários desse teste (parágrafo 3.5.3).

Construímos a camada [métier]. Passamos agora para a última camada, a camada [web].

4.3. A camada [web]

Para construir a camada [web], seguiremos o mesmo procedimento utilizado nas outras duas camadas, ou seja, copiando e colando a partir da camada [web] do projeto EJB.

4.3.1. O projeto NetBeans

Primeiro, criamos um projeto web:

  • no [1], criamos um novo projeto,
  • em [2], um projeto Maven do tipo [Web Application],
  • em [3], atribuímos um nome a ele,
  • em [4], escolhe-se, desta vez, o servidor Tomcat e não o Glassfish, que foi utilizado no projeto EJB,
  • no [5], o projeto obtido,
  • em [6], o projeto após a exclusão de [index.jsp] e do pacote de [Source Packages].

4.3.2. As dependências do projeto

Vamos examinar a arquitetura do projeto:

A camada [web] depende das camadas [métier], [DAO] e [JPA]. Essas fazem parte dos dois projetos que acabamos de compilar. Daí a dependência em relação a cada um desses projetos:

  • no [1], adicionamos a dependência do projeto Spring/negócio,
  • no [2], o projeto Spring / domínio foi adicionado. Como ele próprio tinha uma dependência do projeto Spring / DAO / JPA, este foi automaticamente adicionado às dependências do [3].

Voltemos à estrutura da nossa aplicação:

A camada web é uma camada JSF. Portanto, precisamos das bibliotecas do Java Server Faces. O servidor Tomcat não as possui. A dependência, portanto, não terá o escopo (scope) [provided], como acontecia com o servidor Glassfish, mas sim o escopo [compile], que é o escopo padrão quando não se especifica nenhum escopo.

Adicionamos essas dependências diretamente no código de [pom.xml]:


<dependencies>
    <dependency>
      <groupId>${project.groupId}</groupId>
      <artifactId>mv-rdvmedecins-spring-metier</artifactId>
      <version>${project.version}</version>
    </dependency>
    <dependency>
      <groupId>com.sun.faces</groupId>
      <artifactId>jsf-api</artifactId>
      <version>2.1.7</version>
    </dependency>
    <dependency>
      <groupId>com.sun.faces</groupId>
      <artifactId>jsf-impl</artifactId>
      <version>2.1.7</version>
    </dependency>
    <dependency>
      <groupId>javax</groupId>
      <artifactId>javaee-web-api</artifactId>
      <version>6.0</version>
      <scope>provided</scope>
    </dependency>
  </dependencies>
  • as linhas 7 a 16 foram adicionadas ao arquivo [pom.xml]. Essas são as dependências em relação ao JSF. São as mesmas utilizadas no projeto EJB / Glassfish. Observe que elas não possuem a tag <scope>. Portanto, por padrão, têm o escopo [compile]. A biblioteca JSF será, assim, incorporada ao arquivo [war] do projeto web.

Após adicionar essas dependências ao arquivo [pom.xml], compilamos o projeto para que elas sejam baixadas.

4.3.3. Portação do projeto JSF / Glassfish para o projeto JSF / Tomcat

Copiamos todo o código do projeto JSF / Glassfish para o projeto JSF / Tomcat:

  • [1, 2, 3]: cópia das páginas da web do projeto antigo para o novo,
  • [1, 2, 3]: cópia dos códigos Java do projeto antigo para o novo. Há erros. Isso é normal. Nós os corrigiremos,
  • em [1], na aba [Files] do NetBeans, criamos uma subpasta [resources] na pasta [main],
  • isso cria, na aba [Projects], o ramo [Other Sources] [3],
  • [1, 2, 3]: os arquivos de mensagens do projeto antigo são copiados para o novo projeto.

4.3.4. Alterações no projeto importado

Observamos que o código Java importado continha erros. Vamos analisá-los:

  • em [1], apenas o bean [Application] está com erro;
  • em [2], o erro se deve exclusivamente à interface [IMetierLocal], que não existe mais. Aqui, pode ser surpreendente que a linha 20 não tenha sido sinalizada como erro. A anotação @EJB faz referência explícita a EJB e é reconhecida aqui. Isso se deve à presença da dependência [javaee-web-api-6.0] [3]. O Java EE 6 trouxe consigo uma arquitetura que permite implantar uma aplicação web baseada em EJB sem interface remota, em servidores que não possuem um contêiner EJB. Basta que o servidor forneça a dependência [javaee-web-api-6.0]. De fato, vemos que ela tem o escopo [provided] [3].

Aqui, não vamos utilizar a dependência [javaee-web-api-6.0]. Vamos removê-la, [1]:

Isso gera novos erros: [2]. Vamos começar pelos erros do bean [Form]:

  • no [1], as linhas com erros estão relacionadas à perda do pacote [javax]. Excluímos todas as linhas do [2]. As linhas com erros transformavam a classe [Form] em um bean de escopo de sessão (linhas 18-20 do [1]). Além disso, o bean [Application] era injetado na linha 25. Essas informações serão migradas para o arquivo de configuração de JSF e [faces-config.xml].

Passemos agora ao bean [Application]:

Excluímos todas as linhas com erros do [1] e alteramos a interface das linhas 13 e 21 do [IMetierLocal] para [IMetier]. No [2], não há mais erros. No [1], excluímos as linhas 15 e 16 que transformavam a classe [Application] em um bean de escopo application.. Essa informação será migrada para o arquivo de configuração de JSF [faces-config.xml]. Também removemos a linha 20, que injetava uma referência da camada [métier] no bean. Agora, essa referência será inicializada pelo Spring. Já temos o arquivo de configuração necessário, que é o do projeto Spring / Métier. Vamos copiá-lo:

  • para [1, 2]; copiamos o arquivo de configuração do Spring do projeto Spring / Métier para o projeto Spring / JSF,

Em [3], o resultado.

No bean [Application], é necessário utilizar esse arquivo de configuração para obter uma referência na camada [métier]. Isso é feito no método [init]:


package beans;

...
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Application {

  // camada de negócios
  private IMetier metier;
...

  public Application() {
  }

  @PostConstruct
  public void init() {
    try {
      // instanciação da camada [métier]
      ApplicationContext ctx = new ClassPathXmlApplicationContext("spring-config-metier-dao.xml");
      metier = (IMetier) ctx.getBean("metier");
      // armazenamos médicos e clientes no cache
...
    } catch (Throwable th) {
...
    }
...
  }
  • linha 20: os beans do arquivo de configuração do Spring são instanciados,
  • linha 21: solicita-se uma referência ao bean de negócio, ou seja, à camada [métier].

De modo geral, a instanciação dos beans do Spring deve ser feita no método init do bean de escopo de aplicação. Existe outro método em que a instanciação dos beans é feita por um servlet do Spring. Isso implica alterar o arquivo [web.xml] e adicionar uma dependência do artefato [spring-web]. Não fizemos isso aqui para manter a consistência com o que havia sido utilizado nos códigos anteriores.

Removemos as anotações nas classes [Application] e [Form] que as transformavam em beans JSF. Essas classes devem permanecer como beans JSF. Em vez das anotações, utilizamos o arquivo de configuração de JSF [WEB-INF / faces.config.xml] para declarar os beans.

Este arquivo agora tem o seguinte conteúdo:


<?xml version='1.0' encoding='UTF-8'?>

<!-- =========== FULL CONFIGURATION FILE ================================== -->

<faces-config version="2.0"
              xmlns="http://java.sun.com/xml/ns/javaee" 
              xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
              xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-facesconfig_2_0.xsd">

  <application>
    <!-- o arquivo de mensagens -->
    <resource-bundle>
      <base-name>
        messages
      </base-name>
      <var>msg</var>
    </resource-bundle>
    <message-bundle>messages</message-bundle>
  </application>
    <!-- o bean applicationBean -->
    <managed-bean>
      <managed-bean-name>applicationBean</managed-bean-name>
      <managed-bean-class>beans.Application</managed-bean-class>
      <managed-bean-scope>application</managed-bean-scope>
    </managed-bean>
    <!-- o bean de formulário -->
    <managed-bean>
      <managed-bean-name>form</managed-bean-name>
      <managed-bean-class>beans.Form</managed-bean-class>
      <managed-bean-scope>session</managed-bean-scope>
      <managed-property>
        <property-name>application</property-name>
        <value>#{applicationBean}</value>
      </managed-property>
    </managed-bean>
</faces-config>
  • as linhas 10 a 19 configuram o arquivo de mensagens. Essa era a única configuração que tínhamos no projeto JSF / EJB,
  • as linhas 21 a 35 declaram os beans do aplicativo JSF. Esse era o método padrão com o JSF 1.x. O JSF 2 introduziu as anotações, mas o método do JSF e do 1.x ainda é compatível,
  • linhas 21-25: declaram o bean applicationBean,
  • linha 22: o nome do bean. Pode ser tentador usar o nome “application”. Isso deve ser evitado, pois é o nome de um bean predefinido do JSF,
  • linha 23: o nome completo da classe do bean,
  • linha 24: seu escopo,
  • linhas 27-35: definem o bean form,
  • linha 28: o nome do bean,
  • linha 29: o nome completo da classe do bean,
  • linha 30: seu escopo,
  • linhas 31-34: definem uma propriedade da classe [beans.Form],
  • linha 32: nome da propriedade. A classe [beans.Form] deve ter um campo com esse nome e o setter correspondente,
  • linha 33: o valor do campo. Aqui, trata-se da referência ao bean applicationBean definido na linha 21. Portanto, estamos realizando aqui a injeção do bean de escopo application no bean de escopo session para que este tenha acesso aos dados do escopo application.

Mencionamos anteriormente que o campo [application] do bean [beans.Form] seria inicializado por meio de um setter. Portanto, é necessário adicioná-lo à classe [beans.Form], caso ainda não exista:


public void setApplication(Application application) {
    this.application = application;
  }

4.3.5. Teste do aplicativo

Nosso aplicativo agora está sem erros e pronto para os testes:

  • em [1], o projeto corrigido,
  • em [2], compilamos,
  • em [3], estamos executando-o. É necessário que o SGBD e o MySQL estejam em execução. O servidor Tomcat será então iniciado ([4]), caso ainda não esteja em execução, e, em seguida, a página inicial do aplicativo será exibida ([5]):

A partir daí, encontramos o aplicativo em questão. Deixamos que o leitor verifique se ele está funcionando. Agora, vamos encerrar o aplicativo:

  • em [1], descarregamos o aplicativo,
  • em [2], ela não está mais lá.

Vamos agora examinar os logs de do Tomcat:

1
2
3
4
5
6
mai 25, 2012 2:15:57 PM org.apache.catalina.loader.WebappClassLoader clearReferencesJdbc
Grave: The web application [/mv-rdvmedecins-spring-jsf2] registered the JDBC driver [com.mysql.jdbc.Driver] but failed to unregister it when the web application was stopped. To prevent a memory leak, the JDBC Driver has been forcibly unregistered.
mai 25, 2012 2:15:57 PM org.apache.catalina.loader.WebappClassLoader clearReferencesThreads
Grave: The web application [/mv-rdvmedecins-spring-jsf2] appears to have started a thread named [MySQL Statement Cancellation Timer] but has failed to stop it. This is very likely to create a memory leak.
mai 25, 2012 2:15:59 PM org.apache.catalina.startup.HostConfig checkResources
Infos: Repli (undeploy) de l'application web ayant pour chemin de contexte /mv-rdvmedecins-spring-jsf2

As linhas 2 e 4 indicam uma falha ao encerrar o aplicativo. A linha 4 indica que há um risco provável de vazamento de memória. De fato, isso ocorre e, após algum tempo, o NetBeans fica inutilizável. Esse problema é particularmente irritante, pois é necessário reiniciar o NetBeans a cada nova execução do projeto. Esse problema já foi abordado no documento “Introdução ao Struts 2 por meio de exemplos” [http://tahe.developpez.com/java/struts2].

Há muitas informações na internet sobre esse erro. Ele ocorre quando se carrega e descarrega repetidamente uma aplicação do Tomcat. Após algum tempo, surge o erro java.lang.OutOfMemoryError: PermGen space. Parece que não há solução para evitar esse erro quando ele é causado por arquivos de terceiros (jar), como é o caso aqui. Nesse caso, é necessário reiniciar o Tomcat para que o erro desapareça.

No entanto, é possível adiar a ocorrência desse erro. Em primeiro lugar, aumenta-se o espaço da memória que sofreu o estouro.

  • em [1], acessa-se as propriedades do servidor Tomcat,
  • em [2], na aba [Platform], definimos o valor da memória que está transbordando. Aqui, definimos 1 GB porque tínhamos uma memória total de 8 GB. É possível definir 512M (512 megabytes) com uma memória menor.

Em seguida, coloca-se o driver JDBC de MySQL em <tomcat>/lib, onde <tomcat> é o diretório de instalação do Tomcat.

  • em [1], nas propriedades do Tomcat, anota-se seu diretório de instalação <tomcat>,
  • em <tomcat>/lib [2], coloca-se um driver JDBC mais recente do que MySQL e [3].

Em seguida, remova a dependência que o projeto tinha do driver JDBC em relação a MySQL e [4].

Feito isso, testamos o aplicativo. Verificamos que é possível realizar carregamentos e descarregamentos repetidos do aplicativo. No entanto, os problemas de vazamento de memória não foram resolvidos. Eles simplesmente ocorrem mais tarde.

4.4. Conclusion

Transferimos a aplicação JSF / EJB / Glassfish para um ambiente JSF / Spring / Tomcat. Isso foi feito basicamente por meio de copiar e colar entre os dois projetos. Isso foi possível porque as tecnologias Spring e EJB3 apresentam grandes semelhanças. O EJB3 foi, de fato, criado depois que o Spring se mostrou mais eficiente do que o EJB2. O EJB3, então, incorporou as boas ideias do Spring.

4.5. Testes com o Eclipse

  • no [1], importam-se os três projetos Spring,
  • no [2], seleciona-se o teste JUnit da camada [DAO] e o executa-se no [3],
  • em [4], o teste é bem-sucedido,
  • em [5], os logs do console.
  • em [6A] [6B], executa-se o cliente de console da camada [métier],
  • em [7], a exibição da console obtida,
  • em [8] [9], executa-se o projeto web em um servidor Tomcat 7 [10],
  • e [11], a página inicial do aplicativo é exibida no navegador interno do Eclipse.