Maven

Koziołek

     1 Wstęp
     2 Dlaczego automatyczne budowanie jest ważne?
     3 Budowanie automatyczne w Bashu
     4 A może by tak Maven?
          4.1 Co to jest Maven ?
          4.2 Dlaczego Maven jest dobry...
          4.3 ...a dlaczego zły?
     5 Instalacja i konfiguracja Mavena
     6 Zanim powstanie pierwszy projekt
          6.4 Artefakt, grupa, wersja
          6.5 Wtyczka, archetyp
          6.6 Nadpisywanie i sumowanie konfiguracji
     7 Pierwszy Projekt
     8 Konfiguracja i dostosowanie projektu do potrzeb
          8.7 pom.xml – serce Mavena
          8.8 Dygresja o bezpieczeństwie
          8.9 Deploy na serwerze
     9 Zobacz też

Wstęp

Każdy projekt informatyczny, który jest realizowany w języku kompilowanym, dochodzi do momentu, w którym musi zostać wykonana kompilacja. Proces ten, nazywany potocznie budowaniem, jest zazwyczaj dość skomplikowany i obejmuje kilka kroków. Pełen build zazwyczaj stanowią, poza plikami wykonywalnymi: dokumentacja, raporty z testów i odpowiednie zmiany w systemach kontroli wersji kodu. Do tego zazwyczaj należy rozesłać mailing informujący klientów o nowej wersji i wprowadzić odpowiednie informacje na stronę internetową.

Jako że programiści i release managerowie są z natury leniwi (co w przypadku programistów jest cnotą), dość szybko powstało wiele narzędzi, które wspomagają proces budowania aplikacji. Jednym z nich jest Apache Maven. W tym artykule postaramy się przybliżyć, czym jest Maven, co można z nim osiągnąć, ale też – jakie ma wady.

Dlaczego automatyczne budowanie jest ważne?

Jak już wspomniano, proces budowania wersji jest dość skomplikowany i składa się z wielu elementów. Tak samo proces kompilacji kodu na poziomie pojedynczego programisty może być skomplikowany i wymagać wielu kroków. Automatyzacja tych procesów pozwala na wyeliminowanie błędów związanych z pominięciem niektórych etapów, zamianą ich kolejności czy też nieprawidłowym przeprowadzeniem.

Język Java charakteryzuje się dużym naciskiem na elementy takie jak testowanie czy przestrzeganie dobrych praktyk programistycznych. Oznacza to – poza wzrostem jakości produktu końcowego – rozbudowane mechanizmy zarządzania cyklem życia kodu. Automatyzacja tego zarządzania pozwala na wyeliminowanie błędów.

Po tym krótkim wstępie przyjrzyjmy się, jak powinien wyglądać proces budowania kodu. Stworzenie wersji binarnej naszego kodu powinno składać się z kilku kroków:

  • usunięcie starej wersji plików binarnych
  • sprawdzenie, czy wszystkie zależności znajdują się w classpath
  • dodanie brakujących zależności
  • kompilacja kodu
  • przeprowadzenie testów jednostkowych
  • generowanie dokumentacji +
  • przygotowanie archiwum JAR z plikami class i dokumentacją +
  • przeprowadzenie testów integracyjnych +
  • umieszczenie kodu w repozytorium jako wersja release +

Punkty oznaczone + są to kroki przeprowadzane zazwyczaj w przypadku tworzenia kolejnej wersji. W codziennej praktyce programista może je pominąć. Jednakże pozostałe kroki są obowiązkowe. Wszystkie współczesne metodyki programowania kładą duży nacisk na testowanie, a co za tym idzie – na pisanie kodu testowego.

Wyobraźmy sobie sytuację, w której pewien pojedynczy moduł przygotowany przez mały zespół programistów musi zostać oddany. Składać się on będzie zasadniczo z trzech oddzielnych elementów. Pierwszy z nich to właściwy kod biznesowy, drugi to kod testujący kod biznesowy, a trzeci to zależności. Najprostsze narzędzie do generowania zrzutu bazy danych w postaci pliku XML jest już na tyle skomplikowane, że wymaga testów, a dodatkowo posiada też zależności zarówno do obsługi XML, jak i bazy danych (może to być jakieś oprogramowanie ORM). Jeżeli teraz programista musiałby ręcznie przygotować polecenie kompilujące, zapewnić dostęp do wszystkich potrzebnych bibliotek i następnie uruchomić testy, to:

  • straciłby bardzo dużo czasu
  • nie zawsze znalazłby wszystkie potrzebne zależności w odpowiednich (lub najnowszych) wersjach
  • mógłby popełnić błędy w trakcie "klepania" poleceń
  • mógłby pominąć któryś z testów

Automatyzacja takiego procesu pozwala na wyeliminowanie potencjalnych źródeł błędów. Nawet najprostszy skrypt w powłoce pozwoli na:

  • przyspieszenie pracy
  • zmniejszenie ilości błędów

Budowanie automatyczne w Bashu

No właśnie: najprostszy skrypt, który wykona za nas brudną robotę związaną z "wyklepywaniem" długich poleceń kompilatora javac. Miejscem szczególnie wrażliwym na błędy jest lista zależności, a zatem należałoby ją generować automatycznie – z założeniem, że istnieje jakaś konwencja dotycząca nazewnictwa. Na całe szczęście konwencja taka istnieje. Wszystkie biblioteki mają rozszerzenie ".jar", dzięki czemu możemy wyszukać je i dołączyć do naszej zmiennej classpath; musimy założyć jeszcze, że wszystkie te biblioteki znajdują się w katalogu ./lib. Spróbujmy więc przygotować skrypt, który wygeneruje nam odpowiednie polecenie:

export node=`ls`
export fileList=""
export classpath=".;"
export currentPath="./"

function wypisz
{
  for i in $*
  do
     if [ -d $i ]
     then
        cd $i
        export node=`ls`
    export currentPath=$currentPath$i/
        wypisz $node
    cd ..
     else
    export tst=`ls $i | grep \.java`
    if [ $tst ]
    then
           export fileList="$currentPath$tst $fileList"
    fi
     fi
  done 
}

function stworzClasspath
{
  export jars=`ls ./lib | grep \.jar`
  for i in $jars
  do
    export classpath="$classpath$i;"
  done
}

wypisz $node
stworzClasspath

echo $fileList
echo $classpath

javac -cp $classpath $fileList

Ten kod posiada pewne wady. Pierwszą z nich jest bezwzględna konieczność posiadania w katalogu ./lib wszystkich bibliotek, zależności do tych bibliotek i zależności do zależności. Wiąże się to z długimi poszukiwaniami w internecie odpowiednich plików w odpowiednich wersjach. Kolejnym minusem jest konieczność trzymania pełniej listy zależności w każdym naszym projekcie. Oczywiście można trochę zmodyfikować nasz skrypt i pobierać zależności z jednego głównego repozytorium. To też jednak ma swoje wady. Po pierwsze, jeżeli w repozytorium znajdą się dwie kopie danej biblioteki w różnych wersjach, to może dość do przemieszania się klas pomiędzy nimi i konfliktów. Po drugie, zmienna classpath będzie bardzo rozbudowana, a co za tym idzie, wydłuża się czas kompilacji (wszystkie pliki JAR trzeba załadować).

Dodatkowo, nasz skrypt nie tworzy dokumentacji i jest całkowicie niekonfigurowalny z zewnątrz. Można oczywiście dopisać odpowiednie fragmenty, ale tym samym wzrośnie jego komplikacja. Zmiana konfiguracji będzie wymagała każdorazowego pisania dużej ilości kodu.

A może by tak Maven?

Widząc, jakie problemy można spotkać w trakcie kompilacji kodu w Javie, już w roku 2000 Fundacja Apache opublikowała narzędzie Ant. Było to pierwsze kompleksowe narzędzie, które pozwalało na definiowanie wszystkich aspektów związanych z budowaniem projektu w ramach jednego pliku XML. Jednak Ant miał swoje wady; należały do nich: brak kontroli zależności i zależności pośrednich, czy też "ograniczenie umysłowe", czyli brak zaawansowanych funkcjonalności (np. automatyzacji wdrożenia ziaren EJB na serwerze). Oczywiście bardzo szybko powstały odpowiednie dodatki, ale niesmak pozostał.

W 2001 roku, w ramach projektów Alexandira, Gump i Forrest, zrodziła się idea stworzenia jednego rozbudowanego narzędzia do zarządzania wszystkimi aspektami cyklu życia i budowania projektu. Tak oto w 2005 roku ujrzał światło dzienne Maven...

Co to jest Maven ?

Maven jest to narzędzie służące do zarządzania cyklem życia i budowania projektu – począwszy od jego stworzenia poprzez kompilację, testy jednostkowe, integracyjne, tworzenie dokumentacji i wdrażanie gotowego programu w miejscu docelowym (np ziarna EJB na serwerze). Obecnie Maven występuje w dwóch niekompatybilnych ze sobą wersjach. Maven 1.x jest niezalecany do użycia, Maven 2 jest obecnie jednym z najpopularniejszych narzędzi do zarządzania projektami. W tym artykule zostanie omówiony Maven 2 w wersji 2.0.8.

Dlaczego Maven jest dobry...

Apache Maven jest obecnie jednym z dwóch standardów budowania w Javie. Pierwszym jest Ant, drugim właśnie Maven. Jakie cechy zadecydowały o tym, że wiele osób używa tego narzędzia? Odpowiedź może być udzielona na kilka sposobów. Jednym z najlepszych jest wyliczenie, z jakimi problemami związanymi z procesem budowania aplikacji Maven sobie radzi:

  • zarządzanie zależnościami i zależnościami pośrednimi
  • prosta konfiguracja za pomocą pliku pom.xml
  • łatwe wybieranie zadań z linii poleceń
  • łatwe wersjonowanie i tagowanie kodu
  • duża ilość wtyczek ("pluginów") zarówno do prostych, jak i skomplikowanych zadań
  • integracja z popularnymi IDE (Eclipse, Netbeans, IntelliJ Idea)
  • możliwość zarządzania wieloma modułami projektu naraz
  • pełne wsparcie dla testów jednostkowych, integracyjnych i obciążeniowo-wydajnościowych

Jak widać, lista wyczerpuje wszystkie przypadki, o których mowa była w pierwszej części tego artykułu. Warto zaznaczyć, że Maven pracuje zgodnie ze wzorcem Convention Over Configuration (CoC), dzięki czemu konfiguracji wymagają tylko te elementy, które będą niestandardowe, lub te, które użytkownik chce przystosować do swoich indywidualnych potrzeb. Sama zmiana konfiguracji odbywa się w ramach jednego pliku pom.xml.

...a dlaczego zły?

Jednak nie wszytko złoto, co się świeci. Maven posiada też kilka dość drażniących wad, które mogą spowodować, że proces budowania aplikacji doprowadzi programistę do szału. Do najpoważniejszych wad Mavena należą:

  • niekompletna lub niezgodna z rzeczywistością dokumentacja
  • brak konsekwencji w działaniach wymagających nadpisania i sumowania konfiguracji
  • "magiczność" i nieintuicyjne zachowanie niektórych funkcjonalności
  • kłopotliwe uruchamianie przy braku połączenia z internetem

Szczególnie dwa pierwsze punkty zazwyczaj okazują się źródłem poważnych błędów, takich jak użycie zależności w niewłaściwej wersji lub nadpisanie któregoś z fragmentów konfiguracji. Jednakże, dzięki integracji z Antem, istnieje możliwość zastąpienia części wadliwych elementów przez odpowiednie zadania Anta (ang. tasks). Przydatnym narzędziem jest też plugin Help, który posiada narzędzia do analizy poszczególnych cykli pracy Mavena. Ostatni punkt oznacza, że mogą pojawić się błędy, jeżeli w trakcie pracy stracimy połączenie z siecią, a potrzebujemy dociągnąć jakąś zależność. Na szczęście istnieje możliwość uruchomienia Mavena w trybie offline i ręcznej instalacji potrzebnych bibliotek w repozytorium.

Instalacja i konfiguracja Mavena

Skoro zakończyliśmy już krótki przegląd dotyczący wad i zalet Mavena, przejdźmy do jego instalacji. Pierwszym krokiem jest ściągnięcie najnowszej wersji ze strony projektu.

Sam proces instalacji jest bardzo prosty. Wystarczy rozpakować ściągnięte archiwum, dodać zmienną M2_PATH, wskazującą na główny katalog Mavena, i zaktualizować zmienną PATH o ścieżkę M2_PATH/bin. Jeżeli chcemy korzystać z dodatkowych opcji JVM, należy dodać jeszcze zmienną M2_OPTS. Jeżeli nie mamy skonfigurowanej ścieżki JAVA_HOME, to też należy ją dodać.

Maven wymaga zainstalowanej Javy w wersji 1.4.2 lub wyższej, 1,5MB wolnego miejsca na dysku na "silnik" oraz miejsce na repozytoriom. Po zainstalowaniu Mavena należy jeszcze dokonać konfiguracji repozytorium. Na początek należy sprawdzić, czy wszystko działa poprawnie. W tym celu należy w linii poleceń uruchomić Mavena i kazać wypisać mu swoją wersję:

$ mvn -version
Maven version: 2.0.7
Java version: 1.5.0_12
OS name: "windows xp" version: "5.1" arch: "x86"

W tym momencie w katalogu domowym użytkownika zostanie utworzony katalog .m2, a w nim plik settings.xml. Taki sam plik znajduje się w katalogu M2_PATH/conf. W pliku w katalogu domowym należy odkomentować linię:

<localRepository>/tu/sciezka/do/repozytorium</localRepository>

I tu wychodzi po raz pierwszy zasada Convention Over Configuration oraz nadpisywania i łączenia ustawień. W celu ilustracji jej działania przeprowadźmy prosty eksperyment. W pliku settings.xml stoi wyraźnie napisane:

localRepository The path to the local repository maven will use to store artifacts.
Default: ~/.m2/repository

Jak widać, jeżeli dokonamy zmiany tylko w pliku "domowym" (tak nazywam plik w ~/.m2/repository, w przeciwieństwie do pliku "głównego" z M2_PATH/conf), to zmiana ta będzie widoczna tylko dla danego użytkownika. Co jednak stanie się, jeżeli w pliku "domowym" nie będzie tego wpisu, a znajdzie się on w pliku "głównym"?

Zanim powstanie pierwszy projekt

Zanim przystąpimy do tworzenia pierwszego projektu, musimy jeszcze omówić kilka podstawowych pojęć i odpowiedzieć na pytanie postawione na końcu poprzedniego rozdziału.

Artefakt, grupa, wersja

Pierwszymi dwoma pojęciami są artefakt i grupa. Zrozumienie tych pojęć pozwoli nam na zrozumienie, jak Maven zarządza zależnościami. Artefakt to nazwa identyfikująca dany projekt w grupie. W przykładowym projekcie będzie to "Maven". Grupa pozwala na określenie przestrzeni nazw, w jakiej znajduje się artefakt. Grupa powinna mieć nazwę utworzoną zgodnie z zasadami JavaBean dotyczącymi nazywania pakietów. W naszym przypadku będzie to "net._4programmers" (uwaga – nazwa nie może zaczynać się od cyfry). Dodając zależność do projektu, należy wyspecyfikować nazwę artefaktu, nazwę grupy oraz wersję. Wersja może być wskazaniem na konkretny numer lub zakres. W tym drugim przypadku można wskazać na przedział domknięty, otwarty lub jednostronnie nieskończony. Grupa, artefakt oraz wersja identyfikują jednoznacznie, z której biblioteki chcemy skorzystać. Istnieje możliwość takiej konfiguracji artefaktu, by był brany pod uwagę tylko w specyficznym przypadku, np. w trakcie testów.

Wtyczka, archetyp

Wtyczka ("plugin") jest specjalną klasą w języku Java, która wykonuje pewne czynności na podstawie zadanej konfiguracji. Wtyczka może wykonywać różne czynności, na przykład pakować skompilowane klasy do pliku JAR, generować dokumentację, kompilować kod za pomocą kompilatora aspektowego. Wtyczki są artefaktami Mavena i, tak jak artefakty, posiadają swoje nazwy (artifactId), należą do grupy (groupId) i posiadają wersje (version). Wtyczka może być uruchamiana zawsze lub tylko w określonym przypadku. Przypadek można definiować za pomocą profili. Istotną kwestią jest to, iż wtyczka może posiadać swoje własne zależności, które będą brane pod uwagę, gdy zostanie uruchomiony jej classloader. Każda wtyczka posiada cele (goal), które powinny być wyspecyfikowane w trakcie jej uruchamiania.

Archetyp (uwaga – nie należy mylić z artefaktem) jest specyficzną wtyczką, za pomocą którego tworzony jest projekt. Zawiera on w swojej strukturze odwzorowanie drzewa katalogów "gołego" projektu, prosty plik pom.xml zawierający podstawowe dane projektu oraz wymagane przez projekt pliki danego typu, na przykład plik web.xml.

Nadpisywanie i sumowanie konfiguracji

Poprzedni rozdział zakończyliśmy pytaniem, co stanie się, gdy zdefiniujemy repozytorium w "głównym" pliku konfiguracyjnym, a co, jak zdefiniujemy je w pliku konfiguracyjnym użytkownika. W tym przypadku Maven weźmie pod uwagę plik użytkownika i nadpisze konfigurację "główną". Nie zawsze jednak jest tak, że konfiguracja jest nadpisywana. Czasami konfiguracja jest sumowana. Dzieje się tak na przykład w momencie, gdy projekt-rodzic propaguje swoje zależności do projektów-dzieci. Ważną cechą jest to, że w trakcie tej propagacji niektóre dane są nadpisywane. Jeżeli na przykład moduł projektu używa biblioteki JUnit w wersji 3.8.2, a projekt-rodzic ma w swoich zależnościach tę bibliotekę w wersji 4.4, to wykorzystana zostanie wersja 3.8.2. W drugą stronę, jeżeli projekt-rodzic jest zależny np. od Springa w wersji 2.5, a projekt-dziecko nie posiada tej zależności, to zostanie ona dodana. Jest to też doskonały przykład zależności pośredniej.

Niezwykle przydatnym narzędziem pozwalającym na diagnostykę projektu jest wtyczka Help. Pozwala ona na określenie używanej konfiguracji (cel help:effective-settings), jak i kształtu pliku pom.xml użytego w procesie kompilacji (cel help:effective-pom).

Pierwszy Projekt

Czas na nasz pierwszy projekt. Jak już wspomniałem, będzie się on nazywał "Maven" i będzie należał do przestrzeni "net._4programmers". Dodatkowo niech będzie miał wersję 0.1, a sam projekt niech będzie prostą aplikacją webową. Żeby osiągnąć takie rezultaty, wystarczy że napiszemy:

$ mvn archetype:create -DarchetypeArtifactId=maven-archetype-webapp -DartifactId=Maven -DgroupId=net._4programmers -Dversion=0.1
[INFO] Scanning for projects...
[INFO] Searching repository for plugin with prefix: 'archetype'.
[INFO] ------------------------------------------------------------------------
[INFO] Building Maven Default Project
[INFO]    task-segment: [archetype:create] (aggregator-style)
[INFO] ------------------------------------------------------------------------
[INFO] Setting property: classpath.resource.loader.class => 'org.codehaus.plexus.velocity.ContextClassLoaderResourceLoader'.
[INFO] Setting property: velocimacro.messages.on => 'false'.
[INFO] Setting property: resource.loader => 'classpath'.
[INFO] Setting property: resource.manager.logwhenfound => 'false'.
[INFO] **************************************************************
[INFO] Starting Jakarta Velocity v1.4
[INFO] RuntimeInstance initializing.
[INFO] Default Properties File: org\apache\velocity\runtime\defaults\velocity.properties
[INFO] Default ResourceManager initializing. (class org.apache.velocity.runtime.resource.ResourceManagerImpl)
[INFO] Resource Loader Instantiated: org.codehaus.plexus.velocity.ContextClassLoaderResourceLoader
[INFO] ClasspathResourceLoader : initialization starting.
[INFO] ClasspathResourceLoader : initialization complete.
[INFO] ResourceCache : initialized. (class org.apache.velocity.runtime.resource.ResourceCacheImpl)
[INFO] Default ResourceManager initialization complete.
[INFO] Loaded System Directive: org.apache.velocity.runtime.directive.Literal
[INFO] Loaded System Directive: org.apache.velocity.runtime.directive.Macro
[INFO] Loaded System Directive: org.apache.velocity.runtime.directive.Parse
[INFO] Loaded System Directive: org.apache.velocity.runtime.directive.Include
[INFO] Loaded System Directive: org.apache.velocity.runtime.directive.Foreach
[INFO] Created: 20 parsers.
[INFO] Velocimacro : initialization starting.
[INFO] Velocimacro : adding VMs from VM library template : VM_global_library.vm
[ERROR] ResourceManager : unable to find resource 'VM_global_library.vm' in any resource loader.
[INFO] Velocimacro : error using  VM library template VM_global_library.vm : org.apache.velocity.exception.ResourceNotFoundException: Unable to find resource 'VM_global_library.vm'
[INFO] Velocimacro :  VM library template macro registration complete.
[INFO] Velocimacro : allowInline = true : VMs can be defined inline in templates

[INFO] Velocimacro : allowInlineToOverride = false : VMs defined inline may NOT replace previous VM definitions
[INFO] Velocimacro : allowInlineLocal = false : VMs defined inline will be  global in scope if allowed.
[INFO] Velocimacro : initialization complete.
[INFO] Velocity successfully started.
[INFO] [archetype:create]
[INFO] Defaulting package to group ID: net._4programmers
[INFO] artifact org.apache.maven.archetypes:maven-archetype-webapp: checking for updates from central
[INFO] ----------------------------------------------------------------------------
[INFO] Using following parameters for creating Archetype: maven-archetype-webapp:RELEASE
[INFO] ----------------------------------------------------------------------------
[INFO] Parameter: groupId, Value: net._4programmers
[INFO] Parameter: packageName, Value: net._4programmers
[INFO] Parameter: basedir, Value: d:\workspace
[INFO] Parameter: package, Value: net._4programmers
[INFO] Parameter: version, Value: 0.1
[INFO] Parameter: artifactId, Value: Maven
[INFO] ********************* End of debug info from resources from generated POM ***********************
[INFO] Archetype created in dir: d:\workspace\Maven
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESSFUL
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 3 seconds
[INFO] Finished at: Mon Feb 18 01:02:33 CET 2008
[INFO] Final Memory: 5M/9M
[INFO] ------------------------------------------------------------------------

Jak widać, polecenie jest dość długie i skomplikowane, ale – w przeciwieństwie do ręcznego tworzenia projektu – nie musimy pisać za każdym razem tylu poleceń. Sprawa uprości się jeszcze, jeżeli będziemy chcieli stworzyć zwykły projekt Java SE. Można wtedy pominąć flagę -DarchetypeArtifactId, bo domyślnie zostanie użyty archetyp projektu Java SE.

Przyjrzyjmy się teraz strukturze katalogu z projektem:

\Maven
 |-pom.xml
 \src
  \main
   \resources
   \webapp
    |-index.jsp
    \WEB-INF
     |-web.xml

Dzięki realizacji wzorca CoC mamy pewność, że tak przygotowany projekt możemy od razu kompilować, i że na pewno uruchomi się on na serwerze.

Konfiguracja i dostosowanie projektu do potrzeb

Skoro mamy już stworzony projekt, to czas na przystosowanie go do naszych potrzeb. Pierwszym krokiem jest przekształcenie go tak, by można było go używać w naszym ulubionym IDE. Mamy tu kilka dróg. Maven pozwala na szybkie wygenerowanie odpowiednich plików konfiguracyjnych dla:

  • Eclipse:
    $ mvn eclipse:eclipse
  • Netbeans:
    $ mvn nbm:nbm
  • IntelliJ Idea:
    $ mvn idea:idea

Zostaje nam już tylko zaimportować nasz projekt do IDE. Przyjrzyjmy się teraz plikowi pom.xml.

pom.xml – serce Mavena

Tytuł tego podrozdziału dość dobitnie mówi, czym jest plik pom.xml, a jest on najważniejszym elementem naszego projektu. To właśnie w nim definiowane są wszystkie zależności, sposób budowania, testowania i uruchamiania. O szczegółach takich jak generowanie dokumentacji czy tworzenie wersji release w repozytorium kodu nie wspominając. Zaraz po wygenerowaniu plik ten wygląda tak:

<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>net._4programmers</groupId>
    <artifactId>Maven</artifactId>
    <packaging>war</packaging>
    <version>0.1</version>
    <name>Maven Maven Webapp</name>
    <a href="http://maven.apache.org">http://maven.apache.org</a>
    <dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>3.8.1</version>
            <scope>test</scope>
        </dependency>
    </dependencies>
    <build>
        <finalName>Maven</finalName>
    </build>
</project>

Pierwszą zmianą, jaką zrobimy, będzie zmiana wersji biblioteki JUnit na wyższą niż 4. Jako że nie mamy pewności, która wersja jest najnowsza, użyjemy tzw. wyrażenia zakresu:

<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>[4, )</version>
    <scope>test</scope>
</dependency>

Jak na razie wystarczy. Skompilujmy nasz projekt i stwórzmy archiwum WAR. Jest to bardzo proste:

$ mvn clean compile war:war
[INFO] Scanning for projects...
[INFO] Searching repository for plugin with prefix: 'war'.
[INFO] ------------------------------------------------------------------------
[INFO] Building Maven Maven Webapp
[INFO]    task-segment: [clean, compile, war:war]
[INFO] ------------------------------------------------------------------------
[INFO] [clean:clean]
[INFO] Deleting directory d:\workspace\Maven\target
[INFO] [resources:resources]
[INFO] Using default encoding to copy filtered resources.
[INFO] [compiler:compile]
[INFO] No sources to compile
[INFO] [war:war]
[INFO] Packaging webapp
[INFO] Assembling webapp[Maven] in [d:\workspace\Maven\target\Maven]
[INFO] Processing war project
[INFO] Webapp assembled in[63 msecs]
[INFO] Building war: d:\workspace\Maven\target\Maven.war
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESSFUL
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 3 seconds
[INFO] Finished at: Mon Feb 18 01:24:59 CET 2008
[INFO] Final Memory: 4M/10M
[INFO] ------------------------------------------------------------------------

Co dokładnie zrobiliśmy:

  • mvn - wywołujemy Mavena, przygotowuje on finalny plik pom.xml
  • clean – Maven usuwa katalog target, a wraz z nim starą wersję plików
  • compile – Maven wywołuje kompilator Java (uwaga – w wersji 1.4! Jak to zmienić, będzie za chwilę)
  • war:war – zostaje uruchomiony plugin war, który tworzy nam archiwum

Po tej operacji nasz katalog ma taką strukturę:

\Maven
 |-.classpath
 |-.project
 |-pom.xml
 \src
  \main
   \resources
   \webapp
    |-index.jsp
    \WEB-INF
     |-web.xml
 \target
  \classes
  \Maven
   |-index.jsp
   \META-INF
   \WEB-INF
    \classes
    |-web.xml
  |-Maven.war
  \war
   \work
    |-webapp-cache.xml

Jak widać, Maven wygenerował nam plik Maven.war oraz katalog, który mu odpowiada. Obydwa znajdują się w standardowym katalogu target.

Stworzyliśmy oraz skompilowaliśmy nasz pierwszy projekt. Jednak zanim wrzucimy go na serwer, należy jeszcze dokonać kilku zmian w pliku pom.xml. Pierwszą z nich jest włączenie kompilatora zgodnego z Java 5 SE. Maven domyślnie używa kompilatora dla tego języka w wersji 1.4, co pozbawia nas możliwości korzystania z udogodnień Java 5, takich jako Generics czy enumy. Uruchomienie tego kompilatora jest możliwe dzięki dodaniu odpowiedniej wtyczki:

<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">
<!-- ... -->
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>1.5</source>
                    <target>1.5</target>
                </configuration>
            </plugin>
        </plugins>
        <!-- ... -->
    </build>
<!-- ... -->
</project>

Dokonujemy tu ponownej konfiguracji jednej z głównych wtyczek Mavena. Mamy już wygenerowany plik WAR w wersji Java 5, czas zatem umieścić go na serwerze. W tym celu musimy posiadać działający kontener (serwer) serwletów (np. Apache Tomcat), lub serwer aplikacji (np. Glassfish). W przykładzie wykorzystam Apache Tomcat.

Dygresja o bezpieczeństwie

W tym miejscu trzeba wspomnieć o kilku prostych zasadach związanych z pracą z serwerami w Mavenie. Zapewne mało kto byłby zadowolony z faktu, że login i hasło administratora wyciekły wraz z plikiem pom.xml, który dołączany jest do każdej dystrybucji. Nierozsądne byłoby też każdorazowe zmuszanie użytkownika do usuwania tych danych z tego pliku przed opublikowaniem nowej wersji. Dlatego też wszystkie dane dotyczące wykorzystywanych serwerów, kont FTP i SMB są przechowywane w pliku settings.xml. Oczywiście polityka bezpieczeństwa powinna zakładać, że dane te są przypisywane do konkretnego użytkownika i zapisane w pliku "domowym". Tak też i zrobimy. Edycja pliku settings.xml sprowadza się do dodania:

<settings>
    <!-- ... -->
    <servers>
        <server>
            <id>tomcat55</id>
            <username>admin</username>
            <password>admin</password>
        </server>
    </servers>
    <!-- ... -->
</settings>

Deploy na serwerze

Po szybkiej zmianie w pliku settings.xml przyszedł czas na kolejną zmianę w pliku pom.xml. Powiedzmy teraz Mavenowi, że do życia niezbędna jest nam wtyczka pozwalająca na korzystanie z serwera Tomcat:

<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">
<!-- ... -->
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>1.5</source>
                    <target>1.5</target>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>tomcat-maven-plugin</artifactId>
                <configuration>
                    <a href="http://localhost:28280/manager">http://localhost:28280/manager</a>
                    <server>tomcat55</server>
                    <warFile>${project.build.directory}/${project.build.finalName}.war</warFile>
                </configuration>
            </plugin>
        </plugins>
    </build>
<!-- ... -->
</project>

W konfiguracji podaliśmy informację, gdzie szukać managera serwera określonego w tagu server. Specjalnie dodałem też domyślną wartość dla parametru warFile. Maven, zanim uruchomi wtyczki, musi przetworzyć pliki pom.xml i settings.xml. Niektóre informacje konfiguracyjne są uzależnione od wartości parametrów projektu. W trakcie przetwarzania wykorzystywany jest język szablonów Velocity.

Ostatnim krokiem jest uruchomienie całości na serwerze:

$ mvn clean compile war:war tomcat:deploy
[INFO] Scanning for projects...
[INFO] Searching repository for plugin with prefix: 'war'.
[INFO] ------------------------------------------------------------------------
[INFO] Building Maven Maven Webapp
[INFO]    task-segment: [clean, compile, war:war, tomcat:deploy]
[INFO] ------------------------------------------------------------------------
[INFO] [clean:clean]
[INFO] Deleting directory d:\workspace\Maven\target
[INFO] [resources:resources]
[INFO] Using default encoding to copy filtered resources.
[INFO] [compiler:compile]
[INFO] No sources to compile
[INFO] [war:war]
[INFO] Packaging webapp
[INFO] Assembling webapp[Maven] in [d:\workspace\Maven\target\Maven]
[INFO] Processing war project
[INFO] Webapp assembled in[62 msecs]
[INFO] Building war: d:\workspace\Maven\target\Maven.war
[INFO] Preparing tomcat:deploy
[INFO] [resources:resources]
[INFO] Using default encoding to copy filtered resources.
[INFO] [compiler:compile]
[INFO] No sources to compile
[INFO] [resources:testResources]
[INFO] Using default encoding to copy filtered resources.
[INFO] [compiler:testCompile]
[INFO] No sources to compile
[INFO] [surefire:test]
[INFO] No tests to run.
[INFO] [war:war]
[INFO] Packaging webapp
[INFO] Assembling webapp[Maven] in [d:\workspace\Maven\target\Maven]
[INFO] Processing war project
[INFO] Webapp assembled in[0 msecs]
[INFO] Building war: d:\workspace\Maven\target\Maven.war
[INFO] [tomcat:deploy]
[INFO] Deploying war to http://localhost:28280/Maven
[INFO] OK - Deployed application at context path /Maven
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESSFUL
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 9 seconds
[INFO] Finished at: Mon Feb 18 19:03:57 CET 2008
[INFO] Final Memory: 7M/14M
[INFO] ------------------------------------------------------------------------

Na tym zakończyliśmy pierwsze spotkanie z Mavenem. W kolejnych artykułach omówione zostaną:

  • testy przy użyciu Mavena
  • zarządzanie aplikacjami o wielu modułach
  • używanie profili
  • integracja z Antem

Zobacz też

Na zakończenie – kilka przydatnych linków:

2 komentarzy

Artykuł fajny jak na początek z mavenem.

PS: Koledze wkradła się mała literówka <samo>pom.xml</samp>.
Pozdrawiam

Art bardzo fajny. Szkoda tylko, że nie wspomniałeś o M2Eclipse, ponieważ ułatwia on zarządzanie zależnościami w Eclipse. Strona projektu - http://m2eclipse.codehaus.org/ Korzystam i bardzo się przydaje, fajnie jakbyś go ujął w tej części arta(Integracja z IDE) :)