Skip to content

4. Приклад додатка – 02: rdvmedecins-jsf2-spring

Тепер ми пропонуємо перенести попередній додаток у середовище Spring / Tomcat:

Це справді перенесення. Ми візьмемо за основу попередній додаток і адаптуємо його до нового середовища. Коментуватимемо лише зміни. Вони поділяються на три категорії:

  • сервером тепер є не Glassfish, а Tomcat — легкий сервер, який не має контейнера EJB,
  • замість EJB ми будемо використовувати Spring — головного конкурента EJB та [http://www.springsource.com/],
  • замість EclipseLink буде використовуватися реалізація Hibernate.

Оскільки ми будемо часто використовувати копіювання та вставку між старим і новим проектами, ми залишаємо попередні проекти відкритими в NetBeans:

  

Використання фреймворку Spring вимагає певних знань, які можна знайти в [ref7] (див. стор. 166).

4.1. Шари [DAO] та [JPA]

4.1.1. Проєкт NetBeans

Ми створюємо проект Maven типу [Java Application]:

  • у [1] — створений проєкт,
  • у [2] — той самий, але без пакетів [Source Packages] та [Test Packages], а також без залежності [junit-3.8.1].

Найскладніше в проектах Maven — це знайти правильні залежності. Для цього проекту Spring / JPA / Hibernate це такі залежності:


<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>
  • рядки 18–29: для Hibernate,
  • рядки 30–34: для драйвера JDBC від MySQL,
  • рядки 35–41: для тесту JUnit,
  • рядки 42–51: для пулу з’єднань Apache Commons DBCP. Пул з’єднань — це набір відкритих з’єднань. Коли додатку потрібне з’єднання, він запитує його у пулу. Коли воно більше не потрібне, він повертає його. З’єднання відкриваються під час запуску додатка і залишаються відкритими протягом усього терміну його роботи. Це дозволяє уникнути витрат, пов’язаних із багаторазовим відкриттям та закриттям з’єднань. Цей тип пулу існував у Glassfish, але його використання було для нас прозорим. Так само буде і в цьому випадку, але нам потрібно його встановити та налаштувати,
  • рядки 52–75: для Spring.

Додамо ці залежності та скомпілюємо проєкт:

  • у [1], ми компілюємо проєкт, що змусить Maven завантажити залежності,
  • у [2], після чого вони з’являться у гілці [Dependencies]. Їх дуже багато, оскільки фреймворки Hibernate та Spring самі мають безліч залежностей. І тут, завдяки Maven, нам не доводиться про них турбуватися. Вони завантажуються автоматично.

Тепер, коли ми маємо залежності, ми вставляємо код проєкту EJB з рівня [dao] у проєкт Spring рівня [dao]:

  • у [1], копіюємо у вихідний проєкт,
  • у [2] вставляємо у проект призначення,
  • у [3] — результат.

Після завершення копіювання потрібно виправити помилки.

4.1.2. Пакет [exceptions]

У класі [RdvMedecinsExceptions] [1] є помилки через пакет [javax] у рядку 4, який більше не існує. Це пакет, властивий EJB. Помилка в рядку 6 є наслідком помилки в рядку 4. Ці два рядки видаляються. Це усуває помилки [2].

4.1.3. Пакет [jpa]

  • у [1] клас [Creneau] містить помилку через відсутність пакета перевірки рядка [5]. Цей пакет можна було б додати до залежностей проєкту. Однак під час тестування Hibernate генерує виняток саме через нього. Оскільки він не є обов’язковим для нашого додатка, ми його видалили. Щоб виправити клас, достатньо видалити всі помилкові рядки [2]. Це робимо для всіх помилкових класів.

4.1.4. Пакет [dao]

Ми дійшли до наступного етапу:

  • у [1], обидва виправлені пакети,
  • у [2] — пакет [dao]. Оскільки EJB більше не існує, то не існує й поняття віддаленого та локального інтерфейсів EJB. Ми видаляємо їх [3].
  • у [1] помилки класу [DaoJpa] мають дві причини:
  • імпорт пакета, пов’язаного з EJB (рядки 6–8);
  • використання локального та віддаленого інтерфейсів, які ми щойно видалили.

Ми видаляємо помилкові рядки та використовуємо інтерфейс [IDao] замість локального та віддаленого інтерфейсів [2].

У проєкті EJB клас [DaoJpa] був синглтоном, і його методи виконувалися в рамках транзакції. Ми побачимо, що клас [DaoJpa] буде біном, керованим Spring. За замовчуванням кожен бін Spring є синглтоном. Ось і перша властивість. Друга досягається за допомогою анотації @Transactional від Spring [3]:

Після цього у проєкті більше немає помилок [4].

4.1.5. Налаштування шару [JPA]

У проєкті EJB ми налаштовували шар [JPA] за допомогою файлу [persistence.xml]. Тут у нас є шар [JPA], тому нам потрібно створити цей файл. У проєкті EJB ми генерували його за допомогою Glassfish. Тут ми створюємо його вручну. Головною причиною цього є те, що частина конфігурації файлу [persistence.xml] переноситься безпосередньо до конфігураційного файлу Spring.

Створюємо файл [persistence.xml]:

із таким вмістом:


<?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>
  • рядок 3: присвоюємо ім’я одиниці збереження,
  • рядок 3: тип транзакцій — RESOURCE_LOCAL. У проєкті EJB це було JTA, що вказувало на те, що транзакції керувалися контейнером EJB. Значення RESOURCE_LOCAL вказує, що додаток самостійно керує своїми транзакціями. У даному випадку це буде здійснюватися за допомогою Spring,
  • рядки 4–7: повні імена чотирьох сутностей JPA. Це необов’язково, оскільки Hibernate автоматично шукає їх у файлі ClassPath проекту.

Ось і все. Ім’я провайдера JPA, його властивості та характеристики JDBC джерела даних тепер містяться у конфігураційному файлі Spring.

4.1.6. Конфігураційний файл Spring

Ми вже зазначали, що клас [DaoJpa] є біном, який керується Spring. Це здійснюється за допомогою конфігураційного файлу. Цей файл також міститиме налаштування доступу до бази даних та управління транзакціями. Він має знаходитися у папці ClassPath проекту. Ми розміщуємо його у гілці [Other sources]:

Файл [spring-config-dao.xml] має такий вигляд:


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

  <!-- прикладні шари -->
  <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>

  <!-- джерело даних 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>

  <!-- менеджер транзакцій -->
  <tx:annotation-driven transaction-manager="txManager" />
  <bean id="txManager" class="org.springframework.orm.jpa.JpaTransactionManager">
    <property name="entityManagerFactory" ref="entityManagerFactory" />
  </bean>

  <!-- переклад винятків -->
  <bean class="org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor" />

  <!-- збереження даних -->
  <bean class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor" />

</beans>

Це файл, сумісний із Spring 2.x. Ми не намагалися використовувати нові можливості версій 3.x.

  • рядки 2–4: кореневий тег <beans> конфігураційного файлу. Ми не коментуємо різні атрибути цього тегу. Слід бути обережним під час копіювання та вставлення, оскільки помилка в одному з цих атрибутів може спричинити помилки, які іноді важко зрозуміти,
  • рядок 7: bean «dao» — це посилання на екземпляр класу [rdvmedecins.dao.DaoJpa]. Буде створено єдиний екземпляр (singleton), який реалізує рівень [dao] додатка,
  • рядки 24–29: визначено джерело даних. Воно надає службу «пулу з’єднань», про яку ми вже згадували. Тут використовується [DBCP] із проєкту Apache Commons DBCP [http://jakarta.apache.org/commons/dbcp/],
  • рядки 25–28: для встановлення з’єднань із цільовою базою даних джерело даних має знати, який драйвер JDBC використовується (рядок 25), ім’я бази даних URL (рядок 26), користувача з’єднання та його пароль (рядки 27–28),
  • рядки 10–21: налаштовують рівень JPA,
  • рядок 10: визначає бін типу [EntityManagerFactory], здатний створювати об’єкти типу [EntityManager] для управління контекстами персистентності. Інстанційований клас [LocalContainerEntityManagerFactoryBean] надається Spring. Для його інстанціювання потрібна певна кількість параметрів, які визначені у рядках 11–20,
  • рядок 11: джерело даних, яке використовується для отримання з’єднань із SGBD. Це джерело [DBCP], визначене в рядках 24–29,
  • рядки 12–20: реалізація JPA, яку слід використовувати,
  • рядок 13: визначає Hibernate як реалізацію JPA, яку слід використовувати,
  • рядок 14: діалект SQL, який Hibernate має використовувати з цільовим SGBD, у даному випадку MySQL5,
  • рядок 16 (у коментарях): вимагає, щоб команди SQL, що виконуються Hibernate, записувалися в журнал консолі,
  • рядок 17 (у коментарях): вимагає, щоб під час запуску додатка база даних була згенерована (drop та create),
  • рядок 32: вказує, що транзакції керуються за допомогою анотацій Java (їх також можна було б оголосити у файлі spring-config.xml). Зокрема, це анотація @Transactional, що зустрічається у класі [DaoJpa],
  • рядки 33–35: визначають менеджер транзакцій, який слід використовувати,
  • рядок 33: менеджер транзакцій — це клас, наданий Spring,
  • рядок 34: менеджеру транзакцій Spring потрібно знати клас EntityManagerFactory, який керує шаром JPA. Це той клас, що визначений у рядках 10–21,
  • рядок 41: визначає клас, який керує анотаціями персистентності Spring,
  • рядок 38: визначає клас Spring, який, зокрема, обробляє анотацію @Repository, завдяки якій клас, позначений цією анотацією, стає придатним для перетворення нативних винятків драйвера JDBC з SGBD у загальні винятки Spring типу [DataAccessException]. Це перетворення інкапсулює нативне виключення JDBC у тип [DataAccessException], що має різні підкласи:

Image

Ця переробка дозволяє клієнтській програмі обробляти винятки узагальнено, незалежно від цільового SGBD. У нашому Java-коді ми не використовували анотацію @Repository. Тому рядок 38 є зайвим. Ми залишили його лише для інформації.

Ми завершили роботу з конфігураційним файлом Spring. Він був взятий із документації Spring. Його адаптація до різних ситуацій часто зводиться до двох змін:

  • зміна цільової бази даних: рядки 24–29,
  • зміна реалізації JPA: рядки 12–20.

Під час виконання коду всі біни з конфігураційного файлу будуть інстанційовані. Ми розглянемо, як саме.

4.1.7. Тестовий клас JUnit

Ми тестували рівень [DAO] проекту EJB за допомогою тесту JUnit. Те саме робимо для рівня [DAO] проекту Spring:

  • у [1] та [2], скопіювавши та вставивши тест JUnit між цими двома проектами,
  • у [3] імпортований тест видає помилки в новому середовищі.

Повідомлена помилка [1] стосується віддаленого інтерфейсу EJB, який більше не існує. Крім того, код ініціалізації поля [dao] у рядку 19 був викликом JNDI, характерним для EJB (рядки 25–28). Щоб створити екземпляр поля [dao] у рядку 19, нам потрібно використати файл конфігурації Spring. Це робиться наступним чином:

  • рядок 21: тип інтерфейсу змінився на [IDao],
  • рядок 28: створює екземпляри всіх бінів, оголошених у файлі [spring-config-dao.xml], зокрема цього:

  <bean id="dao" class="rdvmedecins.dao.DaoJpa" />
  • у рядку 29 запитується у контексті Spring із рядка 28 посилання на bean з id="dao". У результаті отримується посилання на синглтон [DaoJpa] (class вище), який Spring інстанціював.

Рядки 28–29 формують такі блоки (рожеві пунктирні лінії):

Під час виконання тестів клієнта JUnit було створено екземпляр шару [DAO]. Отже, можна тестувати його методи. Зауважимо, що для виконання цього тесту сервер не потрібен, на відміну від тесту EJB [DAO], для якого був необхідний сервер Glassfish. Тут усе виконується в тому самому JVM.

Тепер можна запустити тест JUnit. Для цього потрібно, щоб сервер MySQL був запущений. Результати такі:

Тест JUnit пройшов успішно. Давайте розглянемо журнали тесту, як це було зроблено під час тесту 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]; корінь заводської ієрархії
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]]
  • рядки 1–4: журнали Spring,
  • рядки 5–10: журнали Hibernate,
  • рядок 11: Spring повідомляє про всі біни, які він інстанціював. Першим там з’являється бін [dao],
  • рядки 12 і наступні: логи тесту JUnit,
  • рядки 60–65: чітко видно виняток, спричинений додаванням зустрічі, яка вже існує в базі даних. Нагадаємо, що з EJB цього винятку не було через проблему з серіалізацією.

Рівень [dao] працює. Тепер ми створюємо рівень [métier].

4.2. Рівень [métier]

Ми діємо так само, як і для шару [DAO], скопіювавши та вставивши проект EJB у проект Spring.

4.2.1. Проєкт NetBeans

Ми створюємо новий проект Maven типу [Java Application], очищений від усього, що ми не хочемо зберігати, [1]:

4.2.2. Залежності проекту

В архітектурі:

шар [métier] спирається на шар [dao]. Тому ми додаємо залежність від попереднього проєкту:

  • у [1] та [2] додаємо залежність від проекту шару [dao],
  • у [3] ця залежність спричинила появу інших залежностей — від проекту шару [dao].
  • у [1] та [2] копіюються Java-джерела з проекту EJB до проекту Spring,
  • в [3], імпортовані вихідні коди містять помилки в новому середовищі.

Спочатку видаляємо віддалений та локальний інтерфейси з шару [métier], які більше не існують у [4]:

  • у [5] помилки класу [Metier] мають кілька причин:
    • використання пакета [javax.ejb], який більше не існує;
    • використання інтерфейсу [IDaoLocal], який більше не існує;
    • використання інтерфейсів [IMetierRemote] та [IMetierLocal], які більше не існують.

Ми

  • видаляємо всі помилкові рядки, пов’язані з пакетом [javax.ejb],
  • замінюємо інтерфейс [IDaoLocal] на інтерфейс [IDao],
  • замінюємо інтерфейси [IMetierRemote] та [IMetierLocal] на інтерфейс [IMetier].
  • на [6], таким чином виправлений клас,
  • У файлі [7] більше немає помилок.

Ми видалили посилання на EJB, але тепер нам потрібно відновити їхні властивості:

 
  • рядок 22: ми мали синглтон. Цю властивість можна отримати, перетворивши клас на бін, керований Spring,
  • рядок 23: кожен метод виконувався в транзакції. Це буде досягнуто за допомогою анотації Spring @Transactional,
  • рядки 27–28: посилання на рівень [DAO] отримувалося шляхом ін’єкції контейнера EJB. Ми будемо використовувати ін’єкцію Spring.

Отже, код класу [Metier] у проєкті Spring змінюється наступним чином:

Це все, що стосується коду Java. Решта відбувається у файлі конфігурації Spring.

4.2.3. Конфігураційний файл Spring

Ми копіюємо файл конфігурації Spring із проєкту шару [DAO] у проєкт шару [métier]. Спочатку створюємо гілку [Other Resources] у проєкті шару [métier], якщо вона ще не існує:

  • у [1], на вкладці [Files] створюємо підпапку в папці [main],
  • у [2] його потрібно перейменувати на [resources],
  • у [3], у вкладці [Projects], було створено гілку [Other Sources].

Тепер можна перейти до копіювання та вставлення файлу конфігурації Spring:

  • у [1] скопіюємо файл з проєкту [DAO] у проєкт [métier] [2],
  • в [3] — скопійований файл.

Скопійований файл конфігурації налаштовує шар [DAO]. Додаємо до нього bean для налаштування шару [métier]:

1
2
3
4
5
   <!-- аплікаційні рівні -->
  <bean id="dao" class="rdvmedecins.dao.DaoJpa" />
  <bean id="metier" class="rdvmedecins.metier.service.Metier">
    <property name="dao" ref="dao"/>
</bean>
  • рядок 2: bean шару [DAO],
  • рядки 3–5: бін шару [métier],
  • рядок 3: bean має назву métier (атрибут id) і є екземпляром класу [rdvmedecins.metier.service.Metier] (атрибут class). Цей bean буде інстанційовано, як і інші, під час запуску додатка.

Нагадаємо код біна [rdvmedecins.metier.service.Metier]:


package rdvmedecins.metier.service;

...

public class Metier implements IMetier, Serializable {

  // рівень DAO
  private IDao dao;

  public Metier() {
}
  • рядок 8: поле [dao] буде інстанційовано Spring одночасно з біном métier. Повернемося до визначення цього біна у файлі конфігурації Spring:
1
2
3
4
5
   <!-- аплікаційні рівні -->
  <bean id="dao" class="rdvmedecins.dao.DaoJpa" />
  <bean id="metier" class="rdvmedecins.metier.service.Metier">
    <property name="dao" ref="dao"/>
</bean>
  • рядок 4: тег <property> використовується для ініціалізації полів інстанційованого біна. Ім’я поля задається атрибутом name. Отже, буде інстанційовано поле dao класу [rdvmedecins.metier.service.Metier]. Це відбудеться за допомогою методу setDao, який повинен існувати. Значенням, яке йому буде присвоєно, є значення атрибута ref. Це значення тут є посиланням на bean dao з рядка 2.

Простіше кажучи, у коді:


package rdvmedecins.metier.service;

...

public class Metier implements IMetier, Serializable {

  // DAO-шар
  private IDao dao;

  public Metier() {
}

Поле dao у рядку 19 буде ініціалізовано Spring із посиланням на шар [dao]. Це саме те, чого ми хотіли. Поле dao буде ініціалізовано Spring за допомогою setter, який ми маємо додати:


  // сеттер

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

Ми перейменовуємо файл конфігурації Spring, щоб врахувати зміни:

Тепер ми готові до тестування. Використаємо той самий консольний тест, що й для перевірки EJB та [Metier].

4.2.4. Тестування рівня [métier]

Тестування відбуватиметься за такою архітектурою:

Ми копіюємо тест консолі з проєкту EJB у проєкт Spring:

  • у [1] та [2], при копіюванні/вставці між цими двома проектами
  • у [3] імпортований код містить помилки.
 

В імпортованому коді є два типи помилок:

  • рядок 13: інтерфейс [IMetierRemote] було замінено на інтерфейс [IMetier],
  • рядки 24–27: інстанціювання шару [métier] більше не відбувається за допомогою виклику JNDI, а шляхом інстанціювання бінів із конфігураційного файлу Spring.

Ми виправляємо ці два моменти:

  • рядок 22: використовується файл [spring-config-metier-dao.xml]. Усі біни з цього файлу інстанціюються. Серед них є такі:

  <!-- прикладні шари -->
  <bean id="dao" class="rdvmedecins.dao.DaoJpa" />
  <bean id="metier" class="rdvmedecins.metier.service.Metier">
    <property name="dao" ref="dao"/>
</bean>

Ці два біни представляють шари [DAO] та [métier] архітектури тесту:

Після цього тест може розпочатися:

  

Журнали тестування мають такий вигляд:

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]; корінь заводської ієрархії
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]]
  • рядки 1–4: журнали Spring та Hibernate,
  • рядок 5: біни, інстанційовані Spring. Зверніть увагу на біни dao та metier,
  • рядки 6–53: журнали тесту. Вони відповідають результатам, отриманим під час тестування проєкту EJB. Просимо читача ознайомитися з коментарями до цього тесту (параграф 3.5.3).

Ми побудували шар [métier]. Переходимо до останнього шару — шару [web].

4.3. Шар [web]

Щоб створити шар [web], ми будемо діяти так само, як і для двох інших шарів, скопіювавши та вставивши його з шару [web] проекту EJB.

4.3.1. Проєкт NetBeans

Спочатку створюємо веб-проект:

  • у [1] створюємо новий проєкт,
  • у [2] — проект Maven типу [Web Application],
  • у [3], даємо йому ім’я,
  • у [4] цього разу вибираємо сервер Tomcat, а не Glassfish, який використовувався у проєкті EJB,
  • у [5] отримано проект,
  • [6] — проект після видалення [index.jsp] та пакета [Source Packages].

4.3.2. Залежності проекту

Розглянемо архітектуру проекту:

Рівень [web] потребує рівнів [métier], [DAO] та [JPA]. Вони входять до складу двох проектів, які ми щойно створили. Звідси випливає залежність від кожного з цих проектів:

  • у [1] ми додаємо залежність від проекту Spring / бізнес-логіка,
  • у [2] додано проект Spring / бізнес-логіка. Оскільки він сам мав залежність від проекту Spring / DAO / JPA, цей проект був автоматично доданий до залежностей [3].

Повернемося до структури нашого додатка:

Веб-шар — це шар JSF. Отже, нам потрібні бібліотеки Java Server Faces. Сервер Tomcat їх не має. Отже, залежність не матиме області дії (scope) [provided], як це було у випадку з сервером Glassfish, а матиме область дії [compile], яка є областю дії за замовчуванням, коли область дії не вказано.

Ми додаємо ці залежності безпосередньо в код [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>
  • рядки 7–16 було додано до файлу [pom.xml]. Це залежності від JSF. Саме вони використовуються в проєкті EJB / Glassfish. Слід зауважити, що вони не мають тегу <scope>. Отже, за замовчуванням їхня область дії — [compile]. Таким чином, бібліотека JSF буде вбудована в архів [war] веб-проєкту.

Після додавання цих залежностей до файлу [pom.xml] ми компілюємо проєкт, щоб вони були завантажені.

4.3.3. Перенесення проєкту JSF / Glassfish до проєкту JSF / Tomcat

Копіюємо весь код з проєкту JSF / Glassfish до проєкту JSF / Tomcat:

  • [1, 2, 3]: копіювання веб-сторінок зі старого проєкту до нового,
  • [1, 2, 3]: копіювання Java-коду зі старого проєкту до нового. Є помилки. Це нормально. Ми їх виправимо,
  • у [1], у вкладці [Files] у NetBeans створюємо підпапку [resources] у папці [main],
  • це створює у вкладці [Projects] гілку [Other Sources] [3],
  • [1, 2, 3]: файли повідомлень копіюються зі старого проєкту до нового.

4.3.4. Зміни в імпортованому проєкті

Ми зазначили, що імпортований Java-код містив помилки. Розглянемо їх:

  • у [1] помилковим є лише bean [Application],
  • у [2] помилка пов’язана виключно з інтерфейсом [IMetierLocal], який більше не існує. Тут може здивувати, що рядок 20 не позначений як помилковий. Анотація @EJB явно посилається на EJB і тут розпізнається. Це пов’язано з наявністю залежності [javaee-web-api-6.0] від [3]. Java EE 6 запровадила архітектуру, яка дозволяє розгортати веб-додаток, що базується на EJB без віддаленого інтерфейсу, на серверах, що не мають контейнера EJB. Достатньо, щоб сервер забезпечував залежність [javaee-web-api-6.0]. Дійсно, бачимо, що вона має область дії [provided] [3].

Тут ми не будемо використовувати залежність [javaee-web-api-6.0]. Ми видаляємо її — [1]:

Це призводить до появи нових помилок [2]. Почнемо з помилок, пов’язаних із компонентом [Form]:

  • у [1] помилкові рядки пов’язані з відсутністю пакета [javax]. Ми видаляємо їх усі з [2]. Помилкові рядки перетворювали клас [Form] на бін сесійної області дії (рядки 18–20 у [1]). Крім того, bean [Application] вводився у рядку 25. Ця інформація буде перенесена до конфігураційного файлу JSF [faces-config.xml].

Перейдемо до біна [Application]:

Видаляємо всі помилкові рядки з файлу [1] і змінюємо інтерфейс [IMetierLocal] у рядках 13 та 21 на [IMetier]. У файлі [2] помилок більше немає. У файлі [1] ми видалили рядки 15–16, які робили клас [Application] біном з областю дії application.. Ця інформація буде перенесена до конфігураційного файлу JSF [faces-config.xml]. Ми також видалили рядок 20, який вводив у bean посилання на шар [métier]. Тепер це посилання буде ініціалізовано Spring. Необхідний файл конфігурації у нас вже є — це файл із проекту Spring / Métier. Скопіюємо його:

  • у [1, 2], копіюємо файл конфігурації Spring із проєкту Spring / Métier у проєкт Spring / JSF,

У [3] — результат.

У біні [Application] потрібно використати цей файл конфігурації, щоб отримати посилання на рівень [métier]. Це робиться в його методі [init]:


package beans;

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

public class Application {

  // бізнес-шар
  private IMetier metier;
...

  public Application() {
  }

  @PostConstruct
  public void init() {
    try {
      // інстанціювання рівня [métier]
      ApplicationContext ctx = new ClassPathXmlApplicationContext("spring-config-metier-dao.xml");
      metier = (IMetier) ctx.getBean("metier");
      // зберігаємо лікарів та клієнтів у кеші
...
    } catch (Throwable th) {
...
    }
...
  }
  • рядок 20: створюються екземпляри бінів із конфігураційного файлу Spring,
  • рядок 21: запитується посилання на бізнес-біан, тобто на рівень [métier].

Як правило, інстанціювання бінів Spring має відбуватися в методі init біна з областю дії «application». Існує інший метод, за якого інстанціювання бінів здійснюється сервлетом Spring. Це передбачає внесення змін до файлу [web.xml] та додавання залежності від артефакту [spring-web]. Ми не зробили цього в даному випадку, щоб дотримуватися підходу, який використовувався в попередніх кодах.

Ми видалили анотації в класах [Application] та [Form], які визначали їх як біни JSF. Ці класи повинні залишатися бінами JSF. Замість анотацій для оголошення бінів використовується файл конфігурації JSF [WEB-INF / faces.config.xml].

Тепер цей файл має такий вигляд:


<?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>
    <!-- файл повідомлень -->
    <resource-bundle>
      <base-name>
        messages
      </base-name>
      <var>msg</var>
    </resource-bundle>
    <message-bundle>messages</message-bundle>
  </application>
    <!-- біан 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>
    <!-- біан форми -->
    <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>
  • рядки 10–19 налаштовують файл повідомлень. Це була єдина конфігурація, яка була в проєкті JSF / EJB,
  • рядки 21–35 оголошують біни додатка JSF. Це був стандартний метод у JSF 1.x. У JSF 2 були введені анотації, але метод JSF та 1.x все ще підтримується,
  • рядки 21–25: оголошують бін applicationBean,
  • рядок 22: ім’я біна. Може виникнути спокуса назвати його «application». Цього слід уникати, оскільки це ім’я попередньо визначеного біна JSF,
  • рядок 23: повна назва класу біна,
  • рядок 24: його область дії,
  • рядки 27–35: визначають бін form,
  • рядок 28: ім’я біна,
  • рядок 29: повна назва класу біна,
  • рядок 30: його область дії,
  • рядки 31–34: визначають властивість класу [beans.Form],
  • рядок 32: ім’я властивості. Клас [beans.Form] повинен мати поле з таким ім’ям та відповідний сеттер,
  • рядок 33: значення поля. Тут це посилання на бін applicationBean, визначений у рядку 21. Отже, тут здійснюється ін’єкція біна з областю дії application у бін з областю дії session, щоб останній мав доступ до даних з області дії application.

Вище ми зазначали, що поле [application] біна [beans.Form] буде ініціалізовано за допомогою setter. Отже, його потрібно додати до класу [beans.Form], якщо воно ще не існує:


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

4.3.5. Тестування додатка

Наш додаток тепер не містить помилок і готовий до тестування:

  • у [1], виправлений проект,
  • у [2] — його компілюємо,
  • у [3] — запускаємо його. Необхідно, щоб SGBD та MySQL були запущені. Тоді сервер Tomcat буде запущений ([4]), якщо він ще не запущений, після чого буде відображена головна сторінка додатка ([5]):

З цього моменту ми повертаємося до розглянутого додатка. Ми залишаємо читачеві можливість перевірити, чи він працює. Тепер зупинимо додаток:

  • у [1], вивантажуємо додаток,
  • у [2] її вже немає.

Тепер поглянемо на журнали 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

Рядки 2 та 4 вказують на збій під час зупинки додатка. Рядок 4 вказує на ймовірний витік пам’яті. Дійсно, це відбувається, і через деякий час NetBeans стає непридатним для використання. Ця проблема особливо дратує, оскільки доводиться перезапускати NetBeans при кожному новому запуску проєкту. Ця проблема вже зустрічалася в документі «Вступ до Struts 2 на прикладі» [http://tahe.developpez.com/java/struts2].

В Інтернеті можна знайти багато інформації про цю помилку. Вона виникає під час багаторазового завантаження/вивантаження додатка з Tomcat. Через деякий час з’являється помилка java.lang.OutOfMemoryError: PermGen space. Виявляється, що уникнути цієї помилки неможливо, якщо вона пов’язана зі сторонніми архівами (jar), як у даному випадку. Тоді для її усунення потрібно перезапустити Tomcat.

Однак можна відстрочити виникнення цієї помилки. По-перше, слід збільшити обсяг пам’яті, яка переповнилася.

  • у [1] переходимо до властивостей сервера Tomcat,
  • в [2], на вкладці [Platform] встановлюємо значення пам’яті, що переповнюється. Тут ми встановили 1 ГБ, оскільки загальний обсяг пам’яті становив 8 ГБ. При меншому обсязі пам’яті можна встановити 512M (512 мегабайт).

Далі драйвер JDBC з MySQL переміщують у <tomcat>/lib, де <tomcat> — це каталог інсталяції Tomcat.

  • у [1], у властивостях Tomcat зазначаємо його каталог інсталяції <tomcat>,
  • у <tomcat>/lib [2] розміщуємо оновлений драйвер JDBC замість MySQL та [3].

Потім видаляємо залежність проекту від драйвера JDBC, замінивши його на MySQL або [4].

Після цього тестуємо додаток. Виявляється, що додаток можна багаторазово завантажувати та вивантажувати. Однак проблеми з витоком пам’яті не вирішені. Вони просто проявляються пізніше.

4.4. Conclusion

Ми перенесли додаток JSF / EJB / Glassfish у середовище JSF / Spring / Tomcat. Це було зроблено, в основному, шляхом копіювання та вставлення між двома проектами. Це стало можливим завдяки значній схожості технологій Spring та EJB3. EJB3 було створено після того, як Spring продемонстрував вищу продуктивність порівняно з EJB2. Тож EJB3 перейняв найкращі ідеї Spring.

4.5. Тестування з Eclipse

  • у [1] імпортуємо три проекти Spring,
  • у [2] вибираємо тест JUnit з рівня [DAO] і запускаємо його у [3],
  • у [4] тест пройшов успішно,
  • у [5] — журнали консолі.
  • у [6A] [6B] запускається консольний клієнт рівня [métier],
  • у [7] — отримане відображення консолі,
  • у [8] [9] запускається веб-проект на сервері Tomcat 7 [10],
  • до [11], головна сторінка додатка відображається у вбудованому браузері Eclipse.