Budowanie aplikacji Java EE wykorzystując Apache Maven


Wykonując ćwiczenia przygotowujące do SCBCD oparłem się na tworzeniu małych projektów Eclipse’owych oraz deploy’owaniu ich za pomocą wtyczki do obsługi serwera JBoss. Przyszedł jednak czas na odświeżenie i ugruntowanie wiadomości z zakresu Maven oraz sposobu pakowania archiwum EAR. Stworzę więc prosty projekt Java EE składający się z kilku modułów:

  • Model – przechowujący mapowanie ORM i pliki encji.
  • View – moduł widoku aplikacji opartym na JSF.
  • Controller – warstwa serwisów aplikacji wykorzystująca EJB.
  • Utils – pomocniczy moduł z często używanymi funkcjami.
  • EAR – projekt budujący archiwum EAR.

W celu stworzenia głównego katalogu projektu wydajemy z konsoli komendę:

mvn archetype:generate -DarchetypeArtifactId=maven-archetype-quickstart -DgroupId=javaee-app -DartifactId=javaee-app -Dversion=1.0

Następnie edytujemy plik pom.xml i zmieniamy sposób pakowania (tag packaging) na pom. Kolejna czynność polega na stworzeniu wszystkich modułów pomocniczych:

mvn archetype:generate -DarchetypeArtifactId=maven-archetype-webapp -DgroupId=javaee-app.view -DartifactId=view -DpackageName=edu.lantoniak.view -Dversion=1.0
mvn archetype:generate -DarchetypeArtifactId=maven-archetype-quickstart -DgroupId=javaee-app.utils -DartifactId=utils -DpackageName=edu.lantoniak.utils -Dversion=1.0
mvn archetype:generate -DarchetypeArtifactId=maven-archetype-quickstart -DgroupId=javaee-app.controller -DartifactId=controller -DpackageName=edu.lantoniak.controller -Dversion=1.0
mvn archetype:generate -DarchetypeArtifactId=maven-archetype-quickstart -DgroupId=javaee-app.model -DartifactId=model -DpackageName=edu.lantoniak.model -Dversion=1.0
mvn archetype:generate -DarchetypeArtifactId=maven-archetype-quickstart -DgroupId=javaee-app.ear -DartifactId=ear -DpackageName=edu.lantoniak.ear -Dversion=1.0

Do pliku POM głównego projektu proponuję dodać wszystkie zależności związane z Javą EE, JPA oraz JSF. Plik ten w końcowej wersji powinien wyglądać następująco:

<?xml version="1.0" encoding="UTF-8"?>
<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/maven-v4_0_0.xsd">
   <modelVersion>4.0.0</modelVersion>
   <groupId>javaee-app</groupId>
   <artifactId>javaee-app</artifactId>
   <packaging>pom</packaging>
   <version>1.0</version>
   <name>javaee-app</name>
   <url>http://maven.apache.org</url>
   <dependencies>
      <dependency>
         <groupId>javaee</groupId>
         <artifactId>javaee-api</artifactId>
         <version>5</version>
         <scope>provided</scope>
      </dependency>
      <dependency>
         <groupId>javax.persistence</groupId>
         <artifactId>persistence-api</artifactId>
         <version>1.0</version>
         <scope>provided</scope>
      </dependency>
      <dependency>
         <groupId>javax.servlet</groupId>
         <artifactId>servlet-api</artifactId>
         <version>2.5</version>
         <scope>provided</scope>
      </dependency>
      <dependency>
         <groupId>javax.faces</groupId>
         <artifactId>jsf-api</artifactId>
         <version>1.2</version>
         <scope>provided</scope>
      </dependency>
      <dependency>
         <groupId>javax.faces</groupId>
         <artifactId>jsf-impl</artifactId>
         <version>1.2_13</version>
         <scope>provided</scope>
      </dependency>
      <dependency>
         <groupId>javax.servlet.jsp</groupId>
         <artifactId>jsp-api</artifactId>
         <version>2.1</version>
         <scope>provided</scope>
      </dependency>
      <dependency>
         <groupId>junit</groupId>
         <artifactId>junit</artifactId>
         <version>3.8.1</version>
         <scope>test</scope>
      </dependency>
   </dependencies>
   <modules>
      <module>view</module>
      <module>utils</module>
      <module>controller</module>
      <module>ear</module>
      <module>model</module>
   </modules>
</project>

Jeśli pobranie pewnych zależności kończy się niepowodzeniem, proponuję dodać następujące repozytorium Maven’a:

<repositories>
   <repository>
      <id>java.net1</id>
      <name>Java.Net Maven1 Repository, hosts the javaee-api dependency</name>
      <url>http://download.java.net/maven/1</url>
      <layout>legacy</layout>
   </repository>
</repositories>

Wracając jednak do pliku POM głównego projektu – na uwagę zasługuje opcja provided znacznika scope. Dzięki temu Maven, przy budowania EAR’a, nie umieszcza w nim bibliotek zainstalowanych na serwerze aplikacyjnym. Wewnątrz tagu modules wymienione zostały wszystkie moduły, z których składa się aplikacja. Zależności między modułami umieszcza się w plikach POM konkretnych modułów wraz z odniesieniem do parent’a, czyli projektu głównego (patrz do kodu źródłowego).

Moduł Controller
Moduł Controller korzysta z EJB, do którego zależność umieszczona została w głównym pliku POM projektu. W celu zbudowania projektu EJB, skorzystać należy z dodatku maven-ejb-plugin:

<plugin>
   <groupId>org.apache.maven.plugins</groupId>
   <artifactId>maven-ejb-plugin</artifactId>
   <configuration>
      <ejbVersion>3.0</ejbVersion>
   </configuration>
</plugin>

Minimalna konfiguracja wymaga jedynie wyspecyfikowanie używanej wersji EJB. Moduł Controller zależeć będzie od dwóch wewnętrznych modułów – Model i Utils (w końcu ciężko byłoby korzystać z JPA po stronie ziaren EJB bez klas encji):

<dependency>
   <groupId>javaee-app.model</groupId>
   <artifactId>model</artifactId>
   <version>1.0</version>
   <scope>compile</scope>
</dependency>
<dependency>
   <groupId>javaee-app.utils</groupId>
   <artifactId>utils</artifactId>
   <version>1.0</version>
   <scope>compile</scope>
</dependency>

Niestety oddzielenie modelu aplikacji (mapowania ORM) od warstwy EJB ciągnie za sobą dwie poważne wady. Deklaracja Persistence Unit pozostać musi po stronie modułu Controller’a, ponieważ zasięg widoczności PU nie wychodzi poza archiwum JAR, a umieszczenie korzenia PU bezpośrednio w archiwum EAR uznałem za gorszy pomysł. Druga bolączka to konieczność wyspecyfikowania nazwy archiwum JAR, w którym znajdą się klasy poszczególnych encji, w pliku persistence.xml (controller/main/resources/META-INF/persistence.xml, linijka 7):

<?xml version="1.0" encoding="UTF-8"?>
<persistence version="1.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_1_0.xsd">
   <persistence-unit name="javaee-appPU">
      <jta-data-source>java:/OracleDS</jta-data-source>
      <jar-file>model-1.0.jar</jar-file>
   </persistence-unit>
</persistence>

Moduł EAR
Moduł EAR odpowiada za stworzenia archiwum EAR (w tym za generację pliku application.xml, spakowanie pozostałych modułów etc.), które w dalszych krokach podlega instalacji na serwerze aplikacyjnym. Do budowy archiwum zastosuję plugin maven-ear-plugin:

<plugin>
   <artifactId>maven-ear-plugin</artifactId>
   <version>2.4.2</version>
   <configuration>
      <jboss>
         <version>5</version>
      </jboss>
      <modules>
         <ejbModule>
            <groupId>javaee-app.controller</groupId>
            <artifactId>controller</artifactId>
         </ejbModule>
         <webModule>
            <groupId>javaee-app.view</groupId>
            <artifactId>view</artifactId>
            <contextRoot>view</contextRoot>
         </webModule>
         <jarModule>
            <groupId>javaee-app.model</groupId>
            <artifactId>model</artifactId>
            <includeInApplicationXml>true</includeInApplicationXml>
         </jarModule>
         <jarModule>
            <groupId>javaee-app.utils</groupId>
            <artifactId>utils</artifactId>
            <includeInApplicationXml>true</includeInApplicationXml>
         </jarModule>
      </modules>
   </configuration>
</plugin>

Instrukcje wewnątrz znacznika modules specyfikują poszczególne moduły aplikacji wraz z ich typami (ejbModule, webModule, jarModule). Ponadto moduły Model i Utils powinny zostać uwzględnione w pliku application.xml (webModule i ejbModule są tam dodane domyślnie – odpowiednio w tagach web i ejb). Powoduje to konieczność ustawienia flagi includeInApplicationXml na true. Plugin maven-ear-plugin umożliwia także zmianę Context Root, pod którym deploy’owana jest aplikacji (linia 16).

Moduł Model, Utils i View
Konfiguracja Maven’a dla tych projektów nie wyróżnia się niczym szczególnym. Zawiera ona jedynie deklarację zależności między wewnętrznymi i zewnętrznymi bibliotekami.

Co dalej?
Aplikację zbudowaną przy pomocy polecenia mvn clean package (plik ear/target/ear-1.0.ear) umieścić można na serwerze JBoss korzystając z konsoli administratora (http://localhost:8080/admin-console/) lub wtyczki do Maven’a jboss-maven-plugin (http://mojo.codehaus.org/jboss-maven-plugin/).

Podczas tworzenia projektu Maven’owego pod EJB, JPA i JSF wzorowałem się na tutorialach Jacka Laskowskiego i Alexander Shvets:

Mam nadzieję, że mój krótki wpis, udostępniona przykładowa aplikacja oraz ich tutoriale pomogą w tzw. szybkim starcie z Java EE i Maven.

Źródła projektu: JavaEE-app.zip.
Errata: Biblioteki zewnętrzne, EAR i Maven.

Uwaga! Dla przejrzystości artykułu nie omawiałem wszystkich zagadnień wykorzystywanych podczas dewelopmentu aplikacji Java EE (mapowania ORM, zasady działania managed bean’ów w JSF, konfiguracji web.xml, persistence.xml etc.). Zachęcam do ściągnięcia projektu i obejrzenia źródeł.

Advertisements

7 Responses to Budowanie aplikacji Java EE wykorzystując Apache Maven

  1. Jędrek says:

    Witaj
    Ciekawy artykuł
    Czy możesz dodać do artykułu odpowiednie repozytoria, bo przy “mvn clean package” dostaję:
    Missing:
    ———-
    1) javaee:javaee-api:jar:5

    Try downloading the file manually from the project website.

    Then, install it using the command:
    mvn install:install-file -DgroupId=javaee -DartifactId=javaee-api -Dversion=5 -Dpackaging=jar -Dfile=/path/to/file

    Alternatively, if you host your own repository you can deploy the file there:
    mvn deploy:deploy-file -DgroupId=javaee -DartifactId=javaee-api -Dversion=5 -Dpackaging=jar -Dfile=/path/to/file -Durl=[url] -DrepositoryId=[id]

    Path to dependency:
    1) javaee-app.model:model:jar:1.0
    2) javaee:javaee-api:jar:5

  2. Jędrek says:

    Dzięki za podpowiedź! Powiem Ci Tyle, że bardzo mi pomógł Twój krótki ale konkretny artykuł
    Mam jedynie problem z dokonfigurowaniem tej aplikacyjki do plików *.xhtml. Czy da się to w prosty sposób zrobić?

    Gdy dodam do pliku faces-config.xml

    com.sun.facelets.FaceletViewHandler

    to dostaję
    23:14:14,166 ERROR [[/bibliographic_database]] Exception sending context initialized event to listener instance of class org.jboss.web.jsf.integration.config.JBossJSFConfigureListener
    com.sun.faces.config.ConfigurationException: CONFIGURATION FAILED!
    Source Document: jndi:/localhost/bibliographic_database/WEB-INF/faces-config.xml
    Cause: Unable to find class ‘com.sun.facelets.FaceletViewHandler’

    Gdy dodam do pom.xml (view)

    com.sun.facelets
    jsf-facelets
    1.1.14
    compile

    to dostaję:
    23:16:39,126 SEVERE [viewhandler] Error Rendering View[/pages/home.xhtml]
    java.lang.NullPointerException
    at com.sun.facelets.compiler.NamespaceHandler.apply(NamespaceHandler.java:49)

    Mam jboss 5.1.0. Czy uruchomienie plików xhtml jest możliwe, czy błądzę….?

    • Sam nigdy z facelet’ów nie korzystałem, więc ciężko jest mi wskazać konkretne rozwiązanie. Po chwili przeglądania Google doszedłem do: http://blog.temposwc.com/2009/05/tip-use-facelets-1115-in-jboss-5x.html. Może problemem jest wersja biblioteki? Próbowałeś z 1.1.15? Poczytaj też jak dodać zewnętrzną bibliotekę do projektu Maven’owego deploy’owanego na serwer aplikacyjny. Bardzo skrótowo wspomniałem o tym przy okazji EJB i Spring: https://lukaszantoniak.wordpress.com/2010/10/03/ejb3-i-spring/. Jeśli odpowiednia wersja facelets jest na serwerze aplikacyjnym to wystarczy ustawić scope na provided. Możesz bibliotekę dołączyć jako osobny moduł (tak jak zrobiłem to ze Spring), ale to brzydkie rozwiązanie. Jest jeszcze katalog WEB-INF/lib, specjalnie przeznaczony do tych celów :).

      • Jędrek says:

        Witaj
        Zapomniałem wcześniej napisać i podziękować za pomoc. Generalnie pomogła tak jak sugerowałeś zmiana wersji. Fajnie zaczęło działać z faceletami… Dzięki za dobry punkt startu. Pozdrawiam

  3. Mateusz says:

    Hej. Próbuję zrobić w moim małym projekcie podział na moduły i w ten sposób trafiłem na Twoją notkę. Próbuję skompilować Twój projekt i dostaję dziwnymi błędami w twarz. Maven jest dla mnie jeszcze szarą magią i nie mogę się rozczytać w jego błędach. Jeśli mógłbyś określić co trzeba dopisać/zrobić żeby mvn clean package przeszedł to będę bardzo wdzięczny (http://dl.dropbox.com/u/143371/log.txt)

    W moim projekcie natomiast zatrzymałem się na etapie, gdy sam maven mi akceptuje zależności, ale w kodzie jednego z modułów nie mam dostępu do klas drugiego pomimo zastosowania bloku dependencies… Jeśli i na to masz radę – będę wdzięczny!

    • Witam!

      Z logu wnioskuję, iż używasz nowszej wersji Maven’a niż ja.
      Komunikat “‘build.plugins.plugin.version’ for org.apache.maven.plugins:maven-compiler-plugin is missing” oznacza, że nie podajesz explicite wersji plugin’u, z której zamierzasz korzystać. Problem ten występuje w modułach javaee-app.model, javaee-app.view, javaee-app.utils oraz javaee-app.controller. Przy deklaracji maven-compiler-plugin powinieneś dopisać wymagany numer wersji, czyli na przykład:
      <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-compiler-plugin</artifactId>
      <version>2.3.2</version>

      Ponadto moduł kontrolera oczekuje zdefiniowania wersji maven-ejb-plugin.

      Błąd, jaki uniemożliwia kompilację projektu to “Failed to execute goal on project model: Could not resolve dependencies for project javaee-app.model:model:jar:1.0: The following artifacts could not be resolved: javaee:javaee-api:jar:5, javax.faces:jsf-impl:jar:1.2_13: Failure to find javaee:javaee-api:jar:5 in http://repo1.maven.org/maven2 was cached in the local repository, resolution will not be reattempted until the update interval of central has elapsed or updates are forced -> [Help 1]”. Tak jak pisałem w artykule, brakujące artefakty pobrać można z repozytorium http://download.java.net/maven/1. Niestety Twoja wersja Maven’a nie rozpoznaje layout’u typu “legacy” (“‘repositories.repository.layout’ for java.net1 uses the unsupported value ‘legacy’, artifact resolution might fail.”). Proponuję usunąć problematyczny wiersz, lub samodzielnie zainstalować wymagane biblioteki w lokalnym repo.

      “W moim projekcie natomiast zatrzymałem się na etapie, gdy sam maven mi akceptuje zależności, ale w kodzie jednego z modułów nie mam dostępu do klas drugiego pomimo zastosowania bloku dependencies… Jeśli i na to masz radę – będę wdzięczny!”
      Nie jestem w stanie pomóc bez wglądu w kod źródłowy.

      Jeśli próbujesz uzyskać zwykły projekt Maven z podziałem na moduły, to proponuję poszukać mniej skomplikowanego przykładu w Internecie. Dobre miejsce do rozpoczęcia nauki to wiki Jacka Laskowskiego. Mój projekt przeznaczony jest do instalacji na serwerze aplikacyjnym, przez co posiada nadmiarowe dla Ciebie zależności – np. EJB i JSF.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: