Projekty informatyczne mają to do siebie, że z czasem się bardzo rozrastają. Sam proces budowania aplikacji staje się dość skomplikowany. Zbudowanie jednego projektu może być uzależnione od zbudowania innego, wersje projektów/bibliotek z czasem mogą się zmieniać na wyższe lub po prostu dochodzić mogą nowe biblioteki. Programiści są leniwi - nie będzie przecież taki człowiek teraz brał i ręcznie ściągał nowe wersje bibliotek i zastępował starsze. Albo pamiętał, że najpierw musi zbudować projekt CommonUtils aby potem projekt MailService mógł korzystać z nowych klas, które można wykorzystać do wysyłania emaili. Mało tego - taki programista musi pamiętać, aby przed zbudowaniem aplikacji - odpalić wszystkie testy, które sprawdzą czy jakaś funkcjonalność przez przypadek nie została zepsuta podczas aktualnych prac.
Programiści postanowili więc stworzyć narzędzie, które będzie te wszystkie czynności (i wiele więcej) robiło samo. Mało tego, ma ono pamiętać jaki projekt od którego zależy, w przypadku zmian wersji bibliotek sam ma to zauważyć i sam ściągnąć je z Internetu, ma sam odpalić testy i pilnować aby przechodziły a na koniec tak zbudowaną aplikację wrzucić na serwer produkcyjny i odpalić nowo zbudowaną aplikację. Czyż to nie piękne? Ile rzeczy, które dotychczas były na głowie programisty zniknęło? Programista ma zająć się pisaniem kodu a nie konfigurować, pamiętać, wykonywać jakieś magiczne skrypty itp.
Na tego typu okazję został stworzony Maven jako następca już prawie wymarłego ANT’a. Maven potrafi zrobić wszystko z naszym projektem podczas jego budowania. Jedyne co my musimy zrobić to zastosować się do konwencji nazewnictwa katalogów w projekcie jaką wymaga Maven. Jeśli to zrobimy - to już sam Maven zatroszczy się o kompilowanie kodu, wykonywanie testów itp. My jedynie będziemy mu wydawali polecenia - on sam ogarnie resztę.
Dlaczego to wszystko jest ważne? Oprócz oczywistych oszczędności czasu i odbarczeniem programisty rzeczami, które on nie powinien się zajmować - można wyróżnić szereg innych zalet jakie daje Automatyzacja procesu budowania aplikacji:
- automatyczna kompilacja kodu
- usunięcie starych plików z poprzedniego budowania aplikacji
- pilnowanie kolejności budowania modułów aplikacji (pilnowanie zależności projektu od projektu)
- pilnowanie bibliotek oraz ich wersji
- wykonywanie testów aplikacji
- generowanie pliku .jar (.war) z naszą aplikacją
- generowanie dokumentacji
- uniwersalność środowiska/konfiguracji - działa u mnie? powinno działać u Ciebie!
- i wiele innych
Wszystkie te rzeczy składają się na jeden wielki proces budowania naszej aplikacji. Gdybyśmy przecież chcieli zbudować aplikację sami krok po kroku to bardzo często byśmy gdzieś się pomylili, o czymś zapomnieli. Wtedy aplikacja nie zbuduje się, bądź zbuduje się niepoprawnie - a my nawet możemy nie być tego świadomi. Maven zatroszczy się o wszystko. Począwszy od stworzenia projektu poprzez kompilację, testy jednostkowe, integracyjne, tworzenie dokumentacji i deploy’owanie gotowego programu w miejscu docelowym. Wszystko za pomocą nawet jednej komendy. Mało tego - jeśli ściągamy jakąś bibliotekę, która wymaga do swojego działania innej (zależy od niej) to Maven to zauważy i dociągnie tą zależność. Często jest tak, że ściągając jakąś bibliotekę - dociąga ponad 20-30 bibliotek zależnych (nasza biblioteka zależała od biblioteki B, B zależała od C i D, D od F oraz J…..). Śmiem twierdzić, że samemu próbując ściągać kolejne biblioteki, odpalając program i zauważając w logach, że brakuje kolejnej - dociągać ją, znów patrzeć w logi… i próbować te 30 bibliotek ściągać ręcznie - po 1h darowalibyśmy sobie przygodę z programowaniem. Na szczęście jest Maven :)
Zamiast opowiadać o Mavenie - zobaczmy go w akcji.
Instalacja Mavena
Mavena się instaluje w swoim systemie. Instalacja - to może za dużo powiedziane - rozpakowuje się go z archiwum i dodaje plik mvn do PATH systemu, aby można było go używać w systemie. Więcej tutaj.
Jednak gdy korzystamy z IntelliJ IDEA to mamy tam wbudowaną wersję Mavena która nam w zupełności wystarczy.
Pierwsza aplikacja stworzona za pomocą Apache Maven
Rozpocznijmy więc od stworzenia naszej pierwszej aplikacji zarządzanej przez Mavena. Wybieramy w IntelliJ IDEA:
- File → New → Project…
- Zakładka Maven → wybieramy wersję Java SDK na 10 → Next
- Wypełniamy pola: ** GroupId: wpiszmy np. pl.kodolamacz ** ArtifactId: firstApp ** Version: 1.0-SNAPSHOT
- klikamy Next
- nazywamy projekt firstApp i wskazujemy jego lokalizację na dysku → klikamy Next
- klikamy Finish
W ten sposób powstał szkielet naszej aplikacji. Gdy przyjrzymy się temu co powstało - zobaczymy następującą strukturę katalogów:
|- src
| \
| |- main
| | \
| | |- java
| | |- resources
| |- test
| \
| | - java
| | - resources
|- target
|- pom.xml
Wyjaśnijmy co jest czym:
Folder | Znaczenie |
---|---|
src/main/java | nasz kod Java projektu (tu kodujemy) |
src/main/resources | pliki konfiguracyjne dla klas Java, np. dla różnych bibliotek itp. |
src/test/java | kod Java dla testów aplikacji |
src/test/resources | pliki konfiguracyjne dla klas testowych |
target | folder gdzie Maven produkuje wszystkie owoce swojej pracy |
pom.xml | główny plik konfiguracyjny projektu |
Czym są foldery resources? Wykorzystuje się często do umieszczania wszelkiej maści plików pomocniczych. Bardzo często wykorzystujemy w projektach jakieś pliki które inicjują naszą aplikację (jakieś nasze własne pliki słowników wczytywane podczas startu aplikacji) albo pliki konfiguracyjne elementów aplikacji - wtedy dobrym miejscem na ich umieszczenie są foldery resources. Różnica między tym umieszczonym w folderze src a tym umieszczonym w folderze test jest taka, że ten z folderu test jest wykorzystywany w fazie testowania aplikacji (sprawdzania czy aplikacja działa jak powinna) i nie zostanie dołączony do finalnego pliku naszej aplikacji (np. jar) podczas budowania.
No dobrze - posiadamy już jakiś kod, którym możemy zarządzać Mavenem. Aby to jednak zrobić musimy być świadomi jak pracuje Maven. Zamysł jego działania opiera się o tzw. cykle życia (build lifecycle). Cykle życia składają się z faz, które są kolejnymi elementami na drodze od kodu źródłowego do projektu uruchomionego np. na serwerze. Istnieją 3 podstawowe cykle życia:
- clean - czyści po sobie poprzednie efekty budowania aplikacji (często oznacza to wyczyszczenie folderu target)
- site - buduje dokumentację projektu (np. tzw. javadoc)
- domyślny cykl, który z kolei składa się z następujących faz (w prawidłowej kolejności):
Faza | Wyjaśnienie |
---|---|
validate | validuje strukture projektu |
compile | kompiluje kod źródłowy |
test | uruchamia testy |
package | tworzy artifact w katalogu określonym jako ‚target’ |
integration-test | deploy artifaktu w środowisku integracyjnym |
integration-test | uruchamia walidacje paczki |
install | instaluje paczke w lokalnym repozytorium |
deploy | kopiuje artifact na serwer |
Każda faza zależy od poprzedniej. Wydając więc polecenie mvn compile Maven najpierw wykona fazę validate a następnie compile. No dobrze, w takim razie użyjmy pierwszy raz Mavena.
Po prawej stronie w IDE będziemy mieć zakładkę Maven Projects. Znajdziemy tam drzewa, gdzie korzej będzie nazywał się jak nasza aplikacja, czyli firstApp, zaś pod nim będzie gałąź Lifecycle oraz Plugins. Z listy cykli życia (Lifecycle) klikamy dwa razy lewym przyciskiem myszy na opcji clean a następnie analogicznie na opcji install. Jak zobaczymy - dużo będzie działo się w konsoli. Maven po kolei będzie wykonywał to co mu powiedzieliśmy. Z reguły pierwsze uruchomienie powoduje, że budowanie trwa dłużej gdyż Maven musi sobie pościągać niezbędne biblioteki. Skutek pracy pojawi się w katalogu target.
Koncepcja repozytoriów
Maven działa na zasadzie trzymania wybudowanych projektów w repozytoriach (wytworzonych w fazie install). Co to wszystko znaczy? Jeśli przyjrzymy się w logi fazy (install) zauważymy, że na końcu były logi w stylu:
...
...
[INFO] --- maven-install-plugin:2.4:install (default-install) @ firstApp ---
[INFO] Installing /home/acacko/IdeaProjects/firstApp/target/firstApp-1.0-SNAPSHOT.jar to /home/acacko/.m2/repository/pl/kodolamacz/firstApp/1.0-SNAPSHOT/firstApp-1.0-SNAPSHOT.jar
[INFO] Installing /home/acacko/IdeaProjects/firstApp/pom.xml to /home/acacko/.m2/repository/pl/kodolamacz/firstApp/1.0-SNAPSHOT/firstApp-1.0-SNAPSHOT.pom
Po co robi to Maven? Po co zbudowaną aplikację wrzuca od razu do repozytorium utworzonym w jednym z katalogów naszego komputera? Odpowiedź jest prosta: gdybyśmy chcieli za chwilę użyć jej w innym projekcie - Maven znajdzie ją sobie w repozytorium. Tak z resztą się często dzieje - gdy nasza aplikacja składa się z kilku podprojektów (tzw. modułów) - to Maven buduje najpierw te, które nie zależą od innych projektów, następnie wrzuca je do repozytorium a potem przechodzi do kolejnych projektów - a gdy któryś kolejny projekt potrzebuje kodu projektu już zbudowanego wcześniej - Maven po prostu sięga po jego kod do repo. Nieco później poznamy jeszcze większą zaletę tego podejścia - ale to jeszcze chwilka.
Plik pom.xml - sercem naszej aplikacji
To, że nasza aplikacja jest taka super - w zasadzie wynika tylko z jednej rzeczy - posiada ona plik pom.xml. Przyjrzyjmy się temu plikowi. Po kliknięciu na pom.xml wybieramy opcję surowego podglądu xml’a. Inne okienka są dla amatorów :)
<?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/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<!-- to na górze jest zawsze takie samo, poniżej jest to co nas interesuje -->
<groupId>pl.kodolamacz</groupId>
<artifactId>firstApp</artifactId>
<version>1.0-SNAPSHOT</version>
</project>
Ta część: „project xmlns=„http://…” jest jaka jest - nie musimy się przejmować, że nie wiemy co to jest. Taka jest konwencja i tyle. Bardziej interesują nas informacje, które są potem:
Tag | Wyjaśnienie |
---|---|
groupId | twórca (pl.edu.uksw) |
artifactId | nazwa aplikacji (firstApp) |
packaging | sposób pakowania aplikacji (jar, war, ear, pom). My znamy tylko jar |
version | wersja naszej aplikacji |
name | nazwa wyświetlana (opcjonalnie) |
url | link do strony projektu (opcjonalnie) |
dependencies | zależności od innych modułów (opcjonalnie) |
To jest podstawowa wersja pom.xml, która zawiera minimalną ilość informacji i funkcjonalności. Przyjrzyjmy się schematowi całego tego pliku:
<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>
<!-- The Basics -->
<groupId>...</groupId>
<artifactId>...</artifactId>
<version>...</version>
<packaging>...</packaging>
<dependencies>...</dependencies>
<parent>...</parent>
<dependencyManagement>...</dependencyManagement>
<modules>...</modules>
<properties>...</properties>
<!-- Build Settings -->
<build>...</build>
<reporting>...</reporting>
<!-- More Project Information -->
<name>...</name>
<description>...</description>
<url>...</url>
<inceptionYear>...</inceptionYear>
<licenses>...</licenses>
<organization>...</organization>
<developers>...</developers>
<contributors>...</contributors>
<!-- Environment Settings -->
<issueManagement>...</issueManagement>
<ciManagement>...</ciManagement>
<mailingLists>...</mailingLists>
<scm>...</scm>
<prerequisites>...</prerequisites>
<repositories>...</repositories>
<pluginRepositories>...</pluginRepositories>
<distributionManagement>...</distributionManagement>
<profiles>...</profiles>
</project>
Cóż - trochę tego jest. W Mavenie można zdziałać cuda - ale za długo by zmieścić je w jednym artykule. Skupimy się na tych najważniejszych.
Zależności
Co to jest zależność? Jest to projekt od którego zależy nasz projekt. Często jest tak, że gdy piszemy coś w projekcie np. aplikacji internetowej to zależeć ona będzie od projektu, który potrafi zapisywać dane do bazy danych. Dołączamy sobie więc w naszym projekcie zależność do projektu zarządzania bazą danych. Maven podłączy potrzebne kody do naszego projektu tak abyśmy mogli używać obiektów z tamtego projektu. Z resztą - może nawet za daleko szukamy - przecież każda biblioteka jakiej używamy w projekcie jest zależnością. Wszak nasz projekt jej potrzebuje do swojego działania. Niech teraz Maven martwi się aby gdzieś ją znaleźć i podłączyć do naszego projektu.
Wróćmy do naszego problemu katalogu ludzi po ich wieku. Pewnie zauważyliśmy, że jest to nieco męczące aby co chwila wrzucać do mapy nowy Set bo inaczej leciały nam NullPointery przy próbie wrzucenia pierwszej osoby o danym wieku. Kod wygląda amatorsko i można zrobić w nim pełno błędów. No więc co? Programista szuka pomocy w google: “Java Map with Multiple Values” i co? Otwieramy pierwszą stronę naszej ulubionej strony i co widzimy? Najwyżej ocenione jest nasze podejście - wrzucanie Set’ów do Mapy - przyznam, że podejście amatorskie - ale szybkie. Trzecie rozwiązanie jest przy użyciu biblioteki Apache Commons Collections 4 i obiektu MultiValueMap. Wygląda spoko więc użyjmy jej w naszym projekcie. Co należy zrobić? Wpisujemy sobie w google: “Apache Collections 4 maven” i wchodzimy w pierwszy link. Jest tam informacja, że Maven posiada tą bibliotekę w swoim repozytorium centralnym. Daje garść informacji o niej oraz to co najważniejsze - kod xml, aby przekopiować do pliku pom. Zróbmy to, pamiętając z ogólnego schematu, że zależności podaje się w bloku
<?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/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>pl.kodolamacz</groupId>
<artifactId>firstApp</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<!-- https://mvnrepository.com/artifact/org.apache.commons/commons-collections4 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
<version>4.0</version>
</dependency>
</dependencies>
</project>
IntelliJ IDEA powinna w tle ogarnąć, że zmienił się POM i spróbować zaciągnąć zależności poprzez uruchomienie w tle Mavena. Jeśli nie, należy jak poprzednio uruchomić opcję clean oraz install. W IDE pojawi się ona na liście z lewej strony w dziale “External Libraries”.
Podczas budowania aplikacji (Maven install) zauważymy, jak nasza biblioteka jest ściągana z Internetu… a następnie wrzucana do naszego repozytorium (~/.m2/repository/…). Jeśli za chwilę drugi raz będziemy budować - Maven tylko sprawdzi czy nic się nie zmieniło z tą wersją biblioteki odczytując sumę kontrolną - a następnie stwierdzi, że ma już tą bibliotekę zaciągniętą wcześniej - i użyje jej. Zajmie to wtedy dużo mniej czasu.
Pluginy
Maven posiada wiele pluginów, które potrafią rozszerzyć funkcjonalności Mavena. Począwszy od możliwości wskazywania wersji Javy jaką chcemy użyć a skończywszy na pluginach, które potrafią same połączyć się ze zdalnym serwerem i podmienić wersję aplikacji na tą właśnie zbudowaną i zrestartować maszynę.
Zacznijmy więc od tego najbardziej powszechnego - ustawiającego wersję Javy na taką jaką chcemy. Gdzie umieszczamy nasze pluginy? Nie widać ich w ogólnym schemacie xml. Nie widać ich bo umieszczamy je w sekcji
<?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/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>pl.kodolamacz</groupId>
<artifactId>firstApp</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<!-- https://mvnrepository.com/artifact/org.apache.commons/commons-collections4 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
<version>4.0</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.7.0</version>
<configuration>
<source>10</source>
<target>10</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
Spróbuj zmienić wersję Javy np. na 1.3 i zobaczy czy skompiluje się kod. Jeśli IDE nie zauważy co się stało w POM to zawsze robimy clean potem install a następnie w zakładce Maven Projects → Reimport All Maven Projects (pierwsza ikonka na górze od lewej)
Pluginów jest mnóstwo i do wszystkiego. Zależnie co chcemy - pewnie już zostało to już zrobione. Część z nich już są zainstalowane domyślnie. Możemy je odnaleźć w zakładce Maven Projects → firstApp → Plugins.
Inne rzeczy, które powinieneś wiedzieć
Poza prostym budowaniem projektu i ściąganiem niezbędnych bibliotek, Maven potrafi znacznie więcej. Warto poznać takie zagadnienia jak choćby:
- Używanie pliku settings.xml, który pozwala ustawić konfigurację dla wszystkich projektów - np. listę repozytoriów Mavena, hasła do GIT, jakieś wartości parametrów itp,
- Tworzenie tzw. Parent POM’a, który daje możliwość zarządzać wieloma projektami, które „dziedziczą” właściwości po tym POMie. Można w jednym miejscu np. zmienić wersję biblioteki na nowszą - i wszystkie projekty będą miały ją zaktualizowaną bez potrzeby szukania jej po wszystkich POM innych projektów
- Tworzenie tzw. modułów czyli wielu podprojektów składających się na jeden projekt. Np. często jest tak, że jeden podprojekt odpowiada za komunikację z bazą danych zaś inny podprojekt odpowiadający za aplikację desktop’ową go używa, oraz inny podprojekt odpowiadający za aplikację WEBową też go używa. Każdy z nich dodaje go do swojej zależności (dependency).
- Używanie parametrów w projektach
- Używanie profili Mavena
- i wiele innych
Mimo że Maven jest jednym z najpopularniejszych rozwiązań w świecie JVM do zarządzania budowaniem projektów, ma swoją konkurencję. Jednym z ciekawszych tego typu projektów jest Gradle.