Wprowadzenie do Maven’a

post_img

Wielką zaletą języka Java jest jego elastyczność. Oznacza to między innymi, że nikt nie mówi nam gdzie mają być nasze źródła, do jakiego katalogu mają trafić binarki ani gdzie mamy mieć umieszczone pliki propertiesów czy biblioteki zewnętrzne. Poza drobnymi ograniczeniami specyficznymi dla pewnych rodzajów rozwiązań możemy zazwyczaj sami zdefiniować praktycznie całą strukturę projektu według własnych potrzeb i upodobań. Do tego mamy jeszcze dowolność w wyborze sposobu budowania aplikacji – możemy zbudować projekt z poziomu IDE, z linii poleceń lub użyć dedykowanych narzędzi do budowania.

Gdzie ja mam wrzucić te pliki?

Ta wielka zaleta potrafi jednak obrócić się przeciwko nam. Ponieważ to projektanci i programiści definiują strukturę projektu i sposób budowania aplikacji trudno jest znaleźć dwa takie same projekty. Każdy, kto pracował w kilku projektach javowych, szczególnie wytwarzanych w ramach różnych zespołów prawdopodobnie spotkał się z opisywaną sytuacją. Wydaje mi się, że taki brak „szablonu projektu” może być problematyczny szczególnie dla młodszych programistów, którzy – przechodząc z projektu do projektu – mogą czuć się zagubieni.

Wsparcie – Maven

Z pomocą przychodzi Maven, czyli narzędzie do zarządzania projektem Javowym. Osobiście uważam Mavena za jeden z najbardziej przełomowych ‚wynalazków’ świata Javowego.

Maven zrodził się z potrzeby zdefiniowania standardowej struktury projektu, standardowego sposobu budowania aplikacji oraz sposobu definiowania zależności pomiędzy modułami projektu. Dzięki tej standaryzacji przejście pomiędzy projektami opartymi o Mavena jest wręcz przezroczyste – możemy skupić się na biznesowych problemach zamiast zastanawiać się nad tym jak to wszystko zbudować i skąd wziąć oraz gdzie wrzucić kolejnego JARka.

Budowanie oprogramowania przy użyciu Mavena oparte jest o cele. Każde wołanie powinno zakończyć osiągnięciem konkretnego celu. Może być nim np. kompilacja (compile), zbudowanie paczki (package) czy przeprowadzenie testów jednostkowych (test). Dostępne cele są określane przez wtyczki rozszerzające funkcjonalność Mavena. Konkretne cele mogą wymagać wykonania wcześniej jakiegoś innego celu (np. package musi być poprzedzony compile).

Projekt mavenowy definiuje się poprzez stworzenie i utrzymywanie pliku pom.xml (POM – ang. Project Object Model). Pom.xml jest głównym miejscem pracy z projektem i zawiera wszystkie istotne elementy definiujące projekt, jego strukturę, sposób budowania i przede wszystkim zależności.

Mavena można pobrać ze strony projektu http://maven.apache.org/download.html – nie wymaga on tak naprawdę żadnej instalacji – po rozpakowaniu należy jedynie ustawić zmienne środowiskowe: M2_HOME (wskazanie na katalog, do którego rozpakowaliśmy mavena), M2 (wskazanie na katalog [M2_HOME]/bin) i dodać katalog bin do zmiennej PATH (wskazanie na [M2_HOME]/bin).

Struktura projektu

Typowy projekt mavenowy ma strukturę taką, jak na poniższym diagramie:

atena-app
|-- pom.xml
`-- src
    |-- main
    |   `-- java
    |       `-- pl
    |           `-- atena
    |               `-- app
    |                   `-- AtenaApp.java
    |   `-- resources
    |       `--META-INF
    |          `--application.properties
    `-- test
        `-- java
            `-- pl
                `-- atena
                    `-- app
                        `-- AtenaAppTest.java

Sam plik pom.xml może wyglądać tak jak w poniższym fragmencie kodu:

<project xmlns="http://maven.apache.org/POM/4.0.0"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
                      http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>pl.atena</groupId>
  <artifactId>atena-app</artifactId>
  <packaging>jar</packaging>
  <version>1.0-SNAPSHOT</version>
  <name>Maven Quick Start Archetype</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>
</project>

Jest to bardzo prosty podstawowy plik pom.xml. Poniżej krótki opis poszczególnych sekcji pom’a:

  • groupId – twórcę (pl.atena),
  • artifactId – nazwę aplikacji (atena-app),
  • packaging – sposób pakowania (jar, war, ear),
  • version – wersję,
  • name – nazwę wyświetlaną
  • url – link do strony projektu
  • dependecies – zależności od innych modułów

Zakładanie i definiowanie projektu

Projekt możemy zdefiniować ręcznie poprzez utworzenie wymaganej struktury katalogów oraz stworzenie pliku pom.xml. Nie ma tutaj żadnej magii – tak przygotowany projekt jest w pełni poprawnym projektem mavenowym. Takie tworzenie nie jest jednak zbyt przyjemne.

Z pomocą przychodzi mechanizm archetypów (a ściślej mówiąc plugin archetype), czyli tzw. szablony projektów. Poprzez wywołanie poniższej linii stworzymy kompletną strukturę prostego projektu wraz z podstawowym plikiem pom.xml:

mvn archetype:generate -DgroupId=pl.atena -DartifactId=atena-app
-DarchetypeArtifactId=maven-archetype-quickstart -DinteractiveMode=false

Powyższa linia oznacza: wywołaj cel ‘generate’ z pluginu ‘archetype’ i stwórz projekt pl.atena.atena-app na podstawie szablonu maven-archetype-quickstart. W wyniku pracy mavena powinniśmy otrzymać katalog z projektem i prosty pom.xml.

Istnieje bardzo dużo gotowych szablonów, dzięki czemu możemy na przykład przez wywołanie takiego prostego polecenia stworzyć kompilujący się i gotowy do deployu projekt aplikacji webowej zgodny z JEE6.

Budowanie projektu

Jak już wcześniej wspominałem budowanie projektu to wykonywanie określonych celi. Jeśli chcemy np. skompilować projekt wykonujemy polecenie mvn compile z poziomu katalogu głównego projektu (czyli tam gdzie jest umieszczony plik pom.xml).

Możemy wykonać kilka celi po kolei – np.: mvn clean package spowoduje wyczyszczenie wyników wcześniejszych buildów i spakowanie od nowa projektu (poprzedzone kompilacją, gdyż package wywołuje najpierw compile).

Cykl życia projektu

Maven definiuje coś co nazywa się cykle życia projektu. Jest to zestaw standardowych najważniejszych z punktu widzenia aplikacji, bardzo precyzyjnie zdefiniowanych i wykonywanych w określonej kolejności faz. Błąd na dowolnym etapie zatrzymuje wykonywanie kolejnych faz w cyklu. Maven definiuje trzy cykle życia: default, clean i site.

Fazy domyślnego (default) cyklu życia:

  • validate – sprawdza poprawność projektu;
  • compile – kompiluje kod źródłowy;
  • test – wykonuje testy jednostkowe;
  • package – pakuje skompilowany kod w paczki dystrybucyjne (np. Jar, war);
  • integration-test – deployuje paczkę w środowisku testów integracyjnych;
  • verify – sprawdza poprawność paczki;
  • install – umieszcza paczkę w lokalnym repozytorium aby mogła być używana przez inne moduły;
  • deploy – umieszcza (publikuje) paczkę w zdalnym repozytorium.

Cykl życia clean czyści wynik działania wcześniejszych buildów.

Cykl życia site generuje dokumentację projektu.

Powyżej opisane fazy są tak naprawdę mapowane na konkretne cele. Na przykład package w przypadku projektu typu jar wywoła jar:jar, a w przypadku projektu typu war war:war.

Pluginy

Funkcjonalność Mavena może być rozszerzana przez pluginy. Wywoływanie celów zdefiniowanych w pluginach odbywa się poprzez wywołanie polecenia mvn [plugin]:[goal], gdzie plugin to nazwa wtyczki, a goal to wywoływany cel.

Użyliśmy tej konstrukcji tworząc projekt z szablonu (mvn archetype:generate).

Samo dodanie plugina do projektu odbywa się poprzez dodanie odpowiedniej definicji w pliku pom.xml. Taka zależność może wyglądać jak poniżej:

<build>
  <plugins>
    <plugin>
      <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-compiler-plugin</artifactId>
      <version>2.0.2</version>
      <configuration>
        <source>1.5</source>
        <target>1.5</target>
      </configuration>
    </plugin>
  </plugins>
</build>

Zależności

Zarządzanie zależnościami bywa trudne nawet w małym projekcie, a w projekcie dużym i złożonym bywa zadaniem bardzo wyczerpującym.

Prawdopodobnie każdy z nas niejednokrotnie wyszukiwał w google’u jakichś JARków i próbował je ściągać z przypadkowych stron. Ponadto pracując w projekcie złożonym z kilku modułów bardzo często napotykamy problem dostarczania coraz to nowszej wersji modułu do innego obszaru projektu. W tych problemach z pomocą ponownie przychodzi Maven. Zarządzanie zależnościami jest chyba tym z czego Maven tak naprawdę słynie najbardziej.

Dodanie zależności sprowadza się do dodanie elementu dependency w pliku pom.xml. W elemencie dependency wkazujemy dostawcę (groupId), nazwę modułu (artifactId), wersję (version) i zasięg (scope). Maven podczas budowania automatycznie pobierze z repozytorium moduł od którego zależność zdefiniowaliśmy (wcześniej może być konieczne zdefiniowanie dodatkowych repozytorium).

Przykładowa definicja zależności:

<dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>3.8.1</version>
      <scope>test</scope>
    </dependency>
    ……
  </dependencies>

Zależności przechodnie

Zależności przechodnie oszczędzają nam pracy związanej z odkrywaniem od czego jeszcze musimy zależeć w związku ze zdefiniowaniem zależności od jakiegoś modułu. Jeśli nasz moduł A zależy od modułu B, a moduł B zależy od kolejnych modułów to te końcowe zależności zostaną automatycznie dołączone do naszego projektu.

Istotnym elementem definiowania zależności jest określenie zasięgu (scope). Może mieć on jedną z poniższych wartości:

  • compile – domyślny zasięg – zależności o zasięgu kompilacji są dostępne we wszystkich fazach (kompilacji, uruchomienia) projektu i są propagowane do projektów zależących od mojego projektu.
  • provided – zasięg zbliżony do compile z tą różnicą, że zakładamy dostępność tych bibliotek w runtime. Przykładem dla użycia provided może być Servlet API – potrzebujemy go do kompilacji, ale w runtime będzie udostępniony przez kontener.
  • runtime – zasięg zakłada, że biblioteka nie jest potrzebna do kompilacji, ale tylko do uruchomienia (runtime i test).
  • test – zasięg wskazuje, że biblioteka nie jest wymagana do normalnej pracy aplikacji i jest potrzebna jedynie w fazie testów.
  • system – zasięg zbliżony do provided z tą różnicą, że jawnie wskazujemy jara zawierającego bibliotekę.
  • import (dostęny od Mavena 2.0.9) – zasięg dostępny jedynie dla zależności typu pom – wskazuje Mavenowi, że chcemy dołożyć do naszego projektu zależności zdefiniowane w innym projekcie.

Integracja z IDE

Korzystanie z Mavena tylko z linii poleceń może być uciążliwe, ale oczywiście nie jest to konieczne. Istnieją oficjalne pluginy do Eclipse’a i Netbeans’a. Szczególnie polecanym pluginem do Eclipse’a jest m2eclipse – jest to najstarszy i najdojrzalszy plugin i działa bardzo poprawnie.

Więcej o pluginach do Eclipse’a można znaleźć na stronie http://maven.apache.org/eclipse-plugin.html, a o pluginach o Netbeans’a na http://maven.apache.org/netbeans-module.html. InteliJ wspiera mavena natywnie.

Podsumowanie

Powyższy artykuł jest tak naprawdę tylko wstępem, który mam nadzieję choć trochę przybliży Mavena i zachęci do zagłębienia się w temat. Maven daje wiele więcej – udostępnia mechanizmy dziedziczenia i agregacji pom’ów, pozwala na tworzenie własnych artefaktów, może być rozszerzany o wtyczki wspierające deployment komponentów na różnych serwerach aplikacyjnych … i wciąż się rozwija.

Gdzie ja mam wrzucić te pliki?


Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *

2 komentarzy do “Wprowadzenie do Maven’a