Spis treści
W tym rozdziale:
- dowiemy się więcej o parametrze scope zależności,
- wykorzystamy lokalny projekt jako zależność,
- dowiemy się, co to jest efektywny POM i zależności przechodnie,
- zobaczymy, jak skonfigurować prosty projekt wielomodułowy.
Parametr scope zależności¶
Gdy konfigurujemy zależność w pliku pom.xml w elemencie <dependency>, możemy ustawić wartość opcjonalnego parametru o nazwie scope.
Domyślnie scope ma wartość compile, co powoduje, że zależność jest wymagana zarówno podczas kompilacji, testów, a także wykonywania naszego programu. Gdy w jednym z poprzednich rozdziałów dodawaliśmy zależność do Log4j, nie ustawiliśmy scope, więc parametrowi temu nadana została wartość domyślna compile:
<dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-api</artifactId> <version>2.13.1</version> </dependency> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-core</artifactId> <version>2.13.1</version> </dependency>
Scope może przyjmować kilka wartości – spójrzmy na trzy z nich:
- compile – wartość domyślna, zależność wymagana podczas kompilacji, testów, oraz wykonywania programu,
- test – zależność potrzebna tylko w fazie testów – taką wartość ustawiamy dla np. JUnit, ponieważ JUnit potrzebujemy w naszych aplikacjach tylko, gdy je testujemy – w wersji produkcyjnej naszego kodu JUnit nie jest potrzebne:
<dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.11</version> <scope>test</scope> </dependency>
- runtime – zależność potrzebna dopiero na etapie działania programu – nie jest wymagana ani podczas kompilacji, ani testów – przykładem może być np. sterownik do obsługi bazy danych takiej jak Oracle.
Więcej informacji o scope i opis pozostałych wartości, jakie może przyjmować, znajdziesz w oficjalnej dokumentacji.
Zależności przechodnie (transitive dependencies)¶
Gdy w pliku pom.xml dodajemy pewną zależność, Maven pobiera ją z centralnego repozytorium. Poza plikiem JAR, pobiera także plik pom.xml, dzięki czemu może sprawdzić, jakie zależności mają nasze zależności.
Zależności takie nazywami przechodnimi (transitive dependencies) i w większych projektach może ich być bardzo wiele – dodanie do projektu jednej biblioteki może pociągnąć za sobą kilkanaście zależności przechodnich.
Spójrzmy na poniższy przykład – wygenerowałem prosty szkielet aplikacji Spring Boot za pomocą Spring Initializr. Aby otrzymać listę wszystkich zależności tego projektu (bezpośrednich oraz przechodnich), korzystamy z komendy mvn dependency:tree, która zwraca informację o zależnościach w formie drzewa:
Powyższa aplikacja to prosty szkielet aplikacji korzystającej ze Spring Boota – pomimo tego, liczba zależności przechodnich jest bardzo duża, co widać na powyższym listingu.
Zależność do lokalnego projektu¶
Zaletą Mavena jest nie tylko to, że możemy wskazać zależności do pewnych bibliotek, które Maven pobierze z centralnego repozytorium, ale także możliwość korzystania z naszych własnych projektów jako zależności.
Na początku tego kursu widzieliśmy, że Maven w fazie install kopiuje wygenerowany plik JAR z naszym projektem do lokalnego repozytorium artefaktów .m2/repository – od tej pory możemy w innych naszych projektach dodawać elementy <dependency> wskazujące na nasz projekt.
W projekcie z poprzedniego rozdziału zawarliśmy klasę, która liczyła silnię. W innym projekcie moglibyśmy dodać następujący wpis do pliku pom.xml, aby móc wykorzystać funkcjonalności z tamtego projektu:
<?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>com.kursjava.maven</groupId> <artifactId>wykorzystanie-innego-projektu</artifactId> <version>1.0-SNAPSHOT</version> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <maven.compiler.source>1.7</maven.compiler.source> <maven.compiler.target>1.7</maven.compiler.target> </properties> <dependencies> <dependency> <groupId>com.kursjava.maven</groupId> <artifactId>policz-silnie</artifactId> <version>1.0-SNAPSHOT</version> </dependency> </dependencies> </project>
W klasach Java tego projektu możemy teraz korzystać z klasy FactorialCounter, którą zaimportowaliśmy za pomocą elementu <dependency>:
package com.kursjava.maven; import com.kursjava.maven.FactorialCounter; public class Main { public static void main(String[] args) { System.out.println("Silnia 10 = " + FactorialCounter.factorial(10)); } }
Projekty wielomodułowe¶
Często projekty, nad którymi pracujemy, dzielimy na różne moduły – jeden może być odpowiedzialny za persystencje danych, inny będzie zawierał model danych, a kolejny – webowy interfejs. Różne elementy takich projektów są zależne od siebie, a dodatkowo wszystkie razem tworzą pewien system. Możemy takie moduły powiązać w Mavenie w jeden projekt wielomodułowy.
Projekty wielomodułowe zawierają w katalogu głównym plik pom.xml, który jest "rodzicem" dla wszystkich podmodułów, które są w nim zdefiniowane. Podmoduły to także projekty Mavenowe, które w swoich plikach pom.xml odnoszą się do pliku-rodzica pom.xml. W tym nadrzędnym pliku pom.xml możemy zdefiniować konfigurację pluginów oraz zależności, z których będą mogły korzystać podprojekty. Zaoszczędzi nam to czas i skróci konfigurację plików pom.xml w podmodułach.
Spójrzmy na przykładowy plik pom.xml będący "rodzicem" w projekcie składającym się z dwóch podmodułów:
<project> <modelVersion>4.0.0</modelVersion> <groupId>com.kursjava.maven.multimod</groupId> <artifactId>wielomodulowy-projekt-parent</artifactId> <packaging>pom</packaging> <version>1.0</version> <properties> <maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.target>1.8</maven.compiler.target> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties> <modules> <module>podprojekt1</module> <module>podprojekt2</module> </modules> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-failsafe-plugin</artifactId> <version>3.0.0-M4</version> <executions> <execution> <goals> <goal>integration-test</goal> <goal>verify</goal> </goals> </execution> </executions> </plugin> </plugins> </build> <dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.11</version> <scope>test</scope> </dependency> </dependencies> </project>
Element <packaging> określa, co jest wynikiem zbudowania projektu – do tej pory go nie ustawialiśmy, ponieważ jego domyślna wartość to jar. W przypadku pliku pom.xml, który jest nadrzędnym plikiem konfiguracyjnym w projekcie wielomodułowym, ten element należy ustawić na pom.
Ponadto, powyższy plik zawiera element <modules>, w którym wylistowane są wszystkie podmoduły tego projektu – w tym przypadku są to podprojekt1 oraz podprojekt2.
Zauważmy, że w tym pliku skonfigurowany jest także jeden plugin oraz jedna zależność. Dzięki temu, wszystkie podmoduły w tym projekcie będą od razu mogły korzystać z uruchamiania testów integracyjnych (dzięki konfiguracji pluginu Failsafe) oraz będą mogły stosować JUnit w testach jednostkowych. Podprojekty odziedziczą także konfigurację z elementu <properties>.
Pierwszy z podmodułów, który powinien znaleźć się w podkatalogu o nazwie podprojekt1, skonfigurowany jest następująco:
<project> <parent> <groupId>com.kursjava.maven.multimod</groupId> <artifactId>wielomodulowy-projekt-parent</artifactId> <version>1.0</version> </parent> <modelVersion>4.0.0</modelVersion> <groupId>com.kursjava.maven.multimod</groupId> <artifactId>podprojekt1</artifactId> <version>1.0-SNAPSHOT</version> </project>
W pliku pom.xml tego podmodułu odnosimy się do projektu-rodzica za pomocą elementu <parent>, który zawiera groupId, artifactId, oraz version, nadrzędnego projektu. W ten sposób ten podprojekt dziedziczy konfigurację z nadrzędnego projektu. Pomimo, że ten moduł nie definiuje bezpośrednio zależności do JUnit, to możemy w nim od razu z tej biblioteki korzystać, ponieważ zależność do niej dziedziczymy po projekcie-rodzicu.
Konfiguracja drugiego podprojektu zostanie pominięta – także zawierałaby ona element <parent>.
Struktura powyższego projektu powinna wyglądać następująco:
wielomodulowy-projekt | |-- pom.xml | |-- podprojekt1 | | | |-- pom.xml | | | `-- src | | | `-- (pozostałe katalogi podprojektu) | `-- podprojekt2 | |--pom.xml | `-- src | `-- (pozostałe katalogi podprojektu)
W tym wielomodułowym projekcie istnieją trzy pliki pom.xml – jeden "rodzic" w katalogu głównym projeku, oraz po jednym pliku pom.xml na każdy z podprojektów.
Podprojekty można normalnie budować i uruchamiać na ich rzecz różne fazy Mavena i pluginy, ale możemy także zbiorczo zbudować wszystkie z poziomu projektu nadrzędnego:
Logi z budowy podmodułów zostały pominięte – byłyby to znane nam już informacje o kolejnych fazach compile, test (wraz z wykonaniem i podsumowaniem testów wykonanych na rzecz każdego z podmodułówy), package itd. Na końcu Maven wypisał podsumowanie o projektach zbudowanych w ramach budowy całego wielomodułowego projektu.
Plugin management i dependency management¶
Konfigurując pluginy i zależności w nadrzędnym pliku pom.xml mamy dwie możliwości:
- możemy tak skonfigurować pluginy i/lub zależności, aby zawsze były dziedziczone przez podmoduły,
- możemy skonfigurować pluginy i/lub zależności, ale nie będą one automatycznie dziedziczone przez podprojekty – każdy podprojekt, który będzie chciał tą konfigurację odziedziczyć z pliku pom.xml-rodzica, będzie musiał ten plugin/zależność umieścić w swoim pliku pom.xml. Taki rodzaj konfiguracji zawarty jest w dodatkowym elemencie w pliku pom.xml: dla pluginów jest to element <pluginManagement>, a dla zależności – <dependencyManagement>.
To rozróżnienie wynika z faktu, że czasem możemy chcieć skonfigurować pewne pluginy i zależności dla wszystkich podmodułów, a inne wstępnie skonfigurować tylko dla tych podmodułów, które faktycznie będą z nich korzystały. Dzięki temu nie wszystkie podmoduły muszą dziedziczyć od razu wszystkie zależności (od pluginów i bibliotek).
Spójrzmy najpierw na na przykład użycia elementu <pluginManagement> w pliku pom.xml-rodzicu:
<build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-failsafe-plugin</artifactId> <version>3.0.0-M4</version> <executions> <execution> <goals> <goal>integration-test</goal> <goal>verify</goal> </goals> </execution> </executions> </plugin> </plugins> <pluginManagement> <plugins> <plugin> <artifactId>maven-assembly-plugin</artifactId> <version>3.2.0</version> <configuration> <descriptorRefs> <descriptorRef>jar-with-dependencies</descriptorRef> </descriptorRefs> </configuration> <executions> <execution> <id>assemble-jar-with-dependencies</id> <phase>package</phase> <goals> <goal>single</goal> </goals> </execution> </executions> </plugin> </plugins> </pluginManagement> </build>
W powyższym pliku pom.xml znajdują się konfiguracje dwóch pluginów: Failsafe oraz Assembly. Ten drugi zawarty jest w elemencie <pluginManagement>, więc nie będzie on od razu wykorzystywany w podprojektach – jeżeli zbudowalibyśmy teraz jeden z podprojektów, to plugin Assembly nie zostałby automatycznie użyty w fazie package do wygenerowanie pliku JAR ze wszystkimi zależnościami.
Aby plugin skonfigurowany w pliku pom.xml-rodzicu był wykorzystywany w podprojekcie, musi on dodać do swojego pliku pom.xml informację, że taki plugin chce używać:
<project> <parent> <groupId>com.kursjava.maven.multimod</groupId> <artifactId>wielomodulowy-projekt-parent</artifactId> <version>1.0</version> </parent> <modelVersion>4.0.0</modelVersion> <groupId>com.kursjava.maven.multimod</groupId> <artifactId>podprojekt1</artifactId> <version>1.0-SNAPSHOT</version> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-assembly-plugin</artifactId> </plugin> </plugins> </build> </project>
Zauważ, że konfiguracja tego pluginu w podprojekcie jest minimalna – w zasadzie podajemy tylko jego nazwę. Reszta konfiguracji zostanie wzięta z pliku pom.xml-rodzica. Powyższa konfiguracja spowoduje, że teraz budując projekt, w fazie package będzie dodatkowo używany plugin Assembly (zgodnie z konfiguracją odziedziczoną z nadrzędnego pliku pom.xml).
Element <dependencyManagement> działa podobnie. Spójrzmy na przykład:
<dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.11</version> <scope>test</scope> </dependency> </dependencies> <dependencyManagement> <dependencies> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-api</artifactId> <version>${log4j.version}</version> </dependency> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-core</artifactId> <version>${log4j.version}</version> </dependency> </dependencies> </dependencyManagement>
Jeżeli skonfigurujemy zależność do Log4j w elemencie <dependencyManagement>, to podprojekty nie będą od razu zależne od Log4j w przeciwieństwie do JUnit, do którego zależność została skonfigurowana poza tym elementem.
Jeżeli któryś z podprojektów chciałby korzystać z Log4j, to musiałby do swojego pliku pom.xml dodać następujący wpis:
<project> <parent> <groupId>com.kursjava.maven.multimod</groupId> <artifactId>wielomodulowy-projekt-parent</artifactId> <version>1.0</version> </parent> <modelVersion>4.0.0</modelVersion> <groupId>com.kursjava.maven.multimod</groupId> <artifactId>podprojekt2</artifactId> <version>1.0-SNAPSHOT</version> <dependencies> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-api</artifactId> </dependency> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-core</artifactId> </dependency> </dependencies> </project>
Tak jak w przypadku konfiguracji pluginów dziedziczonej z nadrzędnego pliku pom.xml, tak i w przypadku zależności wystarczy podać jej nazwę. Wersja (i ewentualnie inne elementy konfiguracji) zostaną odziedziczone z pliku pom.xml-rodzica. Teraz podprojekt2 może korzystać z Log4j.
Podprojekt ten może także stosować JUnit, ale tej zależności w ogóle nie musi konfigurować, ponieważ została ona skonfigurowana w nadrzędnym pliku pom.xml poza elementem <dependencyManagement>, więc jest od razu automatycznie dziedziczona przez podprojekty.
<?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">
Efektywny POM¶
Czasem możemy mieć potrzebę sprawdzić, jak wygląda finalny plik pom.xml np. w jednym z podprojektów w projekcie wielomodułowym. Możemy wtedy skorzystać z komendy mvn help:effective-pom, która zwróci zawartość pliku pom.xml projektu uwzględniając:
- domyślne ustawienia Mavena dla projektów,
- ustawienia odziedziczone z nadrzędnego pliku pom.xml.
Efektywny pom.xml jest bardzo długim plikiem – poniżej znajduje się fragment wyniku komendy mvn help:effective-pom wywołanej na rzecz modułu podprojekt1. Zauważ, że w tym efektywnym pliku pom.xml są m.in. elementy <properties> i <dependency> z JUnit, odziedziczone z pliku pom.xml-rodzica:
<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 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>com.kursjava.maven.multimod</groupId> <artifactId>wielomodulowy-projekt-parent</artifactId> <version>1.0</version> </parent> <groupId>com.kursjava.maven.multimod</groupId> <artifactId>podprojekt1</artifactId> <version>1.0-SNAPSHOT</version> <properties> <log4j.version>2.13.1</log4j.version> <maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.target>1.8</maven.compiler.target> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties> <dependencyManagement> <dependencies> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-api</artifactId> <version>2.13.1</version> </dependency> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-core</artifactId> <version>2.13.1</version> </dependency> </dependencies> </dependencyManagement> <dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.11</version> <scope>test</scope> </dependency> </dependencies> <repositories> <repository> <snapshots> <enabled>false</enabled> </snapshots> <id>central</id> <name>Central Repository</name> <url>https://repo.maven.apache.org/maven2</url> </repository> </repositories> (... pozostała część pliku została pominięta ... ) </project>
Podsumowanie¶
- Parametr scope, który możemy ustawić w <dependency>, określa, kiedy zależność jest wymagana przez nasz projekt.
- Domyślnie scope ma wartość compile, co oznacza, że zależność jest wymagana zarówno podczas kompilacji, testów, a także wykonywania naszego programu.
- Inną możliwą wartością scope jest np. test, co powoduje, że zależność wymagana jest jedynie podczas fazy testów. Przykładem jest JUnit:
<dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.11</version> <scope>test</scope> </dependency>
- Gdy w pliku pom.xml dodajemy pewną zależność, Maven pobiera ją z centralnego repozytorium. Poza plikiem JAR, pobiera także plik pom.xml, dzięki czemu może sprawdzić jakie zależności mają nasze zależności.
- Zależności przechodnie (transitive dependencies) to zależności naszych zależności.
- Dodanie do projektu jednej biblioteki może pociągnąć za sobą kilkanaście zależności przechodnich.
- Aby otrzymać listę wszystkich zależności projektu (bezpośrednich oraz przechodnich), korzystamy z komendy mvn dependency:tree
- Zaletą Mavena jest nie tylko to, że możemy wskazać zależności do pewnych bibliotek, które Maven pobierze z centralnego repozytorium, ale także możliwość korzystania z naszych własnych projektów jako zależności:
<dependency> <groupId>com.kursjava.maven</groupId> <artifactId>policz-silnie</artifactId> <version>1.0-SNAPSHOT</version> </dependency>
- Maven pozwala na tworzenie projektów wielomodułowych.
- Projekty wielomodułowe zawierają w katalogu głównym plik pom.xml, który jest "rodzicem" dla wszystkich podmodułów, które są w nim zdefiniowane.
- W tym "nadrzędnym" pliku pom.xml możemy zdefiniować konfigurację pluginów oraz zależności, z których będą mogły korzystać podprojekty. Zaoszczędzi nam to czas i skróci konfigurację plików pom.xml w podmodułach.
- W nadrzędnym pliku pom.xml listę podmodułów umieszczamy w elemencie <modules>:
<modules> <module>podprojekt1</module> <module>podprojekt2</module> </modules>
- Dla projektu-rodzica ustawiamy element <packaging> na pom. Element ten określa, co jest wynikiem zbudowania projektu – do tej pory nie ustawialiśmy tego elementu, ponieważ jego domyślna wartość to jar:
<groupId>com.kursjava.maven.multimod</groupId> <artifactId>wielomodulowy-projekt-parent</artifactId> <packaging>pom</packaging> <version>1.0</version>
- Podmoduły to także projekty Mavenowe, które w swoich plikach pom.xml odnoszą się do pliku-rodzica pom.xml w elemencie <parent>:
<project> <parent> <groupId>com.kursjava.maven.multimod</groupId> <artifactId>wielomodulowy-projekt-parent</artifactId> <version>1.0</version> </parent> <modelVersion>4.0.0</modelVersion> <groupId>com.kursjava.maven.multimod</groupId> <artifactId>podprojekt1</artifactId> <version>1.0-SNAPSHOT</version> </project>
- Podprojekty można normalnie budować i uruchamiać na ich rzecz różne fazy Mavena i pluginy, ale możemy także zbiorczo zbudować wszystkie z poziomu projektu nadrzędnego.
- Konfigurując pluginy i zależności w nadrzędnym pliku pom.xml mamy dwie możliwości:
- możemy tak skonfigurować pluginy i/lub zależności, aby zawsze były dziedziczone przez podmoduły,
- możemy skonfigurować pluginy i/lub zależności, ale nie będą one automatycznie dziedziczone przez podprojekty – każdy podprojekt, który będzie chciał tą konfigurację odziedziczyć z pliku pom.xml-rodzica, będzie musiał ten plugin/zależność umieścić w swoim pliku pom.xml. Taki rodzaj konfiguracji zawarty jest w dodatkowym elemencie w pliku pom.xml: dla pluginów jest to element <pluginManagement>, a dla zależności – <dependencyManagement>.
- Czasem możemy mieć potrzebę sprawdzić, jak wygląda finalny plik pom.xml np. w jednym z podprojektów w projekcie wielomodułowym. Możemy wtedy skorzystać z komendy mvn help:effective-pom, która zwróci zawartość pliku pom.xml projektu uwzględniając:
- domyślne ustawienia Mavena dla projektów,
- ustawienia odziedziczone z nadrzędnego pliku pom.xml.
Bardzo dziękuje za ten kurs, akurat mam w pracy projekt wielomodułowy i potrzebna jest szersza wiedza z mavena niz zwykłe zarządzanie zależnościami. Ten kurs spełnia oczekiwania w pełni. Dziękuje jeszcze raz 🙂
Dziękuję, cieszę się, że kurs się przydaje 🙂