Skip to content

4. Aplicação de exemplo – 02: rdvmedecins-jsf2-spring

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

Trata-se, de facto, de uma adaptação. Começaremos pela aplicação anterior e iremos adaptá-la ao novo ambiente. Iremos apenas comentar as alterações. Existem três alterações principais:

  • o servidor já não é o GlassFish, mas sim o Tomcat, um servidor leve que não possui um contentor EJB;
  • para substituir os EJBs, utilizaremos o Spring, o principal concorrente do EJB [http://www.springsource.com/],
  • a implementação JPA utilizada será o Hibernate em vez do EclipseLink.

Uma vez que iremos fazer muitas operações de copiar e colar entre os projetos antigos e novos, manteremos os projetos anteriores abertos no NetBeans:

  

A utilização do framework Spring requer determinados conhecimentos, que podem ser consultados em [ref7] (ver página 166).

4.1. As camadas [DAO] e [JPA]

4.1.1. O projeto NetBeans

Estamos a criar um projeto Maven do tipo [Aplicação Java]:

  • em [1], o projeto criado,
  • em [2], o mesmo projeto com os [Pacotes de código-fonte] e [Pacotes de teste] removidos, juntamente com a dependência [junit-3.8.1].

A parte mais difícil dos projetos Maven é encontrar as dependências certas. 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 controlador 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 a aplicação precisa de uma conexão, solicita-a ao pool. Quando já não precisa dela, devolve-a. As ligações são abertas quando a aplicação arranca e permanecem abertas durante todo o tempo de vida da aplicação. Isto evita a sobrecarga de abrir e fechar ligações repetidamente. Este tipo de pool existia no GlassFish, mas a sua utilização era transparente para nós. Este também será o caso aqui, mas precisamos de o instalar e configurar,
  • linhas 52–75: para o Spring.

Vamos adicionar estas dependências e compilar o projeto:

  • em [1], compilamos o projeto, o que forçará o Maven a descarregar as dependências;
  • em [2], estas aparecem então no ramo [Dependencies]. São muitas, uma vez que as próprias estruturas Hibernate e Spring têm inúmeras dependências. Mais uma vez, graças ao Maven, não precisamos de nos preocupar com isto. São descarregadas automaticamente.

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

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

Assim que a cópia estiver concluída, é necessário corrigir os erros.

4.1.2. O pacote [exceptions]

A classe [RdvMedecinsExceptions] [1] apresenta erros porque o pacote [javax] na linha 4 já não existe. Trata-se de um pacote específico do EJB. O erro na linha 6 decorre do erro na linha 4. Eliminamos estas duas linhas. Isto resolve os erros [2].

4.1.3. O pacote [jpa]

  • Em [1], a classe [Creneau] está incorreta devido à ausência do pacote de validação na linha [5]. Poderíamos ter adicionado este pacote às dependências do projeto. No entanto, durante os testes, o Hibernate lança uma exceção por causa disso. Uma vez que não é essencial para a nossa aplicação, removemo-lo. Para corrigir a classe, basta eliminar todas as linhas incorretas [2]. Fazemos isto para todas as classes incorretas.

4.1.4. O pacote [dao]

Estamos agora no seguinte ponto:

  • em [1], os dois pacotes corrigidos,
  • em [2], o pacote [dao]. Uma vez que já não existem EJBs, os conceitos de interfaces remotas e locais de EJB já não se aplicam. Removemo-los [3].
  • Em [1], os erros na classe [DaoJpa] têm duas causas:
  • a importação de um pacote relacionado com EJBs (linhas 6–8);
  • a utilização das interfaces locais e remotas que acabámos de remover.

Removemos as linhas erradas e utilizamos a interface [IDao] em vez das interfaces local e remota [2].

No projeto EJB, a classe [DaoJpa] era um singleton e os seus métodos eram executados dentro de uma transação. Veremos que a classe [DaoJpa] será um bean gerido pelo Spring. Por predefinição, todos os beans do Spring são singletons. Isso cobre a primeira propriedade. A segunda é alcançada utilizando a anotação @Transactional do Spring [3]:

Com isto feito, o projeto já não apresenta erros [4].

4.1.5. Configurar a camada [JPA]

No projeto EJB, configurámos a camada [JPA] utilizando o ficheiro [persistence.xml]. Aqui temos uma camada [JPA], pelo que precisamos de criar este ficheiro. No projeto EJB, gerámo-lo utilizando o GlassFish. Aqui, estamos a criá-lo manualmente. A principal razão para isso é que parte da configuração do ficheiro [persistence.xml] é migrada para o próprio ficheiro de configuração do Spring.

Criamos o ficheiro [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 de transação é RESOURCE_LOCAL. No projeto EJB, era JTA para indicar que as transações eram geridas pelo contentor EJB. O valor RESOURCE_LOCAL indica que a aplicação gere as suas próprias transações. Este será o caso aqui através do Spring,
  • linhas 4–7: os nomes completos das quatro entidades JPA. Isto é opcional porque o Hibernate as procura automaticamente no ClassPath do projeto.

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

4.1.6. O ficheiro de configuração do Spring

Mencionámos que a classe [DaoJpa] é um bean gerido pelo Spring. Isto é feito através de um ficheiro de configuração. Este ficheiro conterá também a configuração de acesso à base de dados, bem como a gestão de transações. Deve estar no ClassPath do projeto. Colocamo-lo na pasta [Outras fontes]:

O ficheiro [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">
 
  <!-- application layers -->
  <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>
 
  <!-- data source 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>
 
  <!-- transaction manager -->
  <tx:annotation-driven transaction-manager="txManager" />
  <bean id="txManager" class="org.springframework.orm.jpa.JpaTransactionManager">
    <property name="entityManagerFactory" ref="entityManagerFactory" />
  </bean>

  <!-- translation of exceptions -->
  <bean class="org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor" />
 
  <!-- persistence -->
  <bean class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor" />
 
</beans>

Este é um ficheiro compatível com o Spring 2.x. Não tentámos utilizar as novas funcionalidades das versões 3.x.

  • Linhas 2–4: a tag raiz <beans> do ficheiro de configuração. Não iremos comentar os vários atributos desta tag. Certifique-se de que copia e cola com cuidado, pois um erro em qualquer um destes atributos pode causar erros que, por vezes, são difíceis de compreender.
  • 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. Esta fornece o serviço de «pool de ligações» que mencionámos anteriormente. O [DBCP] do projeto Apache Commons DBCP [http://jakarta.apache.org/commons/dbcp/] é utilizado aqui,
  • linhas 25–28: para estabelecer ligações com a base de dados de destino, a fonte de dados precisa de saber o controlador JDBC que está a ser utilizado (linha 25), o URL da base de dados (linha 26), o nome de utilizador da ligação e a sua palavra-passe (linhas 27–28),
  • linhas 10–21: configurar a camada JPA,
  • linha 10: define um bean [EntityManagerFactory] capaz de criar objetos [EntityManager] para gerir contextos de persistência. A classe instanciada [LocalContainerEntityManagerFactoryBean] é fornecida pelo Spring. Requer vários parâmetros para se instanciar, definidos nas linhas 11–20,
  • linha 11: a fonte de dados a utilizar para obter ligações ao SGBD. Esta é a fonte [DBCP] definida nas linhas 24–29,
  • linhas 12–20: a implementação JPA a utilizar,
  • linha 13: define o Hibernate como a implementação JPA a utilizar,
  • linha 14: o dialeto SQL que o Hibernate deve utilizar com o SGBD de destino, neste caso o MySQL5,
  • linha 16 (comentada): solicita que as instruções SQL executadas pelo Hibernate sejam registadas na consola,
  • linha 17 (comentada): solicita que a base de dados seja gerada (drop e create) quando a aplicação for iniciada,
  • linha 32: especifica que as transações são geridas utilizando anotações Java (também poderiam ter sido declaradas no spring-config.xml). Especificamente, trata-se da anotação @Transactional encontrada na classe [DaoJpa],
  • linhas 33–35: definem o gestor de transações a ser utilizado,
  • linha 33: o gestor de transações é uma classe fornecida pelo Spring,
  • linha 34: o gestor de transações do Spring precisa de conhecer o EntityManagerFactory que gere a camada JPA. Este é o definido nas linhas 10–21,
  • linha 41: define a classe que gere as anotações de persistência do Spring,
  • Linha 38: define a classe Spring que lida, entre outras coisas, com a anotação @Repository, a qual torna uma classe assim anotada elegível para a conversão de exceções nativas do controlador JDBC do SGBD em exceções genéricas do Spring do tipo [DataAccessException]. Esta conversão encapsula a exceção nativa do JDBC num tipo [DataAccessException] que possui várias subclasses:

Image

Esta tradução permite que o programa cliente lide com exceções de forma genérica, independentemente do SGBD de destino. Não utilizámos a anotação @Repository no nosso código Java. Por conseguinte, a linha 38 é desnecessária. Mantivemo-la apenas para fins informativos.

Terminámos o ficheiro de configuração do Spring. Foi retirado da documentação do Spring. Adaptá-lo a várias situações resume-se frequentemente a duas modificações:

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

Quando o código for executado, todos os beans no ficheiro de configuração serão instanciados. Vamos ver como.

4.1.7. A classe de teste JUnit

Testámos a camada [DAO] do projeto EJB com um teste JUnit. Faremos o mesmo para a camada [DAO] do projeto Spring:

  • em [1] e [2], copiando e colando o teste JUnit entre os dois projetos,
  • em [3], o teste importado apresenta erros no seu novo ambiente.

O erro relatado em [1] é que a interface remota do EJB já não existe. Além disso, o código de inicialização para o campo [dao] na linha 19 era uma chamada JNDI específica do EJB (linhas 25–28). Para instanciar o campo [dao] na linha 19, precisamos de utilizar o ficheiro de configuração do Spring. Isto é feito da seguinte forma:

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

  <bean id="dao" class="rdvmedecins.dao.DaoJpa" />
  • A linha 29 solicita uma referência ao bean com id="dao" do contexto Spring na linha 28. Obtemos então uma referência ao singleton [DaoJpa] (classe acima) que o Spring instanciou.

As linhas 28–29 constroem os seguintes blocos (linhas pontilhadas a rosa):

Quando os testes do cliente JUnit são executados, a camada [DAO] já foi instanciada. Podemos, portanto, testar os seus métodos. Note-se que não é necessário nenhum servidor para executar este teste, ao contrário do teste EJB [DAO], que exigia o servidor Glassfish. Aqui, tudo é executado dentro da mesma JVM.

Podemos agora executar o teste JUnit. O servidor MySQL deve estar em execução. Os resultados são os seguintes:

O teste JUnit foi aprovado. Vamos examinar os registos do teste, tal como fizemos durante o teste 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]; root of factory hierarchy
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: Registos Spring,
  • linhas 5-10: registos do Hibernate,
  • linha 11: O Spring reporta todos os beans que instanciou. Primeiro, vemos o bean [dao],
  • linhas 12 e seguintes: registos de teste JUnit,
  • linhas 60–65: vemos claramente a exceção causada pela adição de um compromisso que já existe na base de dados. Recorde-se que, com o EJB, não encontrámos esta exceção devido a um problema de serialização.

A camada [dao] está operacional. Vamos agora construir a camada [business].

4.2. A camada [business]

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

Estamos a criar um novo projeto Maven do tipo [Aplicação Java], sem nada do que não queremos manter [1]:

4.2.2. Dependências do projeto

Na arquitetura:

a camada [de negócios] depende da camada [DAO]. Por isso, adicionamos uma dependência do projeto anterior:

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

Começamos por remover as interfaces remotas e locais da camada [business] que já não existem [4]:

  • Em [5], os erros na classe [Business] têm várias causas:
    • a utilização do pacote [javax.ejb], que já não existe;
    • a utilização da interface [IDaoLocal], que já não existe;
    • a utilização das interfaces [IMetierRemote] e [IMetierLocal], que já não existem.

Nós

  • eliminamos todas as linhas erradas relacionadas com o pacote [javax.ejb],
  • substituímos a interface [IDaoLocal] pela interface [IDao],
  • substituímos as interfaces [IMetierRemote] e [IMetierLocal] pela interface [IMetier].
  • Em [6], com a classe corrigida conforme descrito,
  • em [7], já não existem erros.

Removemos as referências aos EJBs, mas agora precisamos de recuperar as suas propriedades:

 
  • linha 22: tínhamos um singleton. Este comportamento será alcançado tornando a classe um bean gerido pelo Spring,
  • linha 23: cada método era executado dentro de uma transação. Isto será conseguido utilizando a anotação @Transactional do Spring,
  • linhas 27–28: a referência à camada [DAO] foi obtida através da injeção do contentor EJB. Iremos utilizar a injeção do Spring.

O código para a classe [Metier] no projeto Spring evolui, portanto, da seguinte forma:

É tudo no que diz respeito ao código Java. O resto ocorre no ficheiro de configuração do Spring.

4.2.3. O ficheiro de configuração do Spring

Copiamos o ficheiro de configuração do Spring do projeto da camada [DAO] para o projeto da camada [Business]. Começamos por criar o ramo [Outros Recursos] no projeto da camada [Business], caso ainda não exista:

  • Em [1], no separador [Ficheiros], crie uma subpasta na pasta [main],
  • em [2], deve ser nomeada [resources],
  • em [3], no separador [Projectos], foi criada a ramificação [Outras fontes].

Agora podemos copiar e colar o ficheiro de configuração do Spring:

  • em [1], copie o ficheiro do projeto [DAO] para o projeto [business] [2],
  • em [3], o ficheiro copiado.

O ficheiro de configuração que foi copiado configura a camada [DAO]. Adicionamos um bean a este ficheiro para configurar a camada [business]:

1
2
3
4
5
  <!-- application layers -->
  <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 [business],
  • linha 3: o bean chama-se métier (atributo id) e é uma instância da classe [rdvmedecins.metier.service.Metier] (atributo class). Este bean será instanciado tal como os outros quando a aplicação for iniciada.

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


package rdvmedecins.metier.service;
 
...
 
public class Metier implements IMetier, Serializable {
 
  // dao layer
  private IDao dao;
 
  public Metier() {
}
  • Linha 8: O campo [dao] será instanciado pelo Spring ao mesmo tempo que o bean de negócios. Voltemos à definição deste bean no ficheiro de configuração do Spring:
1
2
3
4
5
  <!-- application layers -->
  <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> é utilizada para inicializar os campos do bean instanciado. O nome do campo é especificado pelo atributo name. Assim, o campo dao da classe [rdvmedecins.metier.service.Metier] será instanciado. Isto é feito através de um método setDao, que deve existir. O valor que lhe é atribuído é o do atributo ref. Aqui, este valor é a referência ao bean dao da linha 2.

Em termos mais simples, no código:


package rdvmedecins.metier.service;
 
...
 
public class Metier implements IMetier, Serializable {
 
  // dao layer
  private IDao dao;
 
  public Metier() {
}

O campo dao na linha 19 será inicializado pelo Spring com uma referência à camada [dao]. Era isso que pretendíamos. O campo dao será inicializado pelo Spring através de um setter que precisamos de adicionar:


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

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

Estamos agora prontos para executar um teste. Vamos utilizar o teste de consola utilizado para testar o EJB [Business].

4.2.4. Testar a camada [business]

O teste será executado com a seguinte arquitetura:

Copiamos o teste da consola do projeto EJB para o projeto Spring:

  • Em [1] e [2], faça a cópia e a colagem entre os dois projetos;
  • em [3], o código importado contém erros.
 

O código importado contém dois tipos de erros:

  • linha 13: a interface [IMetierRemote] foi substituída pela interface [IMetier],
  • linhas 24–27: a camada [business] já não é instanciada através de uma chamada JNDI, mas sim através da instanciação dos beans a partir do ficheiro de configuração do Spring.

Corrigimos estas duas questões:

  • linha 22: é utilizado o ficheiro [spring-config-metier-dao.xml]. Todos os beans neste ficheiro são então instanciados. Entre eles encontram-se os seguintes:

  <!-- application layers -->
  <bean id="dao" class="rdvmedecins.dao.DaoJpa" />
  <bean id="metier" class="rdvmedecins.metier.service.Metier">
    <property name="dao" ref="dao"/>
</bean>

Estes dois beans representam as camadas [DAO] e [business] da arquitetura de teste:

Depois de fazer isto, o teste pode ser executado:

  

Os registos dos testes sã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]; root of factory hierarchy
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: registos do Spring e do Hibernate,
  • linha 5: beans instanciados pelo Spring. Observe os beans DAO e de negócios,
  • linhas 6–53: os registos de teste. Estes correspondem aos resultados obtidos no teste do projeto EJB. Remetemos o leitor para os comentários sobre esse teste (secção 3.5.3).

Construímos a camada [de negócios]. Passamos agora para a camada final, a camada [web].

4.3. A camada [web]

Para criar a camada [web], procederemos da mesma forma que para as outras duas camadas, copiando e colando a partir da camada [web] do projeto EJB.

4.3.1. O projeto NetBeans

Primeiro, criamos um projeto web:

  • em [1], crie um novo projeto,
  • em [2], um projeto Maven do tipo [Aplicação Web],
  • em [3], atribuímos-lhe um nome,
  • em [4], desta vez escolhemos o servidor Tomcat em vez do GlassFish, que foi utilizado para o projeto EJB,
  • em [5], o projeto resultante,
  • em [6], o projeto após a remoção do [index.jsp] e do pacote [Source Packages].

4.3.2. Dependências do projeto

Vejamos a arquitetura do projeto:

A camada [web] depende das camadas [business], [DAO] e [JPA]. Estas fazem parte dos dois projetos que acabámos de construir. Por isso, existe uma dependência de cada um destes projetos:

  • em [1], adicionamos a dependência do projeto Spring / business,
  • em [2], o projeto Spring / business foi adicionado. Uma vez que este, por sua vez, tinha uma dependência do projeto Spring / DAO / JPA, este foi automaticamente adicionado às dependências [3].

Voltemos à estrutura da nossa aplicação:

A camada web é uma camada JSF. Por isso, precisamos das bibliotecas Java Server Faces. O servidor Tomcat não as possui. A dependência não terá, portanto, um âmbito [provided], como acontecia com o servidor Glassfish, mas um âmbito [compile], que é o âmbito padrão quando nenhum âmbito é especificado.

Adicionamos estas dependências diretamente no ficheiro [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–16 foram adicionadas ao ficheiro [pom.xml]. Estas são as dependências para o JSF. São as mesmas utilizadas no projeto EJB/GlassFish. Note-se que não possuem a tag <scope>. Por conseguinte, têm o âmbito [compile] por predefinição. A biblioteca JSF será, assim, incorporada no arquivo [war] do projeto web.

Após adicionar estas dependências ao ficheiro [pom.xml], compilamos o projeto para que elas sejam descarregadas.

4.3.3. Portar o 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]: copiamos as páginas web do projeto antigo para o novo,
  • [1, 2, 3]: copiar o código Java do projeto antigo para o novo. Existem erros. Isto é normal. Vamos corrigi-los,
  • em [1], no separador [Ficheiros] do NetBeans, crie uma subpasta [resources] dentro da pasta [main],
  • isto cria o ramo [Outras fontes] no separador [Projetos] [3],
  • [1, 2, 3]: Copie os ficheiros de mensagens do projeto antigo para o novo projeto.

4.3.4. Alterações ao projeto importado

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

  • Em [1], apenas o bean [Application] está incorreto;
  • em [2], o erro deve-se exclusivamente à interface [IMetierLocal], que já não existe. Aqui, pode ser surpreendente que a linha 20 não seja sinalizada como um erro. A anotação @EJB refere-se explicitamente a EJBs e é reconhecida aqui. Isto deve-se à presença da dependência [javaee-web-api-6.0] [3]. O Java EE 6 introduziu uma arquitetura que permite a implementação de uma aplicação web baseada em EJBs sem uma interface remota em servidores que não possuem um contentor EJB. O servidor precisa simplesmente de fornecer a dependência [javaee-web-api-6.0]. Podemos ver que esta dependência tem o âmbito [provided] [3].

Aqui, não utilizaremos a dependência [javaee-web-api-6.0]. Removemo-la [1]:

Isto causa novos erros [2]. Comecemos pelos que se encontram no bean [Form]:

  • Em [1], as linhas erradas estão relacionadas com a perda do pacote [javax]. Removemos todas elas [2]. As linhas erradas tornavam a classe [Form] um bean com escopo de sessão (linhas 18–20 de [1]). Além disso, o bean [Application] foi injetado na linha 25. Esta informação será movida para o ficheiro de configuração JSF [faces-config.xml].

Passemos agora ao bean [Application]:

Removemos todas as linhas erradas de [1] e alteramos a interface [IMetierLocal] nas linhas 13 e 21 para [IMetier]. Em [2], já não há erros. Em [1], removemos as linhas 15–16, que tornavam a classe [Application] um bean com escopo de aplicação. Esta informação será transferida para o ficheiro de configuração JSF [faces-config.xml]. Também removemos a linha 20, que injetava uma referência da camada [business] no bean. Agora, isto será inicializado pelo Spring. Já temos o ficheiro de configuração necessário; é o do projeto Spring / Business. Copiamo-lo:

  • em [1, 2], copiamos o ficheiro de configuração do Spring do projeto Spring / Business para o projeto Spring / JSF,

Em [3], o resultado.

No bean [Application], precisamos de utilizar este ficheiro de configuração para obter uma referência à camada [business]. Isto é feito no seu método [init]:


package beans;
 
...
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
 
public class Application {
 
  // business layer
  private IMetier metier;
...
 
  public Application() {
  }
 
  @PostConstruct
  public void init() {
    try {
      // instantiation layer [business]
      ApplicationContext ctx = new ClassPathXmlApplicationContext("spring-config-metier-dao.xml");
      metier = (IMetier) ctx.getBean("metier");
      // caching doctors and customers
...
    } catch (Throwable th) {
...
    }
...
  }
  • linha 20: os beans do ficheiro de configuração do Spring são instanciados,
  • linha 21: é solicitada uma referência para o bean de negócios, ou seja, na camada [business].

De um modo geral, os beans do Spring devem ser instanciados no método init do bean com âmbito de aplicação. Existe outro método em que os beans são instanciados por um servlet do Spring. Isto envolve modificar o ficheiro [web.xml] e adicionar uma dependência do artefacto [spring-web]. Não o fizemos aqui para manter a consistência com o que foi utilizado no código anterior.

Removemos as anotações nas classes [Application] e [Form] que as tornavam beans JSF. Estas classes devem permanecer como beans JSF. Em vez de anotações, agora usamos o ficheiro de configuração JSF [WEB-INF/faces.config.xml] para declarar os beans.

Este ficheiro tem agora o seguinte aspeto:


<?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>
    <!-- message file -->
    <resource-bundle>
      <base-name>
        messages
      </base-name>
      <var>msg</var>
    </resource-bundle>
    <message-bundle>messages</message-bundle>
  </application>
    <!-- the applicationBean bean -->
    <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>
    <!-- the bean form -->
    <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–19 configuram o ficheiro de mensagens. Esta foi a única configuração que tivemos no projeto JSF/EJB,
  • As linhas 21–35 declaram os beans da aplicação JSF. Esta era a abordagem padrão no JSF 1.x. O JSF 2 introduziu as anotações, mas o método do JSF 1.x continua a ser suportado,
  • Linhas 21–25: declaram o applicationBean,
  • linha 22: o nome do bean. Pode sentir-se tentado a usar o nome «application». Evite isso, pois é o nome de um bean JSF predefinido,
  • linha 23: o nome completo da classe do bean,
  • linha 24: o seu âmbito,
  • linhas 27–35: definam o bean do formulário,
  • linha 28: o nome do bean,
  • linha 29: o nome completo da classe do bean,
  • linha 30: o seu âmbito,
  • linhas 31–34: definem uma propriedade da classe [beans.Form],
  • linha 32: nome da propriedade. A classe [beans.Form] deve ter um campo com este nome e o setter correspondente,
  • linha 33: o valor do campo. Aqui, trata-se da referência ao applicationBean definido na linha 21. Estamos, assim, a injetar o bean com âmbito de aplicação no bean com âmbito de sessão, para que este último tenha acesso aos dados com âmbito de aplicação.

Mencionámos anteriormente que o campo [application] do bean [beans.Form] seria inicializado através de um setter. Devemos, portanto, adicioná-lo à classe [beans.Form] caso ainda não exista:


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

4.3.5. Testar a aplicação

A nossa aplicação está agora sem erros e pronta para ser testada:

  • em [1], o projeto corrigido,
  • em [2], compilamo-lo,
  • em [3], executamo-lo. O SGBD MySQL deve estar em execução. O servidor Tomcat será então iniciado [4], caso ainda não o estivesse, e a página inicial da aplicação será apresentada [5]:

A partir daí, vemos a aplicação que temos vindo a estudar. Deixaremos que o leitor verifique se funciona. Agora, vamos parar a aplicação:

  • em [1], descarregamos a aplicação,
  • em [2], já não está lá.

Agora, vamos analisar os registos do Tomcat em :

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 um mau funcionamento quando a aplicação é encerrada. A linha 4 indica que existe um risco provável de fuga de memória. De facto, isso ocorre e, após algum tempo, o NetBeans torna-se inutilizável. Este problema é particularmente frustrante porque o NetBeans tem de ser reiniciado sempre que o projeto é executado. Esta questão já foi abordada no documento «Introduction to Struts 2 by Example» [http://tahe.developpez.com/java/struts2].

Há muita informação disponível online sobre este erro. 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 não haver solução para evitar este erro quando este tem origem em bibliotecas de terceiros (JARs), como é o caso aqui. É então necessário reiniciar o Tomcat para o resolver.

No entanto, é possível adiar a ocorrência deste erro. Primeiro, aumente o espaço de memória que ficou esgotado.

  • Em [1], aceda às propriedades do servidor Tomcat,
  • em [2], no separador [Plataforma], defina o valor para o limite de memória. Aqui, definimo-lo para 1 GB porque tínhamos um total de 8 GB de memória. Pode defini-lo para 512 MB (512 megabytes) com uma quantidade menor de memória.

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

  • Em [1], nas propriedades do Tomcat, anote o seu diretório de instalação <tomcat>,
  • em <tomcat>/lib [2], coloque um controlador JDBC do MySQL recente [3].

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

Depois de fazer isso, teste a aplicação. Verá que pode carregar e descarregar a aplicação repetidamente. No entanto, os problemas de fuga de memória não estão resolvidos; simplesmente ocorrem mais tarde.

4.4. Conclusão

Migrámos a aplicação JSF/EJB/GlassFish para um ambiente JSF/Spring/Tomcat. Isto foi feito principalmente através de cópia e colagem entre os dois projetos. Tal foi possível porque o Spring e o EJB3 partilham muitas semelhanças. O EJB3 foi, de facto, criado depois de o Spring ter provado ser mais eficiente do que o EJB2. O EJB3 incorporou então as melhores funcionalidades do Spring.

4.5. Testes com o Eclipse

  • Em [1], importe os três projetos Spring,
  • em [2], selecione o teste JUnit da camada [DAO] e execute-o em [3],
  • em [4], o teste é aprovado,
  • em [5], o console regista.
  • Em [6A] [6B], execute o cliente de consola para a camada [business],
  • em [7], a saída resultante da consola,
  • Em [8] [9], executamos o projeto web num servidor Tomcat 7 [10],
  • em [11], a página inicial da aplicação é apresentada no navegador interno do Eclipse.