Tworzenie aplikacji Java EE 5 z Apache Maven 2 i Glassfish

Z Jacek Laskowski - Wiki Projektanta Java EE

Serwer aplikacyjny Glassfish jest referencyjną implementacją specyfikacji Java EE 5. Jest to najnowsza "zabawka" ludzi zajmujących się rozwiązaniami przemysłowymi (ang. enterprise solutions) w Javie. Glassfish jest, z tego powodu, idealnym środowiskiem do poznawania specyfikacji Java EE 5. Przestrzegam jednak przed upatrywaniem w tym materiale prezentacji zalet i wad samego Glassfisha. Należy z pewnością do grona nielicznych szczęśliwców dostarczających implementację specyfikacji Java EE 5 i tylko z tego powodu jest tutaj wykorzystany. Jako, że Glassfish jest implementacją referencyjną Java EE 5, to chociażby z tego powodu warto zapoznać się z nim, zanim przystąpimy do poznania rozwiązań alternatywnych (np. Apache Geronimo, JBoss AS czy IBM WebSphere Application Server lub BEA WebLogic Server). Wydawać by się mogło, że jeśli tak niewiele produktów oferuje wsparcie dla Java EE 5, to w takim razie korzystamy z elementów niedostępnych powszechnie, co mogłoby dalej sugerować, że niepotrzebnie zaprzątamy sobie tym głowę. Szczęśliwie jednak wiele z zaprezentowanych w tym artykule elementów tworzenia aplikacji Java EE nie wymaga dostępności realizacji całej specyfikacji i może być jednocześnie inspiracją do kolejnych możliwych zastosowań.

Artykuł stara się przedstawić krok po kroku tworzenie aplikacji Java EE 5 przy pomocy Apache Maven 2 (M2) i Glassfish. W ramach aplikacji zaprezentowane będzie wykorzystanie JavaServer Faces (JSF) 1.2 jako technologii do budowania interfejsu użytkownika, która współpracuje z modułem Enterprise JavaBeans (EJB) 3.0 opartym o bezstanowy komponent sesyjny (ang. stateless session bean - SLSB). Stara się tym samym zaprezentować prostotę tworzenia aplikacji Java EE 5 z wykorzystaniem specyfikacji JSF i EJB 3.0 i mechanizmu annotacji wprowadzonego w samym języku Java SE w wersji 5. Jako narzędzie pomocnicze skorzystano z NetBeans IDE 5.5, jednakże jego rola nie jest wiodąca, a jedynie wspomagająca i to opcjonalnie. W poprzedniej serii artykułów o Java Persistence API (JPA), NetBeans IDE był narzędziem, w którym powstawał projekt od samego początku. W tym przypadku zaprezentowany projekt będzie prezentował elementy Java EE 5 i wsparcie M2 koncentrując się na samym tworzeniu aplikacji bez względu na zastosowane IDE. Stąd też mimo zastosowania NetBeans IDE (wraz z wtyczką do obsługi projektów opartych o M2 - Mevenide2-Netbeans), większość materiału jest również do wykorzystania podczas tworzenia aplikacji w innym wybranym zintegrowanym środowisku programistycznym, np. Eclipse IDE.

Kiedy tworzyłem ten artykuł zastanawiałem się jaki miałby być cel tworzenia projektu Java EE przy pomocy Apache Maven 2, kiedy można za pomocą kilku(nastu) ruchów myszką stworzyć dokładnie tę samą aplikację w NetBeans IDE czy Eclipse IDE (choć przy tym drugim - Eclipse IDE - skłaniałbym się do skorzystania z bogatszego rozwiązania, np. IBM Rational Application Developer 7.0, który rozbudowuje Eclipse o niezbędne wtyczki do tworzenia aplikacji Java EE). Wykorzystanie M2 to skorzystanie z możliwości narzędzia stworzonego do wspomagania zarządzania procesem budowania aplikacji bez konieczności uruchamiania IDE. A gdzie to może być przydatne? Np. podczas automatyzacji budowania i testowania aplikacji czy podczas weryfikacji jakości aplikacji. Nie zamierzam przedstawiać zalet stosowania M2 w projektach, ale napiszę, że Ci, którzy nie korzystają z niego z pewnością wiele tracą (alternatywą może być Apache Ant, który usprawnia automatyzację na przysłowiową piątkę z plusem, jednakże Apache Maven jest na szóstkę - choć nie ukrywam, że i tutaj znaleźć można kilka wad).

Materiał zakłada, że nasze środowisko ma poprawnie zainstalowane Apache Maven 2 oraz serwer Glassfish. Instrukcje instalacji znajdują się na stronie projektów.

Po przydługim wstępie przejdźmy do stworzenia bardzo ubogiej funkcjonalnie, ale bogatej technologicznie aplikacji.

Gotowa aplikacja do uruchomienia dostępna jest jako javaee5-m2-glassfish.zip. Wystarczy rozpakować paczkę i wykonać polecenie mvn install w katalogu głównym (zakładając, że wcześniej uruchomiono serwer GlassFish).

Spis treści

Aplikacja Java EE 5 pod nadzorem Apache Maven 2 krok po kroku

Utworzenie projektu głównego przy pomocy Apache Maven 2

Otwieramy interpreter linii poleceń i wykonujemy następujące polecenie.

mvn archetype:create -DgroupId=pl.jaceklaskowski.javaee5 -DartifactId=javaee5-m2-glassfish

Zakończenie polecenia to stworzenie projektu kontrolowanego przez M2 w katalogu wskazanym przez parametr artifactId, czyli javaee-m2-glassfish. Jest on projektem głównym, który następnie będzie składał się z podprojektów (które mogłyby być realizowane przez osobne zespoły programistów).

Przechodzimy do katalogu javaee-m2-glassfish.

cd javaee5-m2-glassfish

Sercem projektu kontrolowanego przez M2 jest pom.xml, który udostępnia metadane projektu. Domyślny wynik budowania projektu M2 to plik jar będący biblioteką reprezentującą projekt, co w tym konkretnie przypadku nie będzie miało jednakże miejsca. Nasz właśnie stworzony projekt będzie agregował inne podprojekty, co jest rekomendowanym sposobem zarządzania projektów wielomodułowych przez M2 (ang. multiproject) .

Edytujemy plik pom.xml wprowadzając następujące zmiany:

  • Zmieniamy wartość znacznika packaging na pom
  • Zmieniamy wartość znacznika name na Demo
  • Konfigurujemy M2 (za pomocą wtyczki maven-compiler-plugin o zależności projektu od Java SE 5)

Ostatecznie plik przyjmuje następującą postać.

<?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>pl.jaceklaskowski.javaee5</groupId>
  <artifactId>javaee5-m2-glassfish</artifactId>
  <packaging>pom</packaging>
  <version>1.0-SNAPSHOT</version>
  <name>Demo</name>
  <url>http://www.jaceklaskowski.pl/wiki/Tworzenie aplikacji Java EE 5 z Apache Maven 2 i Glassfish</url>
  <description>
    Przykladowa aplikacja Java EE 5 tworzona przy pomocy Apache Maven 2 i uruchamiana na serwerze aplikacyjnym Glassfish
  </description>
  <build>
    <plugins>
      <plugin>
        <artifactId>maven-compiler-plugin</artifactId>
        <configuration>

Niepoprawny język.

Musisz wybrać język w następujący sposób: <source lang="html4strict">...</source>

Języki obsługiwane w podświetlaniu składni:

abap, actionscript, actionscript3, ada, apache, applescript, apt_sources, asm, asp, autoit, avisynth, bash, basic4gl, bf, bibtex, blitzbasic, bnf, boo, c, c_mac, caddcl, cadlisp, cfdg, cfm, cil, cmake, cobol, cpp, cpp-qt, csharp, css, d, dcs, delphi, diff, div, dos, dot, eiffel, email, erlang, fo, fortran, freebasic, genero, gettext, glsl, gml, gnuplot, groovy, haskell, hq9plus, html4strict, idl, ini, inno, intercal, io, java, java5, javascript, kixtart, klonec, klonecpp, latex, lisp, locobasic, lolcode, lotusformulas, lotusscript, lscript, lsl2, lua, m68k, make, matlab, mirc, modula3, mpasm, mxml, mysql, nsis, oberon2, objc, ocaml, ocaml-brief, oobas, oracle11, oracle8, pascal, per, perl, php, php-brief, pic16, pixelbender, plsql, povray, powershell, progress, prolog, properties, providex, python, qbasic, rails, rebol, reg, robots, ruby, sas, scala, scheme, scilab, sdlbasic, smalltalk, smarty, sql, tcl, teraterm, text, thinbasic, tsql, typoscript, vb, vbnet, verilog, vhdl, vim, visualfoxpro, visualprolog, whitespace, whois, winbatch, xml, xorg_conf, xpp, z80

          <target>1.5</target>
        </configuration>
      </plugin>
    </plugins>
  </build>
</project>

Projekt będzie spinał inne projekty i jako taki nie będzie zawierał żadnych źródeł. Katalog src nie jest zatem potrzebny i może zostać skasowany.

Naturalnym byłoby skorzystanie z IDE wspierającego projekty M2, jednakże dla prostoty prezentacji materiału nie jest to prezentowane. Skorzystamy z tego udogodnienia w krokach późniejszych.

Utworzenie podprojektu dla aplikacji internetowej

W tym kroku stworzymy podprojekt dla aplikacji internetowej (ang. web application). Wykonujemy krok identyczny do poprzedniego z tym, że nazwa i typ artefaktu M2 do tworzenia projektu będą ostatecznie inne. Ważne jest, aby wykonanie polecenia odbywało się z poziomu katalogu projektu głównego, który stworzyliśmy w kroku poprzednim, czyli javaee5-m2-glassfish. W ten sposób nastąpi automatyczne uaktualnienie zależności między projektami.

mvn archetype:create -DartifactId=webapp -DarchetypeArtifactId=maven-archetype-webapp

Wynik wykonania polecenia to stworzony katalog webapp będący przestrzenią roboczą dla naszej aplikacji internetowej.

Edytujemy plik pom.xml w katalogu webapp wprowadzając następujące zmiany:

  • Zmieniamy wartość znacznika name na Demo :: ${artifactId}

Jest to jedynie zmiana kosmetyczna, ale jak zobaczymy później znacznie poprawiająca wizualne postrzeganie naszego projektu.

Ostatecznie plik pom.xml z katalogu webapp powinien mieć następującą postać.

<?xml version="1.0"?>
<project>
  <parent>
    <artifactId>javaee5-m2-glassfish</artifactId>
    <groupId>pl.jaceklaskowski.javaee5</groupId>
    <version>1.0-SNAPSHOT</version>
  </parent>
  <modelVersion>4.0.0</modelVersion>
  <groupId>pl.jaceklaskowski.javaee5</groupId>
  <artifactId>webapp</artifactId>
  <packaging>war</packaging>
  <name>Demo :: ${artifactId}</name>
  <version>1.0-SNAPSHOT</version>
  <build>
    <finalName>webapp</finalName>
  </build>
  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>3.8.1</version>
      <scope>test</scope>
    </dependency>
  </dependencies>
</project>

Utworzenie podprojektu dla komponentów EJB 3.0

Podobnie jak dla projektu głównego i podprojektu aplikacji internetowej, tworzymy przestrzeń projektową dla komponentów EJB 3.0. Sensowność takiego postępowania uwidacznia się podczas podziału obowiązków tworzenia całej aplikacji, gdzie poszczególne podprojekty stają się przestrzeniami roboczymi dla oddzielnych zespołów programistów.

mvn archetype:create -DartifactId=ejb3

Wykonanie polecenia to stworzenie katalogu ejb3 wraz ze strukturą katalogową projektu zarządzanego przez M2.

Edytujemy plik pom.xml w katalogu ejb3 wprowadzając następujące zmiany:

  • Dodajemy znacznik packaging z wartością ejb (domyślna wartość to jar)

Ustawienie wartości znacznika packaging na ejb to jedynie obejście dla działania wtyczek M2, które nie wspierają jeszcze komponentów EJB 3.0 jako zwykłych plików jar z annotacjami. Jedyna możliwość to ustawienie parametru packaging projektu na ejb i stworzenie pustego pliku META-INF/ejb-jar.xml, który zgodnie ze specyfikacją EJB 3.0 nie jest już konieczny (annotacje są wystarczającym środkiem wyrazu konfiguracji komponentów).

Ostatecznie plik pom.xml z katalogu ejb3 powinien mieć następującą postać.

<?xml version="1.0"?>
<project>
  <parent>
    <artifactId>javaee5-m2-glassfish</artifactId>
    <groupId>pl.jaceklaskowski.javaee5</groupId>
    <version>1.0-SNAPSHOT</version>
  </parent>
  <modelVersion>4.0.0</modelVersion>
  <groupId>pl.jaceklaskowski.javaee5</groupId>
  <artifactId>ejb3</artifactId>
  <packaging>ejb</packaging>
  <name>Demo :: ${artifactId}</name>
  <version>1.0-SNAPSHOT</version>
  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>3.8.1</version>
      <scope>test</scope>
    </dependency>
  </dependencies>
</project>

Utworzenie podprojektu dla aplikacji (EAR)

I ostatecznie krok, dzięki któremu stworzymy przestrzeń projektową dla projektu, którego wynikiem będzie EAR (ang. enterprise archive).

mvn archetype:create -DartifactId=ear

Wykonanie polecenia to stworzenie katalogu ear wraz ze strukturą katalogową projektu zarządzanego przez M2.

Edytujemy plik pom.xml w katalogu ear wprowadzając następujące zmiany:

  • Dodajemy znacznik packaging z wartością ear (domyślna wartość to jar)

Zbieżność nazwy projektu - ear - z wartością znacznika packaging jest przypadkowa. Wartość packaging=ear jest obowiązkowa, jednakże nazwa projektu jest dowolna.

Poza zmianą znacznika packaging, konieczne jest dodanie modułów zależnych, które utworzą wynikową plik EAR z podmodułami. Bez zagłębiania się w tajniki konfiguracji M2, wystarczy umieścić odpowiednie dependencies oraz uaktywnić wtyczkę maven-ear-plugin i skonfigurować ją. Wszystkie operacje wykonujemy w pliku - sercu projektu zarządzanego przez M2 - pom.xml.

Wynikiem naszych zmian jest plik pom.xml z katalogu ear, który przedstawia się następująco:

<?xml version="1.0" encoding="UTF-8"?>
<project>
  <parent>
    <artifactId>javaee5-m2-glassfish</artifactId>
    <groupId>pl.jaceklaskowski.javaee5</groupId>
    <version>1.0-SNAPSHOT</version>
  </parent>
  <modelVersion>4.0.0</modelVersion>
  <groupId>pl.jaceklaskowski.javaee5</groupId>
  <artifactId>ear</artifactId>
  <packaging>ear</packaging>
  <name>Demo :: ${artifactId}</name>
  <version>1.0-SNAPSHOT</version>
  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>3.8.1</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>pl.jaceklaskowski.javaee5</groupId>
      <artifactId>ejb3</artifactId>
      <version>1.0-SNAPSHOT</version>
      <type>ejb</type>
    </dependency>
    <dependency>
      <groupId>pl.jaceklaskowski.javaee5</groupId>
      <artifactId>webapp</artifactId>
      <version>1.0-SNAPSHOT</version>
      <type>war</type>
    </dependency>
  </dependencies>
  <build>
    <plugins>
      <plugin>
        <artifactId>maven-ear-plugin</artifactId>
        <configuration>
          <modules>
            <ejbModule>
              <groupId>pl.jaceklaskowski.javaee5</groupId>
              <artifactId>ejb3</artifactId>
            </ejbModule>
            <webModule>
              <groupId>pl.jaceklaskowski.javaee5</groupId>
              <artifactId>webapp</artifactId>
              <contextRoot>demo</contextRoot>
            </webModule>
          </modules>
        </configuration>
      </plugin>
    </plugins>
  </build>
</project>

(opcjonalnie) Otwarcie projektu w NetBeans IDE

Dla uproszczenia pracy z M2 i zaniechania wykonywania poleceń z linii poleceń możemy skorzystać ze wsparcia NetBeans IDE i wtyczki wspomagającej pracę z projektami nadzorowanymi przez M2 - Mevenide2-NetBeans.

Instalacja wtyczki Mevenide2-NetBeans to pobranie modułów ze strony domowej projektu i wykonanie kroków instalacyjnych, których opis znaleźć można na w/w stronie. Ostatecznie wynikiem instalacji będzie NetBeans IDE ze wsparciem dla projektów kontrolowanych przez M2 tak, że wydawanie poleceń projektowych z poziomu NetBeans IDE będzie równoznaczne z wydawaniem poleceń M2. Dzięki wtyczce nie jest konieczne tworzenie plików specyficznych dla środowiska IDE oraz możliwa jest praca z projektami M2 jakby były projektami natywnymi dla NetBeans IDE. W przypadku Eclipse konieczne jest generowanie plików projektowych specyficznych dla nich (wykonanie polecenia mvn eclipse:eclipse jest jednym z rozwiązań).

Otwórzmy stworzone projekty w NetBeans IDE wybierając menu File->Open Project... (bądź akternatywnie wciśnięcie kombinacji klawiszy Ctrl-Shift-O).

Grafika:javaee5-netbeans-openproject.PNG

Następnie przechodzimy do katalogu, w którym stworzyliśmy katalog projektu javaee5-m2-glassfish, zaznaczamy go i wybieramy przycisk Open Project Folder, co w efekcie otworzy nam projekt i jego podprojekty.

Grafika:javaee5-netbeans-projekty.PNG

Mając otwarte projekty możemy wykonywać na nich polecenia, które wcześniej zmuszeni byliśmy wykonywać z poziomu linii poleceń. Modyfikacje są odzwierciedlone natychmiast w pliku pom.xml modyfikowanego projektu.

Utworzenie bezstanowego komponentu sesyjnego EJB 3.0

Po fazie przygotowywania przestrzeni projektowej i mając otwarte projekty w wybranym IDE przystępujemy do utworzenia elementów samej aplikacji. W tym kroku stworzymy bezstanowy komponent sesyjny, którego zadaniem będzie wyświetlenie komunikatu na standardowe wyjście. Zadanie nie jest wyrafinowane, ale ma posłuzyć do demonstacji prostoty tworzenia EJB w najnowszej wersji specyfikacji - 3.0. Główna zaleta nowej specyfikacji EJB to przede wszystkim usunięcie wymagania dziedziczenia specyficznych klas należących do specyfikacji oraz tworzenie deskryptora ejb-jar.xml. Do dyspozycji, twórcy EJB 3.0, dali nam annotacje, za pomocą których wyrazimy zależności ze światem zewnętrznym.

Najpierw rozpocznijmy od stworzenia interfejsu biznesowego komponentu EJB - WyswietlKomunikat.

package pl.jaceklaskowski.javaee5;

public interface WyswietlKomunikat {
    void display(String message);
}

, który będzie realizowany przez bezstanowy komponent sesyjny EJB 3.0.

package pl.jaceklaskowski.javaee5;

import java.util.logging.Logger;
import javax.ejb.Stateless;

@Stateless
public class WyswietlKomunikatBean implements WyswietlKomunikat {

    Logger logger = Logger.getLogger(getClass().toString());

    public void display(String message) {
        logger.info(message);
    }

}

Oba pliki zapisujemy w katalogu ejb3/src/main/java/pl/jaceklaskowski/javaee5.

Kolejne modyfikacje będą dotyczyły pliku pom.xml w katalogu ejb3, w którym zadeklarujemy zależność projektu od biblioteki, która dostarcza implementacji dla annotacji Stateless. Efektem końcowym modyfikacji będzie następujący plik pom.xml.

<?xml version="1.0"?>
<project>
  <parent>
    <artifactId>javaee5-m2-glassfish</artifactId>
    <groupId>pl.jaceklaskowski.javaee5</groupId>
    <version>1.0-SNAPSHOT</version>
  </parent>
  <modelVersion>4.0.0</modelVersion>
  <groupId>pl.jaceklaskowski.javaee5</groupId>
  <artifactId>ejb3</artifactId>
  <packaging>ejb</packaging>
  <name>Demo :: ${artifactId}</name>
  <version>1.0-SNAPSHOT</version>
  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-ejb-plugin</artifactId>
        <configuration>
          <ejbVersion>3.0</ejbVersion>
        </configuration>
      </plugin>
    </plugins>
  </build>
  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>3.8.1</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>org.apache.geronimo.specs</groupId>
      <artifactId>geronimo-ejb_3.0_spec</artifactId>
      <version>1.0</version>
      <scope>provided</scope>
    </dependency>
  </dependencies>
</project>

Utworzenie aplikacji internetowej

Nasza aplikacja będzie składała się z jednej strony - index.jsp - zbudowanej przy pomocy JavaServer Faces (JSF), której zarządzany komponent wywoła komponent bezstanowy EJB3. Strona została stworzona podczas konfiguracji projektu przy pomocy m2, jednakże jej zawartość powinna zostać zmieniona na następującą. Plik index.jsp znajduje się w katalogu webapp/src/main/webapp.

<%@page contentType="text/html"%>
<%@page pageEncoding="UTF-8"%>

<%@taglib prefix="f" uri="http://java.sun.com/jsf/core"%>
<%@taglib prefix="h" uri="http://java.sun.com/jsf/html"%>

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">

<html>
<body>
<f:view>
  <h:form>
    <center><h:commandButton value="Wywołaj komponent bezstanowy EJB"
      action="#{actionManager.executeSessionBean}" /></center>
  </h:form>
</f:view>
</body>
</html>

Strona składa się z pojedyńczego przycisku h:commandButton, który związany jest z komponentem zarządzanym actionManager, a dokładnie z jego metodą executeSessionBean. Stwórzmy teraz ów komponent zarządzany - actionManager. Jest on reprezentowany przez klasę pl.jaceklaskowski.javaee5.ActionManager.

package pl.jaceklaskowski.javaee5;

import javax.ejb.EJB;

public class ActionManager {

    @EJB
    WyswietlKomunikat bean;

    public String executeSessionBean() {
        bean.display("Pozdrowienia od komponentu EJB!");
        return "success";
    }
}

Plik klasy komponentu zarządzanego - ActionManager.java - zapisujemy w katalogu webapp/src/main/java/pl/jaceklaskowski/javaee5.

Na uwagę zasługuje zdefiniowanie zależności między klasą ActionManager (której definicję jako komponent zarządzany JSF zobaczymy za moment) a komponentem EJB - WyswietlKomunikat. Korzystamy z interfejsu komponentu EJB, który określa interfejs publiczny (kontrakt) między nim a światem zewnętrznym. W ten niezwykle trywialny sposób połączyliśmy dwa światy - świat JavaServer Faces i Enterprise JavaBeans. Nic więcej nie jest konieczne, aby nastąpiła komunikacja między JSF a EJB, czyli między warstwą kliencką aplikacji a jej warstwą biznesową.

Kolejny krok w tworzeniu warstwy prezentacji naszej aplikacji to konfiguracja JSF za pomocą pliku faces-config.xml. Plik ten jest sercem aplikacji JSF, w której znajdziemy między innymi definicje komponentów zarządzanych. W nim znajdziemy również definicje przepływów między stronami aplikacji (ang. navigation-rule).

<?xml version="1.0" encoding="UTF-8"?>
<faces-config 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_1_2.xsd"
  version="1.2">
  <managed-bean>
    <managed-bean-name>actionManager</managed-bean-name>
    <managed-bean-class>pl.jaceklaskowski.javaee5.ActionManager</managed-bean-class>
    <managed-bean-scope>request</managed-bean-scope>
  </managed-bean>
</faces-config>

Plik faces-config.xml zapisujemy w katalogu webapp/src/main/webapp/WEB-INF.

Przejdźmy do stworzenia pliku konfiguracyjnego aplikacji internetowej, czyli web.xml. Deskryptor aplikacji internetowej definiuje servlet odpowiedzialny za obsługę zleceń JSF - javax.faces.webapp.FacesServlet i mapuje go do adresów zawierających rozszerzenie .jsf.

<?xml version="1.0" encoding="UTF-8"?>
<web-app 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-app_2_5.xsd"
  version="2.5">
  <servlet>
    <servlet-name>Faces Servlet</servlet-name>
    <servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
    <load-on-startup>1</load-on-startup>
  </servlet>
  <servlet-mapping>
    <servlet-name>Faces Servlet</servlet-name>
    <url-pattern>*.jsf</url-pattern>
  </servlet-mapping>
  <welcome-file-list>
    <welcome-file>index.jsf</welcome-file>
  </welcome-file-list>
</web-app>

Plik deskryptora aplikacji internetowej - web.xml zapisujemy w katalogu webapp/src/main/webapp/WEB-INF.

I na koniec zajmijmy się plikiem konfiguracyjnym samej przestrzeni projektowej zarządzanej przez M2 - pom.xml. Jak już napisano, plik pom.xml jest tym dla projektów M2, czym web.xml jest dla aplikacji internetowych lub faces-config.xml dla aplikacji JSF. Plik znajduje się w katalogu głównym projektu, który opisuje, co w naszym przypadku daje webapp/pom.xml.

<?xml version="1.0" encoding="UTF-8"?>
<project>
  <parent>
    <artifactId>javaee5-m2-glassfish</artifactId>
    <groupId>pl.jaceklaskowski.javaee5</groupId>
    <version>1.0-SNAPSHOT</version>
  </parent>
  <modelVersion>4.0.0</modelVersion>
  <groupId>pl.jaceklaskowski.javaee5</groupId>
  <artifactId>webapp</artifactId>
  <packaging>war</packaging>
  <name>Demo :: ${artifactId}</name>
  <version>1.0-SNAPSHOT</version>
  <build>
    <finalName>webapp</finalName>
  </build>
  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>3.8.1</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>org.apache.geronimo.specs</groupId>
      <artifactId>geronimo-ejb_3.0_spec</artifactId>
      <version>1.0</version>
      <scope>provided</scope>
    </dependency>
    <dependency>
      <groupId>pl.jaceklaskowski.javaee5</groupId>
      <artifactId>ejb3</artifactId>
      <version>1.0-SNAPSHOT</version>
    </dependency>
  </dependencies>
</project>

W pliku zadeklarowano zależności od biblioteki EJB 3.0 (której implementację zapożyczyliśmy od projektu Apache Geronimo) oraz modułu EJB, który stworzyliśmy poprzednio.

Utworzenie kompletnej aplikacji (EAR)

Rozpocznijmy konstruowanie aplikacji EAR od modyfikacji pliku konfiguracyjnego projektu M2 - pom.xml w katalogu ear.

<?xml version="1.0" encoding="UTF-8"?>
<project>
  <parent>
    <artifactId>javaee5-m2-glassfish</artifactId>
    <groupId>pl.jaceklaskowski.javaee5</groupId>
    <version>1.0-SNAPSHOT</version>
  </parent>
  <modelVersion>4.0.0</modelVersion>
  <groupId>pl.jaceklaskowski.javaee5</groupId>
  <artifactId>ear</artifactId>
  <packaging>ear</packaging>
  <name>Demo :: ${artifactId}</name>
  <version>1.0-SNAPSHOT</version>
  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>3.8.1</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>pl.jaceklaskowski.javaee5</groupId>
      <artifactId>ejb3</artifactId>
      <version>1.0-SNAPSHOT</version>
      <type>ejb</type>
    </dependency>
    <dependency>
      <groupId>pl.jaceklaskowski.javaee5</groupId>
      <artifactId>webapp</artifactId>
      <version>1.0-SNAPSHOT</version>
      <type>war</type>
    </dependency>
  </dependencies>
  <build>
    <plugins>
      <plugin>
        <artifactId>maven-ear-plugin</artifactId>
        <configuration>
          <modules>
            <ejbModule>
              <groupId>pl.jaceklaskowski.javaee5</groupId>
              <artifactId>ejb3</artifactId>
            </ejbModule>
            <webModule>
              <groupId>pl.jaceklaskowski.javaee5</groupId>
              <artifactId>webapp</artifactId>
              <contextRoot>demo</contextRoot>
            </webModule>
          </modules>
        </configuration>
      </plugin>
      <plugin>
        <artifactId>maven-antrun-plugin</artifactId>
        <executions>
          <execution>
            <id>asadmin</id>
            <phase>install</phase>
            <configuration>
              <tasks>
                <property name="asadmin.cmd" value="c:/apps/glassfish/bin/asadmin.bat" />
                <property name="admin.user" value="admin" />
                <property name="admin.password.file" value="${basedir}/src/main/glassfish/passwd" />
                <property name="aplikacja" value="${basedir}/target/ear-1.0-SNAPSHOT.ear" />

                <echo message="Instalacja ${pom.name} [${aplikacja}] z ${basedir}." />

                <exec executable="${asadmin.cmd}" failonerror="false">
                  <arg line="deploy" />
                  <arg line="--user ${admin.user}" />
                  <arg line="--passwordfile ${admin.password.file}" />
                  <arg line="${aplikacja}" />
                </exec>
              </tasks>
            </configuration>
            <goals>
              <goal>run</goal>
            </goals>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>
</project>

Plik zawiera definicję zależności projektu od plików wynikowych poprzednich projektów - ejb3 oraz webapp. Oba projekty składają się na kompletną aplikację przemysłową (ang. enterprise archive - EAR). W sekcji build konfigurowane są dwie wtyczki M2 - maven-ear-plugin oraz maven-antrun-plugin. Pierwsza wtyczka - maven-ear-plugin - posłuży nam do skonstruowania pliku ear podczas, gdy druga maven-antrun-plugin wywoła skrypt asadmin.bat do zainstalowania pliku EAR na serwerze Glassfish. Tutaj bardzo istotne jest sprawdzenie, że ścieżka katalogu domowego serwera Glassfish jest rzeczywiście c:/apps/glassfish i zmiana jej, jeśli konieczne. Istnieją sposoby konfiguracji M2 w bardziej elegancki sposób (za pomocą profili), jednakże temat wykroczyłby poza swoje ramy i merytoryczne i objętościowe. Pozostańmy na bezpośrednim podaniu ścieżki do skryptu asadmin.bat. Oczywiście do poprawnego działania skryptu będzie wymagane uruchomienie serwera.

Dodatkowym plikiem projektu ear będzie plik z hasłem dla serwera Glassfish, który znajduje się w katalogu ear/src/main/glassfish/passwd i zawiera następującą treść:

AS_ADMIN_PASSWORD=adminadmin

Plik passwd jest wymagany do uruchamiania poleceń administracyjnych Glassfish bez konieczności podawania hasła na linii poleceń.

Instalacja i uruchomienie aplikacji

Przyszła pora na zweryfikowanie naszych dotychczasowych poczynań w zakresie tworzenia aplikacji Java EE 5 - uruchamiamy ją na serwerze aplikacyjnym Glassfish. Wykonanie skryptów instalacyjnych serwera wymaga jego wcześniejszego uruchomienia, więc uruchamiamy serwer poleceniem:

asadmin start-domain domain1

w katalogu domowym Glassfish, np. c:/apps/glassfish.

Pomyślne uruchomienie serwera może wyglądać w ten sposób.

C:\apps\glassfish>asadmin start-domain domain1
Starting Domain domain1, please wait.
Log redirected to C:\apps\glassfish\domains\domain1\logs\server.log.
Domain domain1 is ready to receive client requests. Additional services are being started in background.
Domain [domain1] is running [Sun Java System Application Server Platform Edition 9.1 (build b25)] with its configuration
 and logs at: [C:\apps\glassfish\domains].
Admin Console is available at [http://localhost:4848].
Use the same port [4848] for "asadmin" commands.
User web applications are available at these URLs:
[http://localhost:8080 https://localhost:8181 ].
Following web-contexts are available:
[/web1 /asadmin ].
Standard JMX Clients (like JConsole) can connect to JMXServiceURL:
[service:jmx:rmi:///jndi/rmi://dev:8686/jmxrmi] for domain management purposes.
Domain listens on at least following ports for connections:
[8080 8181 4848 3700 3820 3920 8686 ].

Po tym wydajemy polecenie instalujące aplikację na serwerze. Polecenie powinno być wykonywane w katalogu głównym naszej aplikacji, tj. javaee5-m2-glassfish.

mvn install

Instalację aplikacji zlecamy wtyczce maven-antrun-plugin, którą podpieliśmy do etapu (pojecie ściśle związane z m2) o nazwie install. Podpięcie wtyczki nastąpiło w pliku pom.xml (w katalogu ear), gdzie wywołanie etapu install spowoduje wywołanie wtyczki maven-antrun-plugin i tym samym skryptu instalującego aplikację na serwerze Glassfish (a ten właśnie uruchomiliśmy).

Pomyślne zakończenie instalacji wygląda następująco.

...
[INFO] [antrun:run {execution: asadmin}]
[INFO]Executing tasks
     [echo] Instalacja Demo :: ear [C:\javaee5-m2-glassfish\ear/target/ear-1.0-SNAPSHOT.ear] z C:\javaee5-m2-glassfish\ear.
     [exec] Command deploy executed successfully.
[INFO]Executed tasks
[INFO]
[INFO]
[INFO]------------------------------------------------------------------------
[INFO]Reactor Summary:
[INFO]------------------------------------------------------------------------
[INFO]Demo .................................................. SUCCESS [0.906s]
[INFO]Demo :: ejb3 .......................................... SUCCESS [1.313s]
[INFO]Demo :: webapp ........................................ SUCCESS [0.953s]
[INFO]Demo :: ear ........................................... SUCCESS [3.844s]
[INFO]------------------------------------------------------------------------
[INFO]------------------------------------------------------------------------
[INFO]BUILD SUCCESSFUL
[INFO]------------------------------------------------------------------------

Wchodząc na adres aplikacji - http://localhost:8080/demo/index.jsf - uruchamiamy stronę startową z przyciskiem za pomocą, którego wywołamy komponent bezstanowy EJB.

Grafika:javaee5-netbeans-index-jsf.PNG

Wciśnięcie przycisku Wywołaj komponent bezstanowy EJB jest niczym innym, jak właśnie wywołaniem komponentu bezstanowego EJB, którego wynik uruchomienia będzie widoczny w dzienniku zdarzeń serwera Glassfish ($GLASSFISH_HOME/domains/domain1/logs/server.log).

[#|2007-11-12T11:07:01.593+0100|INFO|sun-appserver9.1|class pl.jaceklaskowski.javaee5.WyswietlKomunikatBean|_ThreadID=21;_ThreadName=httpSSLWorkerThread-8080-0;|
Pozdrowienia od komponentu EJB!|#]

Podsumowanie

Prześledźmy przepływ komunikatów pomiędzy przeglądarką a naszą aplikacją.

Otworzenie strony startowej aplikacji - index.jsf - to wykonanie strony index.jsp zbudowanej z komponentów JSF, dokładniej z pojedyńczego przycisku. Wciśnięcie przycisku to wyzwolenie metody executeSessionBean na komponencie zarządzanym actionManager reprezentowanym przez klasę org.jaceklaskowski.javaee5.ActionManager. Podczas tworzenia komponentu zarządzanego przez serwer aplikacyjny Glassfish następuje automatyczne wstrzyknięcie (ang. injection) zależności wskazanej przez annotację @EJB. Jej argumentem jest nazwa interfejsu biznesowego EJB - WyswietlKomunikat. Specyfikacja Java EE 5 zapewnia, że jeśli klasa deklarująca zależność poprzez annotację zostanie pomyślnie stworzona wartość pola instancji klasy deklarującej będzie poprawnie zainicjowana referencją do właściwego zasobu (w tym przypadku komponentu sesyjnego EJB). Dzięki temu wywołanie metody executeSessionBean na komponencie zarządzanym JSF to (zgodnie z implementacją) wywołanie metody display na komponencie sesyjnym. Metoda display nie należy do najbardziej wyrafinowanych i sprowadza się do wyświetlenia komunikatu na konsolę serwera aplikacyjnego. Zakończenie wywołania metody komponentu sesyjnego to powrót do strony index.jsf (dzięki navigation-rule=success, która nie jest zdefiniowana w pliku faces-config.xml, a to zgodnie ze specyfikacją JSF powoduje powrót do strony skąd nadeszło zlecenie).

Do stworzenia aplikacji wykorzystaliśmy kilka otwartych (wolnodostępnych) projektów:

  • Apache Maven 2
  • NetBeans IDE 5.5
  • Glassfish V2 b25

Na pierwszy plan postawione zostało wykorzystanie Apache Maven 2 do zbudowania aplikacji Java EE 5 i jej uruchomieniu na serwerze Glassfish. Dzięki M2 mogliśmy deklarować zależności projektu, które były automatycznie dołączane na żądanie, podczas jej budowania. Dodatkowo wykorzystanie M2 to wprowadzenie do projektu elementów, których zalety nie zostały zaprezentowane w tym artykule - możliwość wykonywania wielu zadań projektowych automatycznie, np. uruchamianie testów jednostkowych, czy weryfikacja jakości kodu źródłowego. Dodatkowo dzięki podziałowi projektu na osobne moduły (wymuszone poniekąd przez M2) umożliwia budowanie aplikacji przez kilka zespołów programistycznych. Wdrożenie narzędzi pomocniczych (np. CruiseControl, Continuum) niewielkim kosztem podnosi ostateczną jakość projektu. Miłej zabawy!

Osobiste