Java

Maven



Wstęp


Każdy projekt informatyczny, który jest realizowany w języku kompilowanym, dochodzi do fazy w której 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, zamiana kolejności czy też nieprawidłowym przeprowadzeniem jednego z etapów.
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, rozbudowanymi mechanizmami zarządzania cyklem życia kodu. Automatyzacja tego zarządzania pozwala na wyeliminowanie błędów.
Po tym, krótkim i zamotanym 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 release +

Punkty oznaczone + są to kroki zazwyczaj przeprowadzane 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 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 kod właściwy "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 (obecnie jest to jakiś 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 (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" kilometrowych poleceń kompilatora javac. Szczególnie wrażliwym na błędy miejscem jest lista zależności, a zatem należało by 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 naszego classpath'a, ale 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

Nie jest to skomplikowany kod, ale posiada pewne wady. Pierwszą z nich jest bezwzględna konieczność posiadania w katalogu ./lib wszystkich bibliotek, zależności 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. Ma to jednak 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. Drugą jest bardzo rozbudowany classpath, a co za tym idzie wydłuża się czas kompilacji (wszystkie jary 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 komplikacja samego skryptu. Konfiguracja będzie wymagała każdorazowego klepania kilometrów kodu.

A może by tak Maven?


Widząc jakie problemy można spotkać w trakcie kompilacji kodu w javie już w 2000 roku 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 depoyu 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 deployowanie gotowego programu w miejscu docelowym (np ziarna EJB na serwerze). Obecnie Maven występuje w dwóch niekompatybilnych se sobą wersjach. Maven 1.x jest stary i 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 dobry. Jeżeli byłby zły to nikt by go nie używał. Obecnie jest jednym z dwóch standardów budowania w Javie. Pierwszym jest ant, drugim właśnie Maven.
Jakie cechy zadecydowały o tym, że tak 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 z 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 ze wszystkimi popularnymi IDE (Eclipse, Netbeans, IntelliJ Idea).
  • Możliwość zarządzania wieloma modułami projektu na raz.
  • 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 użytkownik chce je przystosować do swoich indywidualnych potrzeb. Sama zmiana konfiguracji odbywa się też w ramach jednego pliku pom.xml.

... a dlaczego zły?


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

  • Niekompletna lub nie zgodna z rzeczywistością dokumentacja.
  • Brak konsekwencji w działaniach wymagających nadpisania i sumowania konfiguracji.
  • "Magiczność" i nie intuicyjne 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 taski anta. 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 całe 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 w 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 zasady jej działania przeprowadźmy prosty eksperyment. W pliku settings.xml stoi wyraźnie napisane:
<quote>
localRepository
   | The path to the local repository maven will use to store artifacts.
   |
   | Default: ~/.m2/repository
</quote>
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 (nazwa nie może zaczynać się od cyfry!). Grupa, artefakt oraz wersja identyfikują jednoznacznie z której biblioteki chcemy skorzystać. 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. Istnieje możliwość takiej konfiguracji artefaktu by był brany pod uwagę tylko w specyficznym przypadku np. w trakcie testów.

Plugin, archetyp


Plugin jest to specjalna klasa w języku Java, która wykonuje pewne czynności na podstawie zadanej konfiguracji. Plugin może wykonywać różne czynności na przykład pakować skompilowane klasy do pliku jar, generować dokumentację, kompilować kod za pomocą kompilatora aspektowego. Pluginy są artefaktami mavena i tak jak artefakty posiadają swoje nazwy (artifactId), należą do grupy (groupId) i wersje (version). Plugin może być uruchamiany zawsze lub tylko w określonym przypadku. Przypadek można definiować za pomocą profili. Istotną kwestią jest to, iż plugin może posiadać swoje własne zależności, które będą brane pod uwagę gdy zostanie uruchomiony classloader pluginu. Każdy plugin posiada cele (goal), które powinny być wyspecyfikowane w trakcie uruchamiania pluginu.
Archetyp jest specyficznym pluginem 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 danego typu pliki na przykład 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 rodzic jest zależny od np. Springa w wersji 2.5, a 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ącymi na diagnostykę projektu jest plugin help. Pozwala on 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ą web. Ż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 na pewno uruchomi się on na serwerze.

Konfiguracja i dostosowanie do potrzeb


Skoro mamy już stworzony projekt to czas na przystosowanie go do naszych potrzeb. Pierwszym krokiem jest przekształcenie projektu 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 releasa 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>
        <url>http://maven.apache.org</url>
        <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 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ć zachwilę.
  • war:war - zostaje uruchomiony plugin war, który stworzy 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 <samo>pom.xml</samp>.
Pierwszą ze zmian jest włączenie kompilatora zgodnego z Java 5 SE. Maven domyślnie używa kompilatora dla języka w wersji 1.4 co pozbawia nas możliwości korzystania z udogodnień Java 5 taich jako Generics czy enumy. Uruchomienie tego kompilatora jest możliwe dzięki dodaniu odpowiedniego pluginu:
<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 jednego z głównych pluginów mavena. Mamy już wygenerowanego wara w wersji Java 5, czas zatem umieścić go na serwerze. W tym celu musimy posiadać działający kontener servletów np. Tomcat lub serwer aplikacji np. Glassfish. W przykładzie wykorzystam ten pierwszy.

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 pomem, który dołączany jest do każdej dystrybucji. Głupotą było by też zmuszanie użytkownika do każdorazowego usuwania tych danych z poma, 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ędny jest nam plugin pozwalający 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>
                                        <url>http://localhost:28280/manager</url>
                                        <server>tomcat55</server>
                                        <warFile>${project.build.directory}/${project.build.finalName}.war</warFile>
                                </configuration>
                        </plugin>
                </plugins>
        </build>
<!-- ... -->
</project>


W konfiguracji podaliśmy informację na temat, gdzie szukać managera serwera określonego w tagu server.  Specjalnie dodałem też domyślną wartość dla parametru warFile. Maven zanim uruchomi pluginy musi przetworzyć pliki pom.xml i settings.xml. Niektóre informacje są 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.

Linki


Na zakończenie kilka przydatnych linków:
http://maven.apache.org/ - strona projektu Apache Maven
http://mojo.codehaus.org/ - miejsce w którym znajdziemy wiele pluginów
http://repo1.maven.org/maven2/ - główne repozytorium mavena. Z tego miejsca ściągane są wszystkie najpopularniejsze biblioteki.
http://jlaskowski.blogspot.com/ - tu przeczytasz jak można uzywać mavena z Javą EE
http://www.runelord.eu/ - ciekawostki mavenowe i nie tylko

2 komentarze

SM 2014-05-26 16:46

Artykuł fajny jak na początek z mavenem.

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

tomek107 2008-07-16 14:44

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) :)