4. Przykładowa aplikacja – 02: rdvmedecins-jsf2-spring
Teraz zamierzamy przenieść poprzednią aplikację do środowiska Spring / Tomcat:
![]() |
Jest to faktycznie przeniesienie aplikacji. Wychodzimy od poprzedniej aplikacji i dostosowujemy ją do nowego środowiska. Omówimy jedynie zmiany. Są one trzech rodzajów:
- serwerem nie jest już Glassfish, lecz Tomcat – lekki serwer, który nie posiada kontenera EJB,
- w miejsce EJB zastosujemy Spring, głównego konkurenta EJB i [http://www.springsource.com/],
- zastosowaną implementacją JPA będzie Hibernate zamiast EclipseLink.
Ponieważ będziemy często korzystać z funkcji kopiowania i wklejania między starym a nowym projektem, pozostawiamy poprzednie projekty otwarte w NetBeans:
![]() |
Korzystanie z frameworka Spring wymaga pewnej wiedzy, którą można znaleźć w [ref7] (patrz strona 166).
4.1. Warstwy [DAO] i [JPA]
![]() |
4.1.1. Projekt NetBeans
Tworzymy projekt Maven typu [Java Application]:
![]() | ![]() | ![]() |
![]() |
- w [1], utworzony projekt,
- w [2], ten sam projekt pozbawiony pakietów z [Source Packages] i [Test Packages] oraz zależności [junit-3.8.1].
Najtrudniejszą rzeczą w projektach Maven jest znalezienie odpowiednich zależności. W przypadku tego projektu Spring / JPA / Hibernate są to następujące zależności:
<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>
- wiersze 18–29: dla Hibernate,
- wiersze 30–34: dla sterownika JDBC z MySQL,
- wiersze 35–41: dla testu JUnit,
- wiersze 42–51: dla puli połączeń Apache Commons o nazwie DBCP. Pula połączeń to zbiór otwartych połączeń. Gdy aplikacja potrzebuje połączenia, zwraca się o nie do puli. Gdy nie jest już potrzebne, zwraca je. Połączenia są otwierane podczas uruchamiania aplikacji i pozostają otwarte przez cały czas jej działania. Pozwala to uniknąć kosztów związanych z wielokrotnym otwieraniem i zamykaniem połączeń. Ten typ puli istniał w Glassfish, ale jego wykorzystanie było dla nas przezroczyste. Tak samo będzie w tym przypadku, ale musimy go zainstalować i skonfigurować,
- wiersze 52–75: dla Springa.
Dodajmy te zależności i skompilujmy projekt:
![]() |
- w [1], kompilujemy projekt, co zmusi Maven do pobrania zależności,
- w [2], a następnie pojawią się one w gałęzi [Dependencies]. Jest ich bardzo wiele, ponieważ same frameworki Hibernate i Spring mają bardzo liczne zależności. Również w tym przypadku, dzięki Mavenowi, nie musimy się nimi martwić. Są one pobierane automatycznie.
Teraz, gdy mamy już zależności, wklejamy kod projektu EJB z warstwy [dao] do projektu Spring w warstwie [dao]:
![]() |
- do [1], kopiujemy do projektu źródłowego,
- w pliku [2] wklejamy do projektu docelowego,
- w pliku [3] – wynik.
Po zakończeniu kopiowania należy poprawić błędy.
4.1.2. Pakiet [exceptions]
![]() |
Klasa [RdvMedecinsExceptions] [1] zawiera błędy spowodowane pakietem [javax] w wierszu 4, który już nie istnieje. Jest to pakiet właściwy dla EJB. Błąd w wierszu 6 wynika z błędu w wierszu 4. Usuwamy te dwa wiersze. Spowoduje to usunięcie błędów dotyczących pakietu [2].
4.1.3. Pakiet [jpa]
![]() |
- w [1] klasa [Creneau] zawiera błąd z powodu braku pakietu walidacyjnego w wierszu [5]. Można było dodać ten pakiet do zależności projektu. Jednak podczas testów Hibernate zgłasza z jego powodu wyjątek. Ponieważ nie jest on niezbędny dla naszej aplikacji, usunęliśmy go. Aby poprawić klasę, wystarczy usunąć wszystkie błędne wiersze [2]. Czynność tę wykonujemy dla wszystkich błędnych klas.
4.1.4. Pakiet [dao]
Jesteśmy w następnym punkcie:
![]() |
- w [1], obie poprawione pakiety,
- w [2], pakiet [dao]. Ponieważ nie ma już pliku EJB, nie ma już również pojęcia interfejsu zdalnego i lokalnego w pliku EJB. Usuwamy je z pliku [3].
![]() |
- W przypadku [1] błędy klasy [DaoJpa] mają dwa źródła:
- import pakietu powiązanego z EJB (wiersze 6–8);
- użycie interfejsów lokalnego i zdalnego, które właśnie usunęliśmy.
Usuwamy błędne wiersze i zamiast lokalnego i zdalnego interfejsu [2] używamy interfejsu [IDao].
![]() |
W projekcie EJB klasa [DaoJpa] była singletonem, a jej metody były wykonywane w ramach transakcji. Zobaczymy, że klasa [DaoJpa] będzie beanem zarządzanym przez Spring. Domyślnie każdy bean Springa jest singletonem. To tyle, jeśli chodzi o pierwszą właściwość. Drugą uzyskuje się dzięki adnotacji @Transactional w Springu [3]:
![]() |
Po wykonaniu tych czynności projekt nie zawiera już żadnych błędów [4].
4.1.5. Konfiguracja warstwy [JPA]
W projekcie EJB skonfigurowaliśmy warstwę [JPA] za pomocą pliku [persistence.xml]. Tutaj mamy warstwę [JPA], więc musimy utworzyć ten plik. W projekcie EJB wygenerowaliśmy go za pomocą Glassfish. Tutaj tworzymy go ręcznie. Głównym powodem jest to, że część konfiguracji pliku [persistence.xml] zostaje przeniesiona do samego pliku konfiguracyjnego Springa.
Tworzymy plik [persistence.xml]:
![]() |
o następującej treści:
<?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>
- wiersz 3: nadajemy nazwę jednostce trwałości,
- wiersz 3: typ transakcji to RESOURCE_LOCAL. W projekcie EJB był to JTA, co oznaczało, że transakcje były zarządzane przez kontener EJB. Wartość RESOURCE_LOCAL oznacza, że aplikacja sama zarządza swoimi transakcjami. W tym przypadku będzie to realizowane za pośrednictwem Springa,
- wiersze 4–7: pełne nazwy czterech encji JPA. Jest to opcjonalne, ponieważ Hibernate automatycznie wyszukuje je w pliku ClassPath projektu.
To wszystko. Nazwa dostawcy JPA, jego właściwości oraz cechy JDBC źródła danych znajdują się teraz w pliku konfiguracyjnym Springa.
4.1.6. Plik konfiguracyjny Springa
Wspomnieliśmy, że klasa [DaoJpa] jest beanem zarządzanym przez Spring. Odbywa się to za pomocą pliku konfiguracyjnego. Plik ten będzie zawierał również konfigurację dostępu do bazy danych oraz zarządzanie transakcjami. Musi on znajdować się w katalogu ClassPath projektu. Umieszczamy go w gałęzi [Other sources]:
![]() |
Plik [spring-config-dao.xml] ma następującą treść:
<?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">
<!-- warstwy aplikacyjne -->
<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>
<!-- źródło danych 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>
<!-- menedżer transakcji -->
<tx:annotation-driven transaction-manager="txManager" />
<bean id="txManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory" />
</bean>
<!-- przetwarzanie wyjątków -->
<bean class="org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor" />
<!-- trwałość -->
<bean class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor" />
</beans>
Jest to plik zgodny ze Spring 2.x. Nie staraliśmy się wykorzystać nowych funkcji wersji 3.x.
- wiersze 2–4: tag nadrzędny <beans> w pliku konfiguracyjnym. Nie będziemy omawiać poszczególnych atrybutów tego tagu. Należy zachować ostrożność podczas kopiowania i wklejania, ponieważ błąd w jednym z tych atrybutów powoduje błędy, które czasami są trudne do zrozumienia,
- wiersz 7: bean „dao” jest odwołaniem do instancji klasy [rdvmedecins.dao.DaoJpa]. Utworzona zostanie pojedyncza instancja (singleton), która będzie implementować warstwę [dao] aplikacji,
- wiersze 24–29: zdefiniowano źródło danych. Zapewnia ono wspomnianą wcześniej usługę „puli połączeń”. Wykorzystano tutaj klasę [DBCP] z projektu Apache Commons DBCP [http://jakarta.apache.org/commons/dbcp/],
- wiersze 25–28: aby nawiązać połączenia z docelową bazą danych, źródło danych musi znać używany sterownik JDBC (wiersz 25), nazwę bazy danych URL (wiersz 26), użytkownika połączenia i jego hasło (wiersze 27–28),
- wiersze 10–21: konfigurują warstwę JPA,
- wiersz 10: definiuje bean typu [EntityManagerFactory] zdolny do tworzenia obiektów typu [EntityManager] w celu zarządzania kontekstami trwałości. Instancja klasy [LocalContainerEntityManagerFactoryBean] jest dostarczana przez Spring. Do instancjonowania potrzebuje ona pewnej liczby parametrów, zdefiniowanych w wierszach 11–20,
- wiersz 11: źródło danych, które ma być wykorzystane do uzyskania połączeń z SGBD. Jest to źródło [DBCP] zdefiniowane w wierszach 24–29,
- wiersze 12–20: implementacja JPA, która ma być używana,
- wiersz 13: określa Hibernate jako implementację JPA, której należy użyć,
- wiersz 14: dialekt SQL, którego Hibernate ma używać z docelowym SGBD, w tym przypadku MySQL5,
- wiersz 16 (w komentarzach): wymaga, aby polecenia SQL wykonywane przez Hibernate były rejestrowane w konsoli,
- wiersz 17 (w komentarzach): nakazuje, aby przy uruchomieniu aplikacji wygenerowano bazę danych (drop i create),
- wiersz 32: wskazuje, że transakcje są zarządzane za pomocą adnotacji Java (mogłyby one również zostać zadeklarowane w pliku spring-config.xml). Chodzi w szczególności o adnotację @Transactional występującą w klasie [DaoJpa],
- wiersze 33–35: definiują menedżera transakcji, który ma być używany,
- wiersz 33: menedżer transakcji to klasa dostarczana przez Spring,
- w wierszu 34: menedżer transakcji Springa musi znać klasę EntityManagerFactory, która zarządza warstwą JPA. Jest to klasa zdefiniowana w wierszach 10–21,
- wiersz 41: definiuje klasę obsługującą adnotacje trwałości Springa,
- wiersz 38: definiuje klasę Spring, która obsługuje w szczególności adnotację @Repository, dzięki której klasa opatrzona tą adnotacją kwalifikuje się do przekształcania natywnych wyjątków sterownika JDBC z SGBD na generyczne wyjątki Spring typu [DataAccessException]. Konwersja ta hermetyzuje natywny wyjątek typu JDBC w typie [DataAccessException], który posiada różne podklasy:

To przekształcenie pozwala programowi klienckiemu na generyczne obsługiwanie wyjątków niezależnie od docelowego typu SGBD. W naszym kodzie Java nie użyliśmy adnotacji @Repository. Dlatego wiersz 38 jest zbędny. Pozostawiliśmy go wyłącznie w celach informacyjnych.
To już wszystko, jeśli chodzi o plik konfiguracyjny Springa. Został on zaczerpnięty z dokumentacji Springa. Jego dostosowanie do różnych sytuacji często sprowadza się do dwóch zmian:
- zmianę docelowej bazy danych: wiersze 24–29,
- zmianę implementacji JPA: wiersze 12–20.
Podczas wykonywania kodu wszystkie bean z pliku konfiguracyjnego zostaną zainicjowane. Zobaczymy, jak to się dzieje.
4.1.7. Klasa testowa JUnit
![]() |
Wcześniej przetestowaliśmy warstwę [DAO] projektu EJB za pomocą testu JUnit. To samo robimy dla warstwy [DAO] projektu Spring:
- w [1] i [2], po skopiowaniu i wklejeniu testu JUnit między tymi dwoma projektami,
- w [3] zaimportowany test wykazuje błędy w nowym środowisku.
![]() |
Zgłoszony błąd [1] dotyczy zdalnego interfejsu EJB, który już nie istnieje. Ponadto kod inicjalizacji pola [dao] w wierszu 19 stanowił wywołanie JNDI specyficzne dla EJB (wiersze 25–28). Aby utworzyć instancję pola [dao] w wierszu 19, musimy wykorzystać plik konfiguracyjny Springa. Odbywa się to w następujący sposób:
![]() |
- wiersz 21: typ interfejsu zmienił się na [IDao],
- wiersz 28: instancjonuje wszystkie bean’y zadeklarowane w pliku [spring-config-dao.xml], w szczególności ten:
<bean id="dao" class="rdvmedecins.dao.DaoJpa" />
- w wierszu 29 zwraca się do kontekstu Spring z wiersza 28 o odwołanie do bean o id="dao". Otrzymujemy wówczas odwołanie do singletonu [DaoJpa] (class powyżej), który został zainicjowany przez Spring.
Wiersze 28–29 tworzą następujące bloki (różne kropkowane linie):
![]() |
Podczas wykonywania testów klienta JUnit instancja warstwy [DAO] została utworzona. Można zatem testować jej metody. Warto zauważyć, że do przeprowadzenia tego testu nie jest potrzebny serwer, w przeciwieństwie do testu EJB [DAO], który wymagał serwera Glassfish. Tutaj wszystko odbywa się w ramach tej samej instancji JVM.
Teraz można uruchomić test JUnit. Serwer MySQL musi być uruchomiony. Wyniki są następujące:
![]() |
Test JUnit zakończył się powodzeniem. Przyjrzyjmy się logom testu, tak jak to zrobiono podczas testu EJB:
- wiersze 1–4: logi Springa,
- wiersze 5–10: logi Hibernate,
- wiersz 11: Spring zgłasza wszystkie instancje beanów, które utworzył. Na pierwszym miejscu znajduje się bean [dao],
- wiersze 12 i kolejne: logi testu JUnit,
- wiersze 60–65: wyraźnie widać wyjątek spowodowany dodaniem terminu, który już istnieje w bazie danych. Przypominamy, że w przypadku EJB nie wystąpił ten wyjątek z powodu problemu z serializacją.
Warstwa [dao] działa poprawnie. Teraz tworzymy warstwę [métier].
4.2. Warstwa [métier]
![]() |
Postępujemy tak samo jak w przypadku warstwy [DAO], kopiując i wklejając projekt EJB do projektu Spring.
4.2.1. Projekt NetBeans
Tworzymy nowy projekt Maven typu [Java Application], oczyszczony ze wszystkiego, czego nie chcemy zachować z projektu [1]:
![]() |
4.2.2. Zależności projektu
W architekturze:
![]() |
warstwa [métier] opiera się na warstwie [dao]. Dodajemy zatem zależność od poprzedniego projektu:
![]() |
- w warstwach [1] i [2] dodajemy zależność od projektu warstwy [dao],
- w przypadku [3] zależność ta spowodowała pojawienie się kolejnych zależności, dotyczących projektu warstwy [dao].
![]() |
- w [1] i [2] skopiowano kody źródłowe Java z projektu EJB do projektu Spring,
- do [3], a zaimportowane pliki źródłowe zawierają błędy w nowym środowisku.
Najpierw usuwamy interfejsy zdalne i lokalne z warstwy [métier], które już nie istnieją w [4]:
![]() |
- w [5] błędy klasy [Metier] mają kilka przyczyn:
- użycie pakietu [javax.ejb], który już nie istnieje;
- użycie interfejsu [IDaoLocal], który już nie istnieje;
- użycie interfejsów [IMetierRemote] i [IMetierLocal], które już nie istnieją.
Należy
- usuwamy wszystkie błędne wiersze związane z pakietem [javax.ejb],
- zastępujemy interfejs [IDaoLocal] interfejsem [IDao],
- zastępujemy interfejsy [IMetierRemote] i [IMetierLocal] interfejsem [IMetier].
![]() |
- na [6], co spowoduje poprawienie klasy,
- W pliku [7] nie ma już błędów.
Usunęliśmy odniesienia do plików EJB, ale teraz musimy odzyskać ich właściwości:
![]() |
- wiersz 22: mieliśmy singleton. Tę właściwość uzyskamy, czyniąc tę klasę beanem zarządzanym przez Spring,
- wiersz 23: każda metoda była wykonywana w ramach transakcji. Osiągniemy to za pomocą adnotacji Spring @Transactional,
- wiersze 27–28: odwołanie do warstwy [DAO] uzyskiwano poprzez wstrzyknięcie z kontenera EJB. Wykorzystamy wstrzyknięcie Springa.
Kod klasy [Metier] w projekcie Spring zmienia się zatem w następujący sposób:
![]() |
To wszystko, jeśli chodzi o kod Java. Reszta dzieje się w pliku konfiguracyjnym Springa.
4.2.3. Plik konfiguracyjny Springa
Kopiujemy plik konfiguracyjny Springa z projektu warstwy [DAO] do projektu warstwy [métier]. Najpierw tworzymy gałąź [Other Resources] w projekcie warstwy [métier], jeśli jeszcze nie istnieje:
![]() |
- w [1], w zakładce [Files], tworzymy podfolder w folderze [main],
- w [2] musi nosić nazwę [resources],
- w [3], w zakładce [Projects] utworzono gałąź [Other Sources].
Możemy teraz przejść do kopiowania i wklejania pliku konfiguracyjnego Springa:
![]() |
- w [1] kopiujemy plik z projektu [DAO] do projektu [métier] [2],
- do [3], skopiowany plik.
Skopiowany plik konfiguracyjny konfiguruje warstwę [DAO]. Dodajemy do niego bean w celu skonfigurowania warstwy [métier]:
- wiersz 2: bean warstwy [DAO],
- wiersze 3–5: bean warstwy [métier],
- wiersz 3: bean nosi nazwę métier (atrybut id) i jest instancją klasy [rdvmedecins.metier.service.Metier] (atrybut class). Bean ten zostanie zainicjowany, podobnie jak pozostałe, podczas uruchamiania aplikacji.
Przypomnijmy kod beana [rdvmedecins.metier.service.Metier]:
package rdvmedecins.metier.service;
...
public class Metier implements IMetier, Serializable {
// warstwa DAO
private IDao dao;
public Metier() {
}
- wiersz 8: pole [dao] zostanie zainicjowane przez Springa jednocześnie z beanem métier. Wróćmy do definicji tego beana w pliku konfiguracyjnym Springa:
- wiersz 4: tag <property> służy do inicjalizacji pól instancjonowanego beana. Nazwa pola jest podana przez atrybut name. Zatem instancjonowane zostanie pole dao klasy [rdvmedecins.metier.service.Metier]. Odbędzie się to za pomocą metody setDao, która musi istnieć. Wartość, która zostanie mu przypisana, to wartość atrybutu ref. W tym przypadku jest to odniesienie do bean dao z wiersza 2.
Mówiąc prościej, w kodzie:
package rdvmedecins.metier.service;
...
public class Metier implements IMetier, Serializable {
// warstwa DAO
private IDao dao;
public Metier() {
}
Pole „dao” w wierszu 19 zostanie zainicjowane przez Springa za pomocą odwołania do warstwy [dao]. Właśnie tego chcieliśmy. Pole „dao” zostanie zainicjowane przez Springa za pomocą obiektu setter, który musimy dodać:
// metoda setter
public void setDao(IDao dao) {
this.dao = dao;
}
Zmieniamy nazwę pliku konfiguracyjnego Springa, aby uwzględnić te zmiany:
![]() |
Jesteśmy teraz gotowi do przeprowadzenia testu. Wykorzystujemy test konsolowy użyty wcześniej do przetestowania EJB i [Metier].
4.2.4. Test warstwy [métier]
Test zostanie przeprowadzony w oparciu o następującą architekturę:
![]() |
Kopiujemy test konsolowy z projektu EJB do projektu Spring:
![]() |
- do [1] i [2], a po skopiowaniu i wklejeniu między tymi dwoma projektami
- w projekcie [3] zaimportowany kod zawiera błędy.
![]() |
W zaimportowanym kodzie występują dwa rodzaje błędów:
- wiersz 13: interfejs [IMetierRemote] został zastąpiony interfejsem [IMetier],
- wiersze 24–27: instancja warstwy [métier] nie jest już tworzona poprzez wywołanie JNDI, lecz poprzez instancjonowanie beanów z pliku konfiguracyjnego Springa.
Poprawiamy te dwa punkty:
![]() |
- wiersz 22: wykorzystywany jest plik [spring-config-metier-dao.xml]. Wszystkie komponenty z tego pliku są wówczas instancjonowane. Wśród nich znajdują się następujące:
<!-- warstwy aplikacyjne -->
<bean id="dao" class="rdvmedecins.dao.DaoJpa" />
<bean id="metier" class="rdvmedecins.metier.service.Metier">
<property name="dao" ref="dao"/>
</bean>
Te dwa bean'y reprezentują warstwy [DAO] i [métier] architektury testu:
![]() |
Po wykonaniu tych czynności test może się rozpocząć:
![]() |
Logi testu wyglądają następująco:
- wiersze 1–4: logi Springa i Hibernate’a,
- wiersz 5: bean’y instancjonowane przez Spring. Warto zwrócić uwagę na bean’y typu DAO i biznesowe,
- wiersze 6–53: logi testu. Są one zgodne z wynikami uzyskanymi w teście projektu EJB. Odsyłamy czytelnika do komentarzy dotyczących tego testu (punkt 3.5.3).
Stworzyliśmy warstwę [métier]. Przechodzimy do ostatniej warstwy, warstwy [web].
4.3. Warstwa [web]
![]() |
Aby utworzyć warstwę [web], postąpimy tak samo jak w przypadku dwóch pozostałych warstw, kopiując i wklejając z warstwy [web] projektu EJB.
4.3.1. Projekt NetBeans
Najpierw tworzymy projekt internetowy:
![]() |
- w [1] tworzymy nowy projekt,
- w [2] tworzymy projekt Maven typu [Web Application],
- w [3] nadajemy mu nazwę,
![]() |
- w [4], tym razem wybieramy serwer Tomcat, a nie Glassfish, który był używany w projekcie EJB,
- w projekcie [5], uzyskany projekt,
- w [6], projekt po usunięciu [index.jsp] oraz pakietu z [Source Packages].
4.3.2. Zależności projektu
Przyjrzyjmy się architekturze projektu:
![]() |
Warstwa [web] wymaga warstw [métier], [DAO] oraz [JPA]. Warstwy te są częścią dwóch projektów, które właśnie skompilowaliśmy. Stąd wynika zależność od każdego z tych projektów:
![]() |
- w projekcie [1] dodajemy zależność od projektu Spring / biznesowego,
- w projekcie [2] dodano projekt Spring / biznesowy. Ponieważ sam miał zależność od projektu Spring / DAO / JPA, został on automatycznie dodany do zależności [3].
Wróćmy do struktury naszej aplikacji:
![]() |
Warstwa internetowa to warstwa JSF. Potrzebujemy więc bibliotek Java Server Faces. Serwer Tomcat ich nie posiada. Zależność ta nie będzie więc miała zakresu (scope) [provided], jak miało to miejsce w przypadku serwera Glassfish, ale zakresu [compile], który jest zakresem domyślnym, gdy nie określa się zakresu.
Dodajemy te zależności bezpośrednio w kodzie [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>
- W pliku [pom.xml] dodano wiersze 7–16. Są to zależności względem JSF. Są to te same zależności, które są używane w projekcie EJB / Glassfish. Należy zauważyć, że nie zawierają one tagu <scope>. Domyślnie mają więc zakres [compile]. Biblioteka JSF zostanie zatem dołączona do archiwum [war] projektu internetowego.
Po dodaniu tych zależności do pliku [pom.xml] kompilujemy projekt, aby zostały one pobrane.
4.3.3. Przeniesienie projektu JSF / Glassfish do projektu JSF / Tomcat
Kopiujemy cały kod z projektu JSF / Glassfish do projektu JSF / Tomcat:
![]() |
- [1, 2, 3]: kopiowanie stron internetowych ze starego projektu do nowego,
![]() |
- [1, 2, 3]: kopiowanie kodu Java ze starego projektu do nowego. Występują błędy. To normalne. Poprawimy je,
![]() |
- w [1], w zakładce [Files] programu NetBeans tworzymy podfolder [resources] w folderze [main],
- co powoduje utworzenie w zakładce [Projects] gałęzi [Other Sources] [3],
![]() |
- [1, 2, 3]: kopiuje się pliki wiadomości ze starego projektu do nowego projektu.
4.3.4. Zmiany w zaimportowanym projekcie
Zauważyliśmy, że zaimportowany kod Java zawiera błędy. Przyjrzyjmy się im:
![]() |
- w pliku [1] błąd dotyczy wyłącznie komponentu [Application],
- w przypadku [2] błąd wynika wyłącznie z tego, że interfejs [IMetierLocal] już nie istnieje. W tym miejscu może budzić zdziwienie fakt, że wiersz 20 nie został oznaczony jako błędny. Adnotacja @EJB odnosi się wyraźnie do EJB i jest tutaj rozpoznawana. Wynika to z obecności zależności [javaee-web-api-6.0] [3]. Java EE 6 wprowadziła architekturę, która umożliwia wdrażanie aplikacji internetowej opartej na EJB bez interfejsu zdalnego, na serwerach nieposiadających kontenera EJB. Wystarczy, aby serwer zapewniał zależność [javaee-web-api-6.0]. Widać bowiem, że ma ona zakres [provided] [3].
W tym przypadku nie będziemy korzystać z zależności [javaee-web-api-6.0]. Usuwamy ją: [1]:
![]() |
![]() |
Powoduje to pojawienie się nowych błędów [2]. Zaczniemy od tych dotyczących komponentu [Form]:
![]() |
- W przypadku [1] błędne wiersze wynikają z braku pakietu [javax]. Usuwamy je wszystkie z pliku [2]. Błędne wiersze sprawiały, że klasa [Form] była beanem o zasięgu sesji (wiersze 18–20 w pliku [1]). Ponadto bean [Application] był wstrzykiwany w wierszu 25. Informacje te zostaną przeniesione do pliku konfiguracyjnego JSF i [faces-config.xml].
Przejdźmy do komponentu [Application]:
![]() |
Usuwamy wszystkie błędne wiersze z pliku [1] i zmieniamy interfejs [IMetierLocal] w wierszach 13 i 21 na [IMetier]. W pliku [2] nie ma już błędów. W pliku [1] usunęliśmy wiersze 15–16, które definiowały klasę [Application] jako bean o zasięgu application.. Informacja ta zostanie przeniesiona do pliku konfiguracyjnego JSF [faces-config.xml]. Usunęliśmy również wiersz 20, który wstrzykiwał do bean'a odwołanie do warstwy [métier]. Teraz zostanie ono zainicjowane przez Spring. Mamy już niezbędny plik konfiguracyjny – jest to plik z projektu Spring / Métier. Kopiujemy go:
![]() |
- do [1, 2], kopiujemy plik konfiguracyjny Springa z projektu Spring / Métier do projektu Spring / JSF,
![]() |
W [3] – wynik.
W beanie [Application] należy wykorzystać ten plik konfiguracyjny, aby uzyskać odwołanie do warstwy [métier]. Odbywa się to w jego metodzie [init]:
package beans;
...
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Application {
// warstwa biznesowa
private IMetier metier;
...
public Application() {
}
@PostConstruct
public void init() {
try {
// instancja warstwy [métier]
ApplicationContext ctx = new ClassPathXmlApplicationContext("spring-config-metier-dao.xml");
metier = (IMetier) ctx.getBean("metier");
// lekarze i klienci są zapisywani w pamięci podręcznej
...
} catch (Throwable th) {
...
}
...
}
- wiersz 20: tworzone są instancje beanów z pliku konfiguracyjnego Springa,
- wiersz 21: pobierane jest odwołanie do fasoli biznesowej, a więc do warstwy [métier].
Ogólnie rzecz biorąc, instancjonowanie beanów Springa powinno odbywać się w metodzie init beana o zasięgu aplikacji. Istnieje również inna metoda, w której instancjonowanie beanów odbywa się za pośrednictwem serwletu Springa. Wymaga to zmiany pliku [web.xml] oraz dodania zależności od artefaktu [spring-web]. Nie zrobiliśmy tego w tym przypadku, aby zachować spójność z tym, co zostało zastosowane w poprzednich kodach.
Usunęliśmy adnotacje z klas [Application] i [Form], które definiowały je jako fasolki JSF. Klasy te muszą pozostać fasolkami JSF. Zamiast adnotacji używamy pliku konfiguracyjnego JSF [WEB-INF / faces.config.xml] do deklarowania beanów.
![]() |
Plik ten ma teraz następującą postać:
<?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>
<!-- plik komunikatów -->
<resource-bundle>
<base-name>
messages
</base-name>
<var>msg</var>
</resource-bundle>
<message-bundle>messages</message-bundle>
</application>
<!-- 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>
<!-- bean formularza -->
<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>
- wiersze 10–19 konfigurują plik komunikatów. Była to jedyna konfiguracja, jaką mieliśmy w projekcie JSF / EJB,
- wiersze 21–35 deklarują komponenty aplikacji JSF. Była to standardowa metoda stosowana w JSF 1.x. W wersji JSF 2 wprowadzono adnotacje, ale metoda stosowana w JSF i 1.x jest nadal obsługiwana,
- wiersze 21–25: deklarują bean applicationBean,
- wiersz 22: nazwa bean. Można by pokusić się o nazwę „application”. Należy tego unikać, ponieważ jest to nazwa predefiniowanego bean w JSF,
- wiersz 23: pełna nazwa klasy bean,
- wiersz 24: jego zakres,
- wiersze 27–35: definiują bean form,
- wiersz 28: nazwa bean,
- wiersz 29: pełna nazwa klasy bean,
- wiersz 30: jego zakres,
- wiersze 31–34: definiują właściwość klasy [beans.Form],
- wiersz 32: nazwa właściwości. Klasa [beans.Form] musi posiadać pole o tej nazwie oraz odpowiadający mu setter,
- wiersz 33: wartość pola. W tym przypadku jest to odwołanie do bean’a applicationBean zdefiniowanego w wierszu 21. W ten sposób dokonujemy wstrzyknięcia bean o zasięgu application do bean o zasięgu session, aby ten ostatni miał dostęp do danych o zasięgu application.
Jak wspomniano powyżej, pole [application] w beanie [beans.Form] ma zostać zainicjowane za pomocą setter. Należy zatem dodać je do klasy [beans.Form], o ile jeszcze nie istnieje:
public void setApplication(Application application) {
this.application = application;
}
4.3.5. Testowanie aplikacji
Nasza aplikacja jest teraz wolna od błędów i gotowa do testów:
![]() |
- w [1], poprawiony projekt,
- w [2] kompilujemy go,
- w [3] uruchamiamy ją. Konieczne jest uruchomienie SGBD i MySQL. Serwer Tomcat zostanie wówczas uruchomiony ([4]), jeśli jeszcze nie był uruchomiony, a następnie wyświetlona zostanie strona główna aplikacji ([5]):
![]() |
W tym momencie pojawia się omawiana aplikacja. Pozostawiamy czytelnikowi sprawdzenie, czy działa. Teraz zatrzymajmy aplikację:
![]() |
- w [1], aplikacja zostaje wyładowana,
- w [2] aplikacji już nie ma.
Przyjrzyjmy się teraz logom serwera Tomcat:
Wiersze 2 i 4 sygnalizują nieprawidłowe działanie podczas zamykania aplikacji. Wiersz 4 wskazuje na prawdopodobne ryzyko wycieku pamięci. Rzeczywiście, wyciek ten ma miejsce i po pewnym czasie NetBeans przestaje działać. Problem ten jest szczególnie irytujący, ponieważ wymaga ponownego uruchamiania NetBeans przy każdym nowym uruchomieniu projektu. Problem ten został już omówiony w dokumencie „Wprowadzenie do Struts 2 na przykładach” [http://tahe.developpez.com/java/struts2].
W Internecie można znaleźć wiele informacji na temat tego błędu. Pojawia się on podczas wielokrotnego ładowania i wyładowywania aplikacji z serwera Tomcat. Po pewnym czasie pojawia się błąd java.lang.OutOfMemoryError: PermGen space. Wydaje się, że nie ma rozwiązania pozwalającego uniknąć tego błędu, gdy wynika on z plików archiwów stron trzecich (jar), jak ma to miejsce w tym przypadku. Aby go usunąć, należy zatem ponownie uruchomić serwer Tomcat.
Można jednak opóźnić wystąpienie tego błędu. Po pierwsze, należy zwiększyć pojemność pamięci, w której doszło do przepełnienia.
![]() |
- w [1] przechodzimy do właściwości serwera Tomcat,
- w [2], w zakładce [Platform], ustalamy wartość pamięci, która ulega przepełnieniu. W tym przypadku ustawiono 1 GB, ponieważ całkowita pamięć wynosiła 8 GB. W przypadku mniejszej pamięci można ustawić 512M (512 megabajtów).
Następnie umieszczamy sterownik JDBC z MySQL w <tomcat>/lib, gdzie <tomcat> to katalog instalacyjny Tomcata.
![]() |
- w pliku [1], w właściwościach serwera Tomcat należy odnotować jego katalog instalacyjny <tomcat>,
- w <tomcat>/lib [2] umieszczamy najnowszy sterownik JDBC, zastępując MySQL i [3].
Następnie usuwamy zależność projektu od sterownika JDBC, zastępując go wersjami MySQL i [4].
![]() |
Po wykonaniu tych czynności testujemy aplikację. Stwierdzamy, że można wielokrotnie ładować i wyładowywać aplikację. Problemy z wyciekami pamięci nie zostały jednak rozwiązane. Po prostu pojawiają się one później.
4.4. Conclusion
Przenieśliśmy aplikację JSF / EJB / Glassfish do środowiska JSF / Spring / Tomcat. Odbyło się to głównie poprzez kopiowanie i wklejanie między tymi dwoma projektami. Było to możliwe, ponieważ technologie Spring i EJB3 wykazują duże podobieństwa. EJB3 powstało bowiem po tym, jak Spring okazał się wydajniejszy od EJB2. EJB3 przejął wówczas sprawdzone rozwiązania z Springa.
4.5. Testy z wykorzystaniem Eclipse
![]() |
- w [1] importujemy trzy projekty Spring,
- w [2] wybieramy test JUnit z warstwy [DAO] i uruchamiamy go w [3],
![]() |
![]() |
- w warstwie [4] test zakończył się powodzeniem,
- w [5] – logi konsoli.
![]() |
- w [6A] [6B] uruchamia się klienta konsoli warstwy [métier],
- w [7] – uzyskany obraz konsoli,
![]() |
- w [8] [9] uruchamia się projekt internetowy na serwerze Tomcat 7 [10],
![]() |
- i [11], strona główna aplikacji zostanie wyświetlona w wewnętrznej przeglądarce środowiska Eclipse.





































































