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], що має різні підкласи:

Ця переробка дозволяє клієнтській програмі обробляти винятки узагальнено, незалежно від цільового 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:
- рядки 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]:
- рядок 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:
- рядок 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] архітектури тесту:
![]() |
Після цього тест може розпочатися:
![]() |
Журнали тестування мають такий вигляд:
- рядки 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:
Рядки 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.





































































