Maven i projekt wielomodułowy - moduły Javy

Dziś zastanawiam się, czym jest moduł (jaki moduł? javowy? mavenowy? IDE-owy?) i próbuję przekształcić projekt modularny, który utworzyłam na potrzeby wpisu o Javie 9 i budowałam używając make
w projekt budowany i uruchamiany przy użyciu Mavena.
Moduły Javy, moduły Mavena, moduły w IDE
Zanim w Javie 9 zostały prowadzone moduły, programiści znali już pojęcie modułu z innych kontekstów. Używali modułów w IntellijIdea, używali modułów w Mavenie. A teraz doszły jeszcze moduły w Javie.
Choć każde z tych pojęć oznacza coś zupełnie innego, to w modelu mentalnym programisty byty o tej samej nazwie były dość często utożsamiane. IntellijIdea, importując wielomodułowy projekt mavenowy, tworzy “swój” moduł dla każdego podprojektu. Przy odpowiedniej strukturze katalogów i umiejscowieniu module-info.java
moduły te mogą stać się również modułami javowymi. Dlatego mówiąc o modułach warto zawsze mieć świadomość tego, o jakim module jest mowa.
Po kolei i bardzo krótko zrobię teraz porządek w swoim modelu mentalnym. Kiedy mowa o modułach, to w zależności od kontekstu chodzi o jeden z trzech typów modułów:
- Moduły w Javie
- Moduły w IDE
- Moduły Mavena
Moduły w Javie
Moduły w Javie są obecne od wydania nr 9. Pisałam o nich wcześniej, możesz rzucić okiem tutaj. Jeśli jako programistka/programista nie miałaś/miałeś okazji ich używać, to warto o nich poczytać. Choćby po to, żeby rozumieć, co oznaczają i jak należy interpretować pliki module-info.java
, które zobaczysz w źródłach.
Polecam również serię artykułów na stronie https://developers.ibm.com (patrz źródła)
Moduły w IDE
Jeden z bardziej popularnych IDE w świecie Javy - IntellijIdea - od dawna posiada pojęcie modułów:
- Każdy projekt zawiera przynajmniej jeden, tworzony podczas tworzenia projektu, moduł. Docelowo może zawierać ich wiele.
- Każdy z modułów wchodzących w skład projektu posiada odrębną konfigurację - tzw. plik modułu z rozszerzeniem
iml.
. - Każdy może (lecz nie musi) mieć tzw. content root, czyli katalog zawierający standardową strukturę podkatalogów ze źródłami, testami czy “zasobami”.
- Każdy może korzystać z odrębnego zbioru bibliotek czy frameworków, każdy w końcu może być modułem z kodem źródłowym w innym języku programowania (Ruby, Kotlin, Python etc.)
IntellijIdea obsługuje moduły Java od wersji 2017.1 (post o obsłudze Javie 9, post o nowościach dla Java 9)
Moduły w Mavenie
Standardowym sposobem tworzenia projektu wielomodułowego w Mavenie jest utworzenie projektu-rodzica, definiującego parent pom, zawierającego w podkatalogach projekty-dzieci, które są właściwymi “modułami” w rozumieniu Mavena.
parent pom definiuje listę swoich dzieci w sekcji modules
. Każdy z projektów-dzieci (mavenowych modułów):
- jest projektem ze standardowym układem katalogów
- posiada swój
pom.xml
- definiuje swoje współrzędne
artefactId
orazversion
- może również wskazywać parent pom jako swojego rodzica
W takim układzie budowanie projektu rodzica oznacza budowanie w odpowiedniej kolejności wszystkich projektów - dzieci.
Przygotowanie projektu do budowania przy użyciu Mavena
W ramach ćwiczenia przekształcę projekt bggen (dostępny na githubie jako java-9-modules-example i do kommita 139f25d budowany przy pomocy make
) na projekt budowany przy pomocy mavena.
Zobaczymy, z jakimi wyzwaniami przyjdzie mi się zmierzyć.
Do dzieła!
Przygotowanie pom.xml dla projektu-rodzica
Na początek należy się zaopatrzyć we właściwe narzędzia. Maven, który będzie w stanie obsłużyć javę 9 to maven w wersji 3.5.0 oraz maven-compiler-plugin w wersji 3.6.0.
Sprawdzę, która wersja mavena jest zainstalowana w moim systemie:
|
|
Werja 3.6.3 może być. Zatem zabieram się do pisania głównego pom.xml:
W konfiguracji głównego pom.xml definiuję koordynaty projektu i ustawiam odpowiednią wersję pluginu kompilatora. Przygotowuję też sekcję modules
, w której wymienię po kolei wszystkie “podmoduły” wchodzące w skład, albo raczej budowane w ramach głównego builda.
|
|
Powyższy pom.xml wymienia w sekcji modules
moduł api
, dla którego również przygotowuję pom.xml (wskazujący na powyższy jako swojego parenta). pom.xml
dla modułu api
wygląda tak:
Przygotowanie pom.xml dla podprojektów
Moduł API: pom.xml
|
|
Podobnie definiuję pliki pom.xml
dla pozostałych modułów: client, guiclient i generator, umieszczając je w katalogach głównych tych modułów, zmieniając <artifactId>
oraz <name>
tak, aby - dla uproszczenia - odpowiadała nazwie modułu.
Budowanie
Buduję aplikację z poziomu katalogu głównego - tam, gdzie znajduje sie parent pom:
|
|
Zbudowanie aplikacji pozornie zakończyło się sukcesem:
|
|
Pojawiły się jednak ostrzeżenia, że jary są puste. Coś w nich jest, ale nie są to moje skompilowanie klasy:
|
|
Jak wskazać Mavenowi katalog ze źródłami
Odpowiednia konwencja
Nie trzymałam się konwencji. Moje źródła są bezpośrednio w katalogu src
, a nie - jak nakazuje zwyczaj - w src/main/java
, dlatego też muszę pokazać mavenowi, gdzież te źródła są. Dzięki szybkiemu spojrzeniu na artykuł Using Maven When You Can’t Use the Conventions wiem, że muszę zdefiniować w projektach właściwość sourceDirectory
wewnątrz sekcji build
. Dodaję do client/pom.xml odpowiedni fragment:
|
|
… i aktualizuję w ten sposób wszystkie pliki pom.xml w modułach.
Jak skompilować z odpowiednimi opcjami kompilatora
Rekordy to wciąż preview feature
Maven widzi już katalog ze źródłami, ale twierdzi, że brakuje mi średników. Gdzieś w okolicy deklaracji rekordu…
|
|
Kompilator ma kłopot - używam rekordów, które w wersji javy 15 wciąż są w fazie preview (objawia się to komunikatem o braku średnika, ponieważ parser nie rozpoznaje składni w deklaracji rekordu). Dodaję więc do opcji kompilatora w parent pomie --enable-preview
:
|
|
I teraz po mvn package
, mimo oczywistych ostrzeżeń:
|
|
…widzę już dobrą zawartość jara:
|
|
Uruchomienie
Klasy wykonywalne znajdują się w modułach client (klient testowy ilustrujący korzystanie z modułów: client/com.kamilachyla.Main) i guiclient (generujący obrazek .png: guiclient/com.kamilachyla.guigen.ImageGenerator).
Klasa główna w guiclient
Zaczynam od modułu guiclient. W guiclient/pom.xml używam plugina exec-maven-plugin
, w którym:
-
używam celu “exec”, który pozwala na uruchomienie nowego procesu (w tym wypadku projesu javy)
-
podaję listę argumentów odzwierciedlającą linię poleceń w moim Makefile:
-
używam
--enable-preview
(klasa główna używa rekordów, które nawet w wydaniu 15. są oznaczone jako preview features) -
wskazuję na
--module-path
, jednak nie wprost, lecz korzystam z automatycznie wygenerowanej przez Mavena listy modułów, od których zależy moduł z klasą wykonywalną -
wskazuję na klasę główną, używając argumentu
--module
-
oraz - w końcu - podaję listę argumentów do mojego programu (wymiary obrazu, nazwę pliku wynikowego oraz nazwę genenratora kwadratów (“squares”)
Konfiguracja pluginu w guiclient/pom.xml wygląda następująco:
|
|
Z wiersza poleceń uruchamiam aplikację używając opcji Mavena -pl guiclient
, dzięki czemu proces budowania zacznie się od guiclient
i zostanie wykonany cel exec:exec.
|
|
Uwaga!
Podczas uruchamiania programu maven określa lokalizacje potrzebnych modułów na podstawie koordynatów zdefiniowanych w pom.xml, a więc próbuje je odnaleźć w lokalnym repozytorium (nie wystarczą jary zbudowane przy użyciu mvn package
). Dlatego należy zainstalować potrzebne zależności uzywając wcześniej mvn install
. Dopiero wtedy będzie możliwe uruchomienie aplikacji z mavena w podany wyżej sposób.
Klasa główna w client
Analogicznie przygotowuję konfigurację pluginu dla modułu client
(który służy jedynie do celów demonstracyjnych - nie generuje obrazków, lecz wypisuje nazwy generatorów i oraz współrzędne wygenerowanych prostokątów):
|
|
Wszystkie pliki pom.xml dodałam do repozytorium w kommicie 758073a7f.
Wnioski
Całe powyższe ćwiczenie miało na celu migrację systemu budowania z make do Mavena. Wcześniej budowałam projekt przy użyciu make
- napisanie własngo Makefile pozwoliło mi przećwiczyć użycie nowych opcji kompilatora i jvm-a w przypadku aplikacji modularnej.
Użycie Mavena to jednak standard świecie Javy, w którym progrmiści używają go (ew. Gradle-a) produkcyjnie. Dlatego “zmejwenizowałam” swoją małą aplikację - może kiedyś przestanie być mała i wtedy warto mieć przy sobie porządny system budowania.
Zresztą, tak sobie myślę, być może dałoby się napisać Makefile w projekcie javowym w sposób bardzo przenośny. Mój Makefile nie aspiruje tak wysoko. Jest pisany na kolanie i służy jako szybki test do sprawdzenia zachowania różnych opcji. I jako taki również ma swoje skromne miejsce w projekcie.
Żródła
- Moduły w IntellijIdea
- Projekt wielomodułowy - Baeldung
- Jak używać Mavena gdy nie można użyć konwencj
- Exec maven plugin examples: exec:exec
Seria artykułów o modułach w javie:
- Java 9+ modularity: The theory and motivations behind modularity
- Java 9+ modularity: Module basics and rules
- Java 9+ modularity: How to design packages and create modules, Part 1
- Java 9+ modularity: How to design packages and create modules, Part 2
- Java 9+ modularity: The difficulties and pitfalls of migrating from Java 8 to Java 9+