Skip to content

4. Aplicación de ejemplo – 02: rdvmedecins-jsf2-spring

Ahora nos proponemos portar la aplicación anterior a un entorno Spring / Tomcat:

Se trata, en realidad, de una adaptación. Partiremos de la aplicación anterior y la adaptaremos al nuevo entorno. Solo comentaremos las modificaciones. Estas son de tres tipos:

  • el servidor ya no es GlassFish, sino Tomcat, un servidor ligero que no cuenta con un contenedor EJB,
  • para reemplazar los EJB, utilizaremos Spring, el principal competidor de los EJB y [http://www.springsource.com/],
  • la implementación JPA que se utilizará será Hibernate en lugar de EclipseLink.

Como vamos a realizar muchas operaciones de copiar y pegar entre el proyecto anterior y el nuevo, mantenemos abiertos los proyectos anteriores en NetBeans:

  

El uso del marco Spring requiere ciertos conocimientos que se pueden encontrar en [ref7] (véase la página 166).

4.1. Las capas [DAO] y [JPA]

4.1.1. El proyecto NetBeans

Creamos un proyecto Maven del tipo [Java Application]:

  • en [1], el proyecto creado,
  • en [2], el mismo sin los paquetes de [Source Packages] y [Test Packages], y sin la dependencia [junit-3.8.1].

Lo más difícil en los proyectos de Maven es encontrar las dependencias correctas. Para este proyecto Spring / JPA / Hibernate, estas son las siguientes:


<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>
  • líneas 18-29: para Hibernate,
  • líneas 30-34: para el controlador JDBC de MySQL,
  • líneas 35-41: para la prueba JUnit,
  • líneas 42-51: para el grupo de conexiones de Apache Commons DBCP. Un grupo de conexiones es un conjunto de conexiones abiertas. Cuando la aplicación necesita una conexión, la solicita al grupo. Cuando ya no la necesita, la devuelve. Las conexiones se abren al iniciar la aplicación y permanecen abiertas durante toda la vida útil de la aplicación. Esto evita el costo de abrir y cerrar conexiones repetidamente. Este tipo de grupo ya existía en Glassfish, pero su uso fue transparente para nosotros. También será así aquí, pero debemos instalarlo y configurarlo,
  • líneas 52-75: para Spring.

Agreguemos estas dependencias y compilemos el proyecto:

  • en [1], al compilar el proyecto se obligará a Maven a descargar las dependencias,
  • en [2], estas aparecen entonces en la rama [Dependencies]. Son muchas, ya que los marcos Hibernate y Spring tienen a su vez numerosas dependencias. Una vez más, gracias a Maven, no tenemos que preocuparnos por ellas. Se descargan automáticamente.

Ahora que contamos con las dependencias, pegamos el código del proyecto EJB de la capa [dao] en el proyecto Spring de la capa [dao]:

  • en [1], se copia en el proyecto de origen,
  • en [2], se pega en el proyecto de destino,
  • en [3], el resultado.

Una vez realizada la copia, hay que corregir los errores.

4.1.2. El paquete [exceptions]

La clase [RdvMedecinsExceptions] [1] presenta errores debido al paquete [javax], línea 4, que ya no existe. Se trata de un paquete específico de EJB. El error de la línea 6 se deriva del de la línea 4. Se eliminan estas dos líneas. Esto elimina los errores [2].

4.1.3. El paquete [jpa]

  • en [1], la clase [Creneau] presenta un error debido a la falta del paquete de validación de la línea [5]. Se podría haber agregado este paquete a las dependencias del proyecto. Sin embargo, durante las pruebas, Hibernate genera una excepción debido a él. Como no es indispensable para nuestra aplicación, lo hemos eliminado. Para corregir la clase, basta con eliminar todas las líneas con error [2]. Hacemos esto para todas las clases con error.

4.1.4. El paquete [dao]

Estamos en el siguiente punto:

  • en [1], los dos paquetes corregidos,
  • en [2], el paquete [dao]. Como ya no existe el EJB, tampoco existe ya el concepto de interfaz remota y local del EJB. Los eliminamos: [3].
  • En [1], los errores de la clase [DaoJpa] tienen dos causas:
  • la importación de un paquete vinculado a EJB (líneas 6-8);
  • el uso de las interfaces local y remota que acabamos de eliminar.

Eliminamos las líneas erróneas y utilizamos la interfaz [IDao] en lugar de las interfaces local y remota [2].

En el proyecto EJB, la clase [DaoJpa] era un singleton y sus métodos se ejecutaban dentro de una transacción. Veremos que la clase [DaoJpa] será un bean administrado por Spring. Por defecto, todo bean de Spring es un singleton. Hasta aquí la primera propiedad. La segunda se obtiene con la anotación @Transactional de Spring [3]:

Una vez hecho esto, el proyecto ya no presenta errores [4].

4.1.5. Configuración de la capa [JPA]

En el proyecto EJB, habíamos configurado la capa [JPA] con el archivo [persistence.xml]. Aquí tenemos una capa [JPA], por lo que debemos crear ese archivo. En el proyecto EJB, lo habíamos generado con Glassfish. Aquí lo creamos manualmente. La razón principal es que parte de la configuración del archivo [persistence.xml] se migra al propio archivo de configuración de Spring.

Creamos el archivo [persistence.xml]:

con el siguiente contenido:


<?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>
  • línea 3: le damos un nombre a la unidad de persistencia,
  • línea 3: el tipo de transacciones es RESOURCE_LOCAL. En el proyecto EJB, era JTA para indicar que las transacciones eran administradas por el contenedor EJB. El valor RESOURCE_LOCAL indica que la aplicación gestiona sus propias transacciones. Este será el caso aquí a través de Spring,
  • líneas 4-7: los nombres completos de las cuatro entidades JPA. Esto es opcional, ya que Hibernate los busca automáticamente en el ClassPath del proyecto.

Eso es todo. El nombre del proveedor JPA, sus propiedades y las características JDBC de la fuente de datos ya se encuentran en el archivo de configuración de Spring.

4.1.6. El archivo de configuración de Spring

Ya mencionamos que la clase [DaoJpa] es un bean administrado por Spring. Esto se logra mediante un archivo de configuración. Este archivo también incluirá la configuración del acceso a la base de datos, así como la gestión de transacciones. Debe estar en el directorio ClassPath del proyecto. Lo colocamos en la rama [Other sources]:

El archivo [spring-config-dao.xml] es el siguiente:


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

  <!-- capas de aplicación -->
  <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>

  <!-- la fuente de datos 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>

  <!-- el administrador de transacciones -->
  <tx:annotation-driven transaction-manager="txManager" />
  <bean id="txManager" class="org.springframework.orm.jpa.JpaTransactionManager">
    <property name="entityManagerFactory" ref="entityManagerFactory" />
  </bean>

  <!-- traducción de excepciones -->
  <bean class="org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor" />

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

</beans>

Es un archivo compatible con Spring 2.x. No hemos intentado utilizar las nuevas características de las versiones 3.x.

  • líneas 2-4: la etiqueta raíz <beans> del archivo de configuración. No comentaremos los diversos atributos de esta etiqueta. Hay que tener cuidado al copiar y pegar, ya que un error en cualquiera de estos atributos provoca errores que a veces son difíciles de entender,
  • línea 7: el bean «dao» es una referencia a una instancia de la clase [rdvmedecins.dao.DaoJpa]. Se creará una única instancia (singleton) que implementará la capa [dao] de la aplicación,
  • líneas 24-29: se define una fuente de datos. Esta proporciona el servicio de «pool de conexiones» del que hemos hablado. Aquí se utiliza [DBCP] del proyecto Apache Commons DBCP [http://jakarta.apache.org/commons/dbcp/],
  • líneas 25-28: para establecer conexiones con la base de datos de destino, la fuente de datos necesita conocer el controlador JDBC utilizado (línea 25), el URL de la base de datos (línea 26), el usuario de la conexión y su contraseña (líneas 27-28),
  • líneas 10-21: configuran la capa JPA,
  • línea 10: define un bean de tipo [EntityManagerFactory] capaz de crear objetos de tipo [EntityManager] para gestionar los contextos de persistencia. La clase instanciada [LocalContainerEntityManagerFactoryBean] es proporcionada por Spring. Requiere una serie de parámetros para su instanciación, definidos en las líneas 11-20,
  • línea 11: la fuente de datos que se utilizará para obtener conexiones con SGBD. Se trata de la fuente [DBCP] definida en las líneas 24 a 29,
  • líneas 12 a 20: la implementación JPA que se utilizará,
  • línea 13: define a Hibernate como la implementación JPA que se debe utilizar,
  • línea 14: el dialecto SQL que Hibernate debe utilizar con el SGBD de destino, en este caso MySQL5,
  • línea 16 (comentada): solicita que las órdenes SQL ejecutadas por Hibernate se registren en la consola,
  • línea 17 (comentada): indica que, al iniciar la aplicación, se genere la base de datos (drop y create),
  • línea 32: indica que las transacciones se administran con anotaciones de Java (también podrían haberse declarado en spring-config.xml). En particular, se trata de la anotación @Transactional que aparece en la clase [DaoJpa],
  • líneas 33-35: definen el administrador de transacciones que se utilizará,
  • línea 33: el administrador de transacciones es una clase proporcionada por Spring,
  • línea 34: el administrador de transacciones de Spring necesita conocer la clase EntityManagerFactory que administra la capa JPA. Es la que se define en las líneas 10-21,
  • línea 41: define la clase que maneja las anotaciones de persistencia de Spring,
  • línea 38: definen la clase Spring que gestiona, entre otras cosas, la anotación @Repository, la cual hace que una clase así anotada sea elegible para la conversión de las excepciones nativas del controlador JDBC de SGBD a excepciones genéricas de Spring del tipo [DataAccessException]. Esta conversión encapsula la excepción nativa JDBC en un tipo [DataAccessException] que cuenta con varias subclases:

Image

Esta conversión permite al programa cliente manejar las excepciones de manera genérica, independientemente del SGBD de destino. No hemos utilizado la anotación @Repository en nuestro código Java. Por lo tanto, la línea 38 es innecesaria. La hemos dejado solo a modo informativo.

Hemos terminado con el archivo de configuración de Spring. Se ha extraído de la documentación de Spring. Su adaptación a diversas situaciones a menudo se resume en dos modificaciones:

  • la de la base de datos de destino: líneas 24-29,
  • la de la implementación JPA: líneas 12-20.

Al ejecutar el código, se instanciarán todos los beans del archivo de configuración. Veremos cómo.

4.1.7. La clase de prueba JUnit

Habíamos probado la capa [DAO] del proyecto EJB con una prueba JUnit. Hacemos lo mismo para la capa [DAO] del proyecto Spring:

  • en [1] y [2], al copiar y pegar la prueba JUnit entre ambos proyectos,
  • en [3], la prueba importada presenta errores en su nuevo entorno.

El error reportado [1] corresponde a la interfaz remota de EJB, que ya no existe. Además, el código de inicialización del campo [dao] de la línea 19 era una llamada a JNDI específica de EJB (líneas 25-28). Para instanciar el campo [dao] de la línea 19, debemos utilizar el archivo de configuración de Spring. Esto se hace de la siguiente manera:

  • línea 21: el tipo de la interfaz ahora es [IDao],
  • línea 28: instancia todos los beans declarados en el archivo [spring-config-dao.xml], en particular este:

  <bean id="dao" class="rdvmedecins.dao.DaoJpa" />
  • la línea 29 solicita al contexto de Spring de la línea 28 una referencia al bean que tiene id="dao". De este modo, se obtiene una referencia al singleton [DaoJpa] (class arriba) que Spring ha instanciado.

Las líneas 28 y 29 construyen los siguientes bloques (líneas punteadas de color rosa):

Cuando se ejecutan las pruebas del cliente JUnit, la capa [DAO] ya ha sido instanciada. Por lo tanto, se pueden probar sus métodos. Cabe señalar que no se necesita un servidor para realizar esta prueba, a diferencia de la prueba de EJB [DAO], que requirió el servidor Glassfish. Aquí, todo se ejecuta en el mismo JVM.

Ahora podemos ejecutar la prueba JUnit. Es necesario que el servidor MySQL esté en ejecución. Los resultados son los siguientes:

La prueba JUnit se realizó con éxito. Analicemos los registros de la prueba tal como se hizo durante la prueba de 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]; raíz de la jerarquía de 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]]
  • líneas 1-4: registros de Spring,
  • líneas 5-10: registros de Hibernate,
  • línea 11: Spring reporta todos los beans que ha instanciado. En primer lugar, aparece el bean [dao],
  • líneas 12 y siguientes: los registros de la prueba JUnit,
  • líneas 60-65: se ve claramente la excepción provocada por agregar una cita que ya existe en la base de datos. Recordemos que con el EJB no se había producido esta excepción debido a un problema de serialización.

La capa [dao] está operativa. Ahora estamos desarrollando la capa [métier].

4.2. La capa [métier]

Procedemos de la misma manera que con la capa [DAO], copiando y pegando del proyecto EJB al proyecto Spring.

4.2.1. El proyecto NetBeans

Creamos un nuevo proyecto Maven del tipo [Java Application], del que eliminamos todo lo que no queremos conservar de [1]:

4.2.2. Las dependencias del proyecto

En la arquitectura:

la capa [métier] se basa en la capa [dao]. Por lo tanto, agregamos una dependencia del proyecto anterior:

  • en [1] y [2], se agrega una dependencia del proyecto de la capa [dao],
  • en [3], esta dependencia ha generado otras dependencias, las del proyecto de la capa [dao].
  • en [1] y [2], se copian los archivos fuente Java del proyecto EJB al proyecto Spring,
  • en [3], los archivos fuente importados presentan errores en su nuevo entorno.

Comenzamos eliminando las interfaces remota y local de la capa [métier], que ya no existen en [4]:

  • En [5], los errores de la clase [Metier] tienen varias causas:
    • el uso del paquete [javax.ejb], que ya no existe;
    • el uso de la interfaz [IDaoLocal], que ya no existe;
    • el uso de las interfaces [IMetierRemote] y [IMetierLocal], que ya no existen.

Vamos a

  • eliminamos todas las líneas erróneas relacionadas con el paquete [javax.ejb],
  • reemplazamos la interfaz [IDaoLocal] por la interfaz [IDao],
  • reemplazamos las interfaces [IMetierRemote] y [IMetierLocal] por la interfaz [IMetier].
  • por [6], la clase así corregida,
  • En [7] ya no hay errores.

Hemos eliminado las referencias a EJB, pero ahora necesitamos recuperar sus propiedades:

 
  • línea 22: teníamos un singleton. Esta característica se logrará convirtiendo la clase en un bean administrado por Spring,
  • línea 23: cada método se ejecutaba en una transacción. Esto se logrará con la anotación de Spring @Transactional,
  • líneas 27-28: la referencia en la capa [DAO] se obtenía mediante la inyección del contenedor EJB. Utilizaremos una inyección de Spring.

Por lo tanto, el código de la clase [Metier] del proyecto Spring queda así:

Eso es todo en cuanto al código Java. El resto se lleva a cabo en el archivo de configuración de Spring.

4.2.3. El archivo de configuración de Spring

Copiamos el archivo de configuración de Spring del proyecto de la capa [DAO] al proyecto de la capa [métier]. Comenzamos creando la rama [Other Resources] en el proyecto de la capa [métier] si aún no existe:

  • en [1], en la pestaña [Files], creamos una subcarpeta dentro de la carpeta [main],
  • en [2], debe llamarse [resources],
  • en [3], en la pestaña [Projects], se ha creado la rama [Other Sources].

Podemos pasar a copiar y pegar el archivo de configuración de Spring:

  • en [1], copiamos el archivo del proyecto [DAO] al proyecto [métier] [2],
  • en [3], el archivo copiado.

El archivo de configuración que se copió configura la capa [DAO]. Le agregamos un bean para configurar la capa [métier]:

1
2
3
4
5
   <!-- capas de aplicación -->
  <bean id="dao" class="rdvmedecins.dao.DaoJpa" />
  <bean id="metier" class="rdvmedecins.metier.service.Metier">
    <property name="dao" ref="dao"/>
</bean>
  • línea 2: el bean de la capa [DAO],
  • líneas 3-5: el bean de la capa [métier],
  • línea 3: el bean se llama métier (atributo id) y es una instancia de la clase [rdvmedecins.metier.service.Metier] (atributo class). Este bean se instanciará como los demás al iniciar la aplicación.

Recordemos el código del bean [rdvmedecins.metier.service.Metier]:


package rdvmedecins.metier.service;

...

public class Metier implements IMetier, Serializable {

  // capa DAO
  private IDao dao;

  public Metier() {
}
  • línea 8: el campo [dao] será instanciado por Spring al mismo tiempo que el bean métier. Volvamos a la definición de este bean en el archivo de configuración de Spring:
1
2
3
4
5
   <!-- capas de aplicación -->
  <bean id="dao" class="rdvmedecins.dao.DaoJpa" />
  <bean id="metier" class="rdvmedecins.metier.service.Metier">
    <property name="dao" ref="dao"/>
</bean>
  • línea 4: la etiqueta <property> sirve para inicializar los campos del bean instanciado. El nombre del campo se indica mediante el atributo name. Por lo tanto, se instanciará el campo dao de la clase [rdvmedecins.metier.service.Metier]. Esto se hará mediante un método setDao que debe existir. El valor que se le asignará es el del atributo ref. Este valor es, en este caso, la referencia del bean dao de la línea 2.

En términos más sencillos, en el código:


package rdvmedecins.metier.service;

...

public class Metier implements IMetier, Serializable {

  // capa DAO
  private IDao dao;

  public Metier() {
}

El campo dao de la línea 19 será inicializado por Spring con una referencia a la capa [dao]. Eso es lo que queríamos. El campo dao será inicializado por Spring mediante un setter que debemos agregar:


  // setter

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

Renombramos el archivo de configuración de Spring para reflejar los cambios:

Ahora estamos listos para una prueba. Volvemos a utilizar la prueba de consola que se usó para probar el EJB [Metier].

4.2.4. Prueba de la capa [métier]

La prueba se llevará a cabo con la siguiente arquitectura:

Copiamos la prueba de consola del proyecto EJB al proyecto Spring:

  • en [1] y [2], al copiar y pegar entre ambos proyectos,
  • en [3], el código importado presenta errores.
 

El código importado presenta dos tipos de error:

  • línea 13: la interfaz [IMetierRemote] ha sido reemplazada por la interfaz [IMetier],
  • líneas 24-27: la instanciación de la capa [métier] ya no se realiza mediante una llamada a JNDI, sino mediante la instanciación de los beans del archivo de configuración de Spring.

Corregimos estos dos puntos:

  • línea 22: se utiliza el archivo [spring-config-metier-dao.xml]. A continuación, se instancian todos los beans de este archivo. Entre ellos, se encuentran los siguientes:

  <!-- capas de aplicación -->
  <bean id="dao" class="rdvmedecins.dao.DaoJpa" />
  <bean id="metier" class="rdvmedecins.metier.service.Metier">
    <property name="dao" ref="dao"/>
</bean>

Estos dos beans representan las capas [DAO] y [métier] de la arquitectura de la prueba:

Una vez hecho esto, la prueba puede ejecutarse:

  

Los registros de la prueba son los siguientes:

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]; raíz de la jerarquía de 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]]
  • líneas 1-4: los registros de Spring y Hibernate,
  • línea 5: los beans instanciados por Spring. Cabe destacar los beans DAO y de negocio,
  • líneas 6-53: los registros de la prueba. Son consistentes con lo que se obtuvo en la prueba del proyecto EJB. Remitimos al lector a los comentarios de esa prueba (párrafo 3.5.3).

Hemos construido la capa [métier]. Pasamos a la última capa, la capa [web].

4.3. La capa [web]

Para crear la capa [web], procederemos de la misma manera que con las otras dos capas, copiando y pegando desde la capa [web] del proyecto EJB.

4.3.1. El proyecto NetBeans

Primero creamos un proyecto web:

  • En [1], creamos un nuevo proyecto,
  • en [2], un proyecto Maven del tipo [Web Application],
  • en [3], le damos un nombre,
  • en [4], esta vez elegimos el servidor Tomcat y no Glassfish, que se utilizó en el proyecto EJB,
  • en [5], el proyecto resultante,
  • en [6], el proyecto después de eliminar [index.jsp] y el paquete de [Source Packages].

4.3.2. Las dependencias del proyecto

Veamos la arquitectura del proyecto:

La capa [web] necesita las capas [métier], [DAO] y [JPA]. Estas forman parte de los dos proyectos que acabamos de compilar. De ahí que exista una dependencia hacia cada uno de estos proyectos:

  • en [1], agregamos la dependencia del proyecto Spring / negocio,
  • en [2], se ha agregado el proyecto Spring / negocio. Como este, a su vez, tenía una dependencia del proyecto Spring / DAO / JPA, este se agregó automáticamente a las dependencias de [3].

Volvamos a la estructura de nuestra aplicación:

La capa web es una capa JSF. Por lo tanto, necesitamos las bibliotecas de Java Server Faces. El servidor Tomcat no las tiene. Por lo tanto, la dependencia no tendrá el alcance (scope) [provided], como lo tenía con el servidor Glassfish, sino el alcance [compile], que es el alcance predeterminado cuando no se especifica uno.

Agregamos estas dependencias directamente en el 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>
  • Se han agregado las líneas 7 a 16 al archivo [pom.xml]. Estas son las dependencias con respecto a JSF. Son las mismas que se utilizan en el proyecto EJB / Glassfish. Cabe señalar que no tienen la etiqueta <scope>. Por lo tanto, por defecto tienen el alcance [compile]. La biblioteca JSF se incluirá, por lo tanto, en el archivo [war] del proyecto web.

Después de agregar estas dependencias al archivo [pom.xml], compilamos el proyecto para que se descarguen.

4.3.3. Migración del proyecto JSF / Glassfish al proyecto JSF / Tomcat

Copiamos todo el código del proyecto JSF / Glassfish al proyecto JSF / Tomcat:

  • [1, 2, 3]: copia de las páginas web del proyecto anterior al nuevo,
  • [1, 2, 3]: copia del código Java del proyecto anterior al nuevo. Hay errores. Es normal. Los corregiremos,
  • en [1], en la pestaña [Files] de NetBeans, se crea una subcarpeta [resources] dentro de la carpeta [main],
  • lo cual crea, en la pestaña [Projects], la rama [Other Sources] [3],
  • [1, 2, 3]: se copian los archivos de mensajes del proyecto anterior al nuevo proyecto.

4.3.4. Modificaciones al proyecto importado

Hemos señalado que el código Java importado contenía errores. Analicémoslos:

  • en [1], solo el bean [Application] es erróneo;
  • en [2], el error se debe únicamente a que la interfaz [IMetierLocal] ya no existe. Aquí puede sorprender que la línea 20 no genere un error. La anotación @EJB hace referencia explícita a EJB y aquí se reconoce. Esto se debe a la presencia de la dependencia [javaee-web-api-6.0] [3]. Java EE 6 introdujo una arquitectura que permite implementar una aplicación web basada en EJB sin interfaz remota, en servidores que no cuentan con un contenedor EJB. Basta con que el servidor proporcione la dependencia [javaee-web-api-6.0]. De hecho, vemos que esta tiene el alcance [provided] [3].

Aquí no vamos a utilizar la dependencia [javaee-web-api-6.0]. La eliminamos: [1]:

Esto genera nuevos errores: [2]. Comenzaremos con los del bean [Form]:

  • En [1], las líneas con error están relacionadas con la pérdida del paquete [javax]. Las eliminamos todas de [2]. Las líneas erróneas convertían a la clase [Form] en un bean de alcance de sesión (líneas 18-20 de [1]). Por otra parte, el bean [Application] se inyectaba en la línea 25. Esta información se trasladará al archivo de configuración de JSF y [faces-config.xml].

Pasemos al bean [Application]:

Se eliminan todas las líneas con errores de [1] y se cambia la interfaz de las líneas 13 y 21 de [IMetierLocal] a [IMetier]. En [2] ya no hay errores. En [1], hemos eliminado las líneas 15 y 16 que convertían a la clase [Application] en un bean de alcance application.. Esta información se trasladará al archivo de configuración de JSF [faces-config.xml]. También hemos eliminado la línea 20, que inyectaba una referencia de la capa [métier] en el bean. Ahora, esta será inicializada por Spring. Ya contamos con el archivo de configuración necesario; es el del proyecto Spring / Métier. Lo copiamos:

  • en [1, 2]; copiamos el archivo de configuración de Spring del proyecto Spring / Métier al proyecto Spring / JSF,

En [3], el resultado.

En el bean [Application], hay que utilizar este archivo de configuración para obtener una referencia en la capa [métier]. Esto se hace en su método [init]:


package beans;

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

public class Application {

  // capa de negocio
  private IMetier metier;
...

  public Application() {
  }

  @PostConstruct
  public void init() {
    try {
      // instanciación de la capa [métier]
      ApplicationContext ctx = new ClassPathXmlApplicationContext("spring-config-metier-dao.xml");
      metier = (IMetier) ctx.getBean("metier");
      // se almacenan en caché los médicos y los clientes
...
    } catch (Throwable th) {
...
    }
...
  }
  • línea 20: se instancian los beans del archivo de configuración de Spring,
  • línea 21: se solicita una referencia al bean de negocio, es decir, a la capa [métier].

En general, la instanciación de los beans de Spring debe realizarse en el método init del bean de alcance de aplicación. Existe otro método en el que la instanciación de los beans la realiza un servlet de Spring. Esto implica modificar el archivo [web.xml] y agregar una dependencia del artefacto [spring-web]. No lo hemos hecho aquí para mantener la coherencia con lo que se había utilizado en los códigos anteriores.

Hemos eliminado las anotaciones en las clases [Application] y [Form] que las convertían en beans JSF. Estas clases deben seguir siendo beans JSF. En lugar de las anotaciones, se utiliza el archivo de configuración de JSF [WEB-INF / faces.config.xml] para declarar los beans.

Este archivo ahora es el siguiente:


<?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>
    <!-- el archivo de mensajes -->
    <resource-bundle>
      <base-name>
        messages
      </base-name>
      <var>msg</var>
    </resource-bundle>
    <message-bundle>messages</message-bundle>
  </application>
    <!-- el 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>
    <!-- el bean de formulario -->
    <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>
  • las líneas 10 a 19 configuran el archivo de mensajes. Esa era la única configuración que teníamos en el proyecto JSF / EJB,
  • las líneas 21 a 35 declaran los beans de la aplicación JSF. Este era el método estándar con JSF 1.x. JSF 2 introdujo las anotaciones, pero el método de JSF y 1.x sigue siendo compatible,
  • líneas 21-25: declaran el bean applicationBean,
  • línea 22: el nombre del bean. Podríamos sentir la tentación de usar el nombre «application». Hay que evitarlo, ya que es el nombre de un bean predefinido de JSF,
  • línea 23: el nombre completo de la clase del bean,
  • línea 24: su ámbito,
  • líneas 27-35: definen el bean «form»,
  • línea 28: el nombre del bean,
  • línea 29: el nombre completo de la clase del bean,
  • línea 30: su ámbito,
  • líneas 31-34: definen una propiedad de la clase [beans.Form],
  • línea 32: nombre de la propiedad. La clase [beans.Form] debe tener un campo con este nombre y el setter correspondiente,
  • línea 33: el valor del campo. Aquí es la referencia al bean applicationBean definido en la línea 21. Por lo tanto, aquí se realiza la inyección del bean de ámbito application en el bean de ámbito session para que este último tenga acceso a los datos de ámbito application.

Anteriormente mencionamos que el campo [application] del bean [beans.Form] se inicializaría mediante un setter. Por lo tanto, es necesario agregarlo a la clase [beans.Form] si aún no existe:


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

4.3.5. Prueba de la aplicación

Nuestra aplicación ya no presenta errores y está lista para las pruebas:

  • en [1], el proyecto corregido,
  • en [2], lo compilamos,
  • en [3], lo ejecutamos. Es necesario que se inicien SGBD y MySQL. A continuación, se iniciará el servidor Tomcat ([4]) si aún no lo estaba, y luego se mostrará la página de inicio de la aplicación ([5]):

A partir de ahí, nos encontramos con la aplicación que estamos analizando. Dejamos que el lector verifique que funcione. Ahora detengamos la aplicación:

  • en [1], se descarga la aplicación,
  • en [2], ya no está ahí.

Ahora veamos los registros de de 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

Las líneas 2 y 4 indican un error al cerrar la aplicación. La línea 4 señala que existe un riesgo probable de fuga de memoria. Efectivamente, esto ocurre y, al cabo de un rato, NetBeans ya no se puede usar. Este problema es particularmente molesto porque hay que reiniciar NetBeans cada vez que se ejecuta el proyecto. Este problema ya se ha mencionado en el documento «Introducción a Struts 2 con ejemplos» [http://tahe.developpez.com/java/struts2].

En Internet se encuentra mucha información sobre este error. Aparece cuando se carga y descarga una aplicación de Tomcat de manera repetida. Al cabo de un tiempo, se obtiene el error java.lang.OutOfMemoryError: PermGen space. Parece que no hay una solución para evitar este error cuando proviene de archivos de terceros (jar), como es el caso aquí. Por lo tanto, hay que reiniciar Tomcat para que desaparezca.

Sin embargo, es posible retrasar la aparición de este error. En primer lugar, se aumenta el espacio de la memoria que se desbordó.

  • en [1], vamos a las propiedades del servidor Tomcat,
  • en [2], en la pestaña [Platform], se establece el valor de la memoria que se desborda. Aquí pusimos 1 GB porque teníamos una memoria total de 8 GB. Se puede poner 512M (512 megabytes) si la memoria es más pequeña.

Luego, se coloca el controlador JDBC de MySQL en <tomcat>/lib, donde <tomcat> es el directorio de instalación de Tomcat.

  • en [1], en las propiedades de Tomcat, se anota su directorio de instalación <tomcat>,
  • en <tomcat>/lib [2], se coloca un controlador JDBC reciente de MySQL [3].

A continuación, se elimina la dependencia que tenía el proyecto del controlador JDBC de MySQL y [4].

Una vez hecho esto, probamos la aplicación. Observamos que se pueden realizar cargas y descargas repetidas de la aplicación. Sin embargo, los problemas de fuga de memoria no se han resuelto. Simplemente aparecen más tarde.

4.4. Conclusion

Hemos migrado la aplicación JSF / EJB / Glassfish a un entorno JSF / Spring / Tomcat. Esto se logró principalmente mediante copiar y pegar entre los dos proyectos. Esto fue posible porque las tecnologías Spring y EJB3 presentan grandes similitudes. De hecho, EJB3 se creó después de que Spring demostrara ser más eficiente que EJB2. EJB3 adoptó entonces las buenas ideas de Spring.

4.5. Pruebas con Eclipse

  • en [1], se importan los tres proyectos de Spring,
  • en [2], se selecciona la prueba JUnit de la capa [DAO] y se ejecuta en [3],
  • en [4], la prueba se realiza con éxito,
  • en [5], los registros de la consola.
  • en [6A] [6B], se ejecuta el cliente de consola de la capa [métier],
  • en [7], la pantalla de la consola obtenida,
  • en [8] [9], se ejecuta el proyecto web en un servidor Tomcat 7 [10],
  • en [11], la página de inicio de la aplicación se muestra en el navegador interno de Eclipse.