Skip to content

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

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

Trata-se, de facto, de uma migração. Partiremos da aplicação anterior e adaptá-la-emos ao novo ambiente. Iremos comentar apenas as alterações. Estas dividem-se em três categorias:

  • o servidor já não é o Glassfish, mas sim o Tomcat, um servidor leve que não possui um contentor 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 iremos recorrer frequentemente à função copiar/colar entre o projeto antigo e o novo, mantemos os projetos anteriores abertos no NetBeans:

  

A utilização do framework Spring requer alguns conhecimentos que se encontram 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 [Java Application]:

  • em [1], o projeto criado,
  • em [2], o mesmo 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, 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 conjunto de ligações Apache Commons DBCP. Um conjunto de ligações é um conjunto de ligações abertas. Quando a aplicação necessita de uma ligação, solicita-a ao conjunto. Quando já não precisa dela, devolve-a. As ligações são abertas no arranque da aplicação e permanecem abertas durante todo o seu ciclo de vida. Isto evita o custo de aberturas e encerramentos repetidos das ligações. Este tipo de pool já existia no Glassfish, mas a sua utilização foi transparente para nós. Será também o caso aqui, mas temos 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 obrigará o Maven a descarregar as dependências,
  • em [2], estas aparecem então no ramo [Dependencies]. São em grande número, uma vez que os frameworks Hibernate e Spring têm, por sua vez, inúmeras dependências. Mais uma vez, graças ao Maven, não precisamos de nos preocupar com elas. São descarregadas 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.

Depois de concluída a cópia, é necessário corrigir os erros.

4.1.2. O pacote [exceptions]

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

4.1.3. O pacote [jpa]

  • em [1], a classe [Creneau] apresenta um erro devido à ausência do pacote de validação da 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 devido a ele. Como não é indispensável para a nossa aplicação, removemo-lo. Para corrigir a classe, basta eliminar todas as linhas com erro [2]. Fazemos isto para todas as classes com erros.

4.1.4. O pacote [dao]

Chegámos ao ponto seguinte:

  • em [1], os dois pacotes corrigidos,
  • em [2], o pacote [dao]. Como já não existe o EJB, também já não existe o conceito de interface remota e local do EJB. Vamos eliminá-los, [3].
  • no [1], os erros da classe [DaoJpa] têm duas origens:
  • a importação de um pacote associado a EJB (linhas 6-8);
  • a utilização das interfaces local e remota que acabámos de eliminar.

Eliminamos as linhas com erros 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 no âmbito de uma transação. Veremos que a classe [DaoJpa] será um bean gerido pelo Spring. Por predefinição, qualquer bean do Spring é um singleton. Eis a primeira propriedade. A segunda é obtida com a anotação @Transactional do Spring [3]:

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

4.1.5. Configuração da camada [JPA]

No projeto EJB, tínhamos configurado a camada [JPA] com o ficheiro [persistence.xml]. Aqui temos uma camada [JPA] e, por isso, temos de criar esse ficheiro. No projeto EJB, tínhamos-o gerado com o Glassfish. Aqui, criamo-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 das transações é 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 própria aplicação gere as suas transações. Será esse o caso aqui, através do Spring,
  • linhas 4-7: os nomes completos das quatro entidades JPA. Isto é opcional, pois o Hibernate procura-as automaticamente no ClassPath do projeto.

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

4.1.6. O ficheiro de configuração do Spring

Já referimos que a classe [DaoJpa] é um bean gerido pelo Spring. Isto é feito através de um ficheiro de configuração. Este ficheiro irá incluir também a configuração do acesso à base de dados, bem como a gestão das transações. Deve estar no diretório ClassPath do projeto. Colocamo-lo no ramo [Other sources]:

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">

  <!-- 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>

  <!-- o gestor 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>

Trata-se de um ficheiro compatível com o Spring 2.x. Não procurámos utilizar as novas funcionalidades das versões 3.x.

  • linhas 2-4: a baliza raiz <beans> do ficheiro de configuração. Não comentamos os vários atributos desta baliza. É importante ter cuidado ao copiar e colar, pois um erro num destes atributos provoca erros por vezes 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» de que falámos. É o [DBCP] do projeto Apache Commons DBCP [http://jakarta.apache.org/commons/dbcp/] que é aqui utilizado,
  • linhas 25-28: para estabelecer ligações com a base de dados de destino, a fonte de dados precisa de saber o controlador JDBC utilizado (linha 25), o URL da base de dados (linha 26), o utilizador da ligação e a sua palavra-passe (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 gerir os contextos de persistência. A classe instanciada [LocalContainerEntityManagerFactoryBean] é fornecida pelo Spring. Necessita de um determinado número de parâmetros para ser instanciada, definidos nas linhas 11-20,
  • linha 11: a fonte de dados a utilizar para obter ligações ao SGBD. Trata-se da fonte [DBCP] definida nas linhas 24 a 29,
  • linhas 12 a 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 os comandos SQL executados pelo Hibernate sejam registados na consola,
  • linha 17 (comentada): solicita que, ao iniciar a aplicação, a base de dados seja gerada (drop e create),
  • linha 32: indica que as transações são geridas com anotações Java (também poderiam ter sido declaradas em spring-config.xml). Trata-se, em particular, da anotação @Transactional presente na classe [DaoJpa],
  • linhas 33-35: definem o gestor de transações a utilizar,
  • linha 33: o gestor de transações é uma classe fornecida pelo Spring,
  • linha 34: o gestor de transações do Spring precisa de conhecer a classe EntityManagerFactory, que gere a camada JPA. É a classe definida 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 gere, nomeadamente, a anotação @Repository, a qual torna uma classe assim anotada elegível para a conversão das exceções nativas do controlador JDBC do SGBD em exceções genéricas Spring do tipo [DataAccessException]. Esta conversão encapsula a exceção nativa JDBC num tipo [DataAccessException] com várias subclasses:

Image

Esta conversão permite que o programa cliente lide com as 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 isso, a linha 38 é desnecessária. Deixámo-la apenas a título informativo.

Terminámos o ficheiro de configuração do Spring. Este foi extraído da documentação do Spring. A sua adaptação a diversas situações resume-se frequentemente a duas alterações:

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

Ao executar o código, todos os beans do ficheiro 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 [1] e [2], ao copiar e colar o teste JUnit entre os dois projetos,
  • em [3], o teste importado apresenta erros no seu novo ambiente.

O erro sinalizado [1] refere-se à interface remota do EJB, que já não existe. Além disso, o código de inicialização do campo [dao] da linha 19 era uma chamada JNDI específica do EJB (linhas 25-28). Para instanciar o campo [dao] da linha 19, temos de utilizar o ficheiro de configuração do Spring. Isso faz-se da seguinte forma:

  • linha 21: o tipo da interface passou a ser [IDao],
  • linha 28: instancia todos os beans declarados no ficheiro [spring-config-dao.xml], nomeadamente 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 a rosa):

Quando os testes do cliente JUnit são executados, a camada [DAO] foi instanciada. Assim, é possível testar os seus métodos. Note-se que não é necessário um servidor para realizar este teste, ao contrário do teste do EJB [DAO], que exigiu o servidor Glassfish. Aqui, tudo é executado no próprio 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 analisar os registos do teste, tal 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: registos do Spring,
  • linhas 5-10: registos do Hibernate,
  • linha 11: o Spring indica todos os beans que instanciou. Encontramos aqui, em primeiro lugar, o bean [dao],
  • linhas 12 e seguintes: os registos do teste JUnit,
  • linhas 60-65: vê-se claramente a exceção provocada pela adição de um compromisso já existente na base de dados. Recorde-se que, com o EJB, não se verificou esta exceção devido a um problema de serialização.

A camada [dao] está operacional. Estamos agora a construir 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], limpo de tudo o que não queremos manter do [1]:

4.2.2. As dependências do projeto

Na arquitetura:

a camada [métier] assenta na camada [dao]. Por isso, adicionamos uma dependência do projeto anterior:

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

Começa-se por eliminar as interfaces remota e local da camada [métier], que já não existem em [4]:

  • no [5], os erros da classe [Metier] 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 incorretas 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], a classe assim corrigida,
  • No [7], já não há erros.

Eliminámos as referências ao EJB, mas agora precisamos de recuperar as suas propriedades:

 
  • linha 22: tínhamos um singleton. Esta característica será obtida transformando a classe num bean gerido pelo Spring,
  • linha 23: cada método decorria numa transação. Isto será conseguido com a anotação @Transactional do Spring,
  • linhas 27-28: a referência na camada [DAO] era obtida por injeção do contentor EJB. Iremos utilizar uma injeção do Spring.

O código da classe [Metier] do projeto Spring passa, assim, a ter a 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 [métier]. Começamos por criar o ramo [Other Resources] no projeto da camada [métier], caso ainda não exista:

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

Podemos passar à operação de copiar/colar do ficheiro de configuração do Spring:

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

O ficheiro de configuração que foi copiado configura a camada [DAO]. Adicionamos-lhe 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 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 no arranque da aplicação.

Recorde-se 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 deste bean no ficheiro 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 baliza <property> serve para inicializar campos do bean instanciado. O nome do campo é indicado pelo atributo name. Será, portanto, o campo `dao` da classe [rdvmedecins.metier.service.Metier] que será instanciado. A instanciação será feita através de um método setDao, que deve existir. O valor que lhe será atribuído é o do atributo `ref`. Este 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 pretendíamos. O campo dao será inicializado pelo Spring através de um setter que temos 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 um teste. Retomamos o teste de consola utilizado para testar o EJB [Metier].

4.2.4. Teste da camada [métier]

O teste decorrerá com a seguinte arquitetura:

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

  • para [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] já não é feita através de uma chamada JNDI, mas sim através da instanciação dos beans do ficheiro de configuração do Spring.

Corrigimos estes dois pontos:

  • linha 22: o ficheiro [spring-config-metier-dao.xml] é utilizado. Todos os beans deste ficheiro são então instanciados. Entre eles, encontram-se 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>

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

Feito isto, o teste pode decorrer:

  

Os registos 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 registos do Spring e do Hibernate,
  • linha 5: os beans instanciados pelo Spring. Destacam-se os beans DAO e de negócio,
  • linhas 6-53: os registos do teste. Estão em conformidade com o que se obteve com o teste do projeto EJB. Remetemos o leitor para os comentários desse teste (parágrafo 3.5.3).

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

4.3. A camada [web]

Para criar a camada [web], vamos proceder 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

Começamos por criar um projeto web:

  • no [1], criamos um novo projeto,
  • em [2], um projeto Maven do tipo [Web Application],
  • em [3], atribuímos-lhe um nome,
  • 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 eliminação de [index.jsp] e do pacote de [Source Packages].

4.3.2. As dependências do projeto

Vejamos a arquitetura do projeto:

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

  • no [1], adicionamos a dependência do projeto Spring / domínio de negócio,
  • no [2], o projeto Spring / negócio foi adicionado. Como este, por sua vez, 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. Por isso, precisamos das bibliotecas do Java Server Faces. O servidor Tomcat não as possui. A dependência não terá, portanto, o âmbito (scope) [provided], como acontecia com o servidor Glassfish, mas sim o âmbito [compile], que é o âmbito por predefinição quando não se especifica nenhum âmbito.

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

Depois de adicionar estas dependências ao ficheiro [pom.xml], compilamos o projeto para que sejam descarregadas.

4.3.3. Portagem 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 web do projeto antigo para o novo,
  • [1, 2, 3]: cópia dos códigos Java do projeto antigo para o novo. Existem erros. É normal. Iremos corrigi-los,
  • em [1], no separador [Files] do NetBeans, cria-se uma subpasta [resources] na pasta [main],
  • o que cria, no separador [Projects], o ramo [Other Sources] [3],
  • [1, 2, 3]: os ficheiros de mensagens do projeto antigo são copiados para o novo projeto.

4.3.4. Alterações no projeto importado

Referimos 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 surpreender o facto de a linha 20 não ser assinalada como errada. A anotação @EJB faz referência explícita a EJB e é aqui reconhecida. Isto deve-se à presença da dependência [javaee-web-api-6.0] [3]. O Java EE 6 trouxe consigo uma arquitetura que permite implementar uma aplicação web baseada em EJB sem interface remota, em servidores que não dispõem de um contentor EJB. Basta que o servidor forneça a dependência [javaee-web-api-6.0]. De facto, verifica-se que esta tem o âmbito [provided] [3].

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

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

  • no [1], as linhas com erros estão relacionadas com a perda do pacote [javax]. Eliminamos todas as linhas do [2]. As linhas com erros transformavam a classe [Form] num bean de âmbito de sessão (linhas 18-20 do [1]). Além disso, o bean [Application] era injetado na linha 25. Estas informações serão transferidas para o ficheiro de configuração de JSF e [faces-config.xml].

Passemos agora ao bean [Application]:

Eliminam-se todas as linhas com erros do [1] e altera-se a interface das linhas 13 e 21 do [IMetierLocal] para [IMetier]. No [2], já não há erros. No [1], eliminámos as linhas 15-16 que transformavam a classe [Application] num bean de âmbito application.. Esta informação será transferida para o ficheiro de configuração de JSF [faces-config.xml]. Também eliminámos a linha 20, que injetava uma referência da camada [métier] no bean. Agora, esta será inicializada pelo Spring. Já dispomos do ficheiro de configuração necessário, que é o do projeto Spring / Métier. Copiamo-lo:

  • para [1, 2]; copiamos o ficheiro 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 este ficheiro de configuração para obter uma referência à camada [métier]. Isto é feito no seu método [init]:


package beans;

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

public class Application {

  // camada de negócio
  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 os médicos e os clientes em cache
...
    } catch (Throwable th) {
...
    }
...
  }
  • linha 20: os beans do ficheiro 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 um modo geral, a instanciação dos beans do Spring deve ser feita no método init do bean de âmbito da aplicação. Existe outro método em que a instanciação dos beans é feita por um servlet do Spring. Isto implica alterar o ficheiro [web.xml] e adicionar uma dependência do artefacto [spring-web]. Não o fizemos aqui para nos mantermos em consonância com o que tinha sido utilizado nos códigos anteriores.

Eliminámos as anotações nas classes [Application] e [Form] que as transformavam em beans JSF. Estas classes devem continuar a ser beans JSF. Em vez das anotações, utiliza-se então o ficheiro de configuração de JSF [WEB-INF / faces.config.xml] para declarar os beans.

Este ficheiro tem agora 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 ficheiro 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 do 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-19 configuram o ficheiro de mensagens. Esta era a única configuração que tínhamos no projeto JSF / EJB,
  • as linhas 21-35 declaram os beans da aplicação JSF. Este 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 continua a ser suportado,
  • linhas 21-25: declaram o bean applicationBean,
  • linha 22: o nome do bean. Poderíamos sentir-nos tentados a usar o nome «application». Deve evitar-se, pois é o nome de um bean predefinido do JSF,
  • linha 23: o nome completo da classe do bean,
  • linha 24: o seu âmbito,
  • linhas 27-35: definem o bean «form»,
  • 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 bean applicationBean definido na linha 21. Estamos, portanto, a realizar a injeção do bean de âmbito application no bean de âmbito session, para que este tenha acesso aos dados do âmbito application.

Já referimos anteriormente que o campo [application] do bean [beans.Form] seria inicializado através de um setter. Por isso, é necessário adicioná-lo à classe [beans.Form], caso ainda não exista:


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

4.3.5. Teste da aplicação

A nossa aplicação está agora sem erros e pronta para os testes:

  • em [1], o projeto corrigido,
  • em [2], compilamo-lo,
  • em [3], executamo-lo. É necessário que o SGBD e o MySQL estejam a ser executados. O servidor Tomcat será então iniciado ([4]), caso ainda não o estivesse, e, em seguida, a página inicial da aplicação será apresentada ([5]):

A partir daqui, voltamos à aplicação em análise. Deixamos que o leitor verifique se esta funciona. Agora, vamos encerrar a aplicação:

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

Vejamos agora os registos 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 a aplicação. A linha 4 indica que existe um risco provável de fuga de memória. De facto, isso acontece e, passado algum tempo, o NetBeans deixa de estar utilizável. Este problema é particularmente irritante, pois obriga a reiniciar o NetBeans sempre que se executa o projeto. Este problema já foi abordado no documento «Introdução ao Struts 2 através de exemplos» [http://tahe.developpez.com/java/struts2].

Encontram-se na Internet muitas informações sobre este erro. Este surge quando se carrega e descarrega repetidamente uma aplicação do Tomcat. Após algum tempo, obtém-se o erro java.lang.OutOfMemoryError: PermGen space. Parece não haver solução para evitar este erro quando este provém de arquivos de terceiros (jar), como é o caso aqui. É, portanto, necessário reiniciar o Tomcat para que o erro desapareça.

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

  • em [1], acede-se às propriedades do servidor Tomcat,
  • em [2], no separador [Platform], definimos o valor da memória que está a transbordar. Neste caso, definimos 1 GB porque tínhamos uma memória total de 8 GB. Poderemos definir 512M (512 megabytes) se a memória for menor.

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

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

Em seguida, remove-se a dependência que o projeto tinha do controlador JDBC em relação a MySQL e [4].

Feito isto, testamos a aplicação. Verificamos que é possível carregar e descarregar a aplicação repetidamente. No entanto, os problemas de fuga de memória não estão resolvidos. Simplesmente surgem mais tarde.

4.4. Conclusion

Transferimos a aplicação JSF / EJB / Glassfish para um ambiente JSF / Spring / Tomcat. Isto foi feito essencialmente através de copiar e colar entre os dois projetos. Isto foi possível porque as tecnologias Spring e EJB3 apresentam grandes semelhanças. De facto, o EJB3 foi criado depois de o Spring ter demonstrado um desempenho superior ao dos EJB2. O EJB3 incorporou então 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 executa-se-o no [3],
  • em [4], o teste é bem-sucedido,
  • em [5], os registos da consola.
  • em [6A] [6B], executa-se o cliente da consola da camada [métier],
  • em [7], o ecrã da consola obtido,
  • em [8] [9], executa-se o projeto web num servidor Tomcat 7 [10],
  • e [11], a página inicial da aplicação é apresentada no navegador interno do Eclipse.