Со временем все программные проекты разрастаются. То, что начиналось как довольно жирный Hello World, весьма скоро обзаводится отдельным фронтендом, парочкой batch процессов, тремя видами RPC и общим кодом доступа к данным. И вот, в какой-то момент времени, возникает желание распилить этого монстра на неколько раздельных maven проектов, которые будут существовать независимо друг от друга.
Однако на пути к светлому многоартефактному будущему имеются некоторые препятствия — артефакты имеют зависимости друг от друга, требуют использования одной и той же версии какой-то библиотеки, должны собираться все вместе и так далее. К счастью, в maven есть механизм для автоматического решения этих проблем — многомодульные проекты.
Многомодульный проект проще всего представить себе как дерево — у него есть общий корень, который ничего не делает, а лишь описывает общие параметры, и листья, которые наследуют эти общие параметры. Листья могут иметь свои листья и так далее, пока память не кончится 🙂
Родительский модуль
Родительский модуль состоит из одного лишь pom файла, в котором описаны его дочерние модули:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | <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>ru.easyjava.maven</groupId> <artifactId>multi</artifactId> <packaging>pom</packaging> <version>1</version> <name>multi</name> <url>http://maven.apache.org</url> <modules> <module>targets</module> <module>greeter</module> </modules> <dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.3</version> <configuration> <source>1.8</source> <target>1.8</target> <encoding>UTF-8</encoding> </configuration> </plugin> </plugins> </build> </project> |
В родительском pom есть две важные вещи — он должен определять <packaging/> как pom и должен перечислять дочерние модули. Всё остальное — как у обычного проекта: зависимости, плагины и так далее. Все настройки сборки, определённые в родительстком модуле, будут автоматически наследоваться дочерними.
Дочерний модуль
Дочерний модуль так же обязательно имеет свой pom файл и может как иметь код, так и быть родительским модулем для своих подмодулей. В моём случае это будет модуль с кодом.
1 2 3 4 5 6 7 8 9 10 11 12 | <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> <parent> <groupId>ru.easyjava.maven</groupId> <artifactId>multi</artifactId> <version>1</version> </parent> <artifactId>targets</artifactId> <packaging>jar</packaging> </project> |
Дочерний модуль должен обязательно ссылаться на родительский модуль с помощью элемента <parent> Всё остальное — как у обычного maven проекта. В моём модуле будет один простой класс и тест к нему:
1 2 3 4 | public class GreetingTarget { public static String getTarget() { return "Modules"; } } |
1 2 3 4 5 6 | public class GreetingTargetTest { @Test public void testGreet() { assertThat(GreetingTarget.getTarget(), is("Modules")); } } |
Обратите внимание, что в коде класса теста используется JUnit, зависимость от которого объявляется в родительском модуле.
Зависимости между дочерними модулями
Иметь многомодульный проект из только одного модуля — грустно. Поэтому давайте напишем класс, который будет использовать уже существующие классы и поместим этот класс в новый модуль:
1 2 3 4 5 6 | public class Greeter { public static void main(String[] args) { System.out.println(String.format("Hello, %s!", GreetingTarget.getTarget())); } } |
Итак, теперь у нас есть два модуля и один зависит от другого. Такую зависимость можно описать в pom файле второго модуля:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 | <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> <parent> <groupId>ru.easyjava.maven</groupId> <artifactId>multi</artifactId> <version>1</version> </parent> <artifactId>greeter</artifactId> <packaging>jar</packaging> <dependencies> <dependency> <groupId>ru.easyjava.maven</groupId> <artifactId>targets</artifactId> <version>1</version> </dependency> </dependencies> <build> <plugins> <plugin> <artifactId>maven-assembly-plugin</artifactId> <configuration> <archive> <manifest> <mainClass>ru.easyjava.maven.Greeter</mainClass> </manifest> </archive> <descriptorRefs> <descriptorRef>jar-with-dependencies</descriptorRef> </descriptorRefs> </configuration> <executions> <execution> <id>make-assembly</id> <phase>package</phase> <goals> <goal>single</goal> </goals> </execution> </executions> </plugin> </plugins> </build> </project> |
Теперь maven знает, что перед сборкой второго модуля надо собрать первый и результаты сборки сделать доступными второму. Maven так же будет сам следить за изменениями в первом модуле и пересобирать второй по мере надобности. Стоит отметить, что если maven не может выявить зависимости между модулями, то он их будет собирать в том порядке, в котором они перечислены в элементе <modules/>.
Так второй модуль зависит от первого, это надо учесть и при сборке финального jar файла, поэтому я добавляю плагин maven-assembly-plugin, тем самым переопределяя унаследованные от родительского модуля настройки. Запуск готового приложения подтверждает корректность сборки:
1 2 | MultiModule\greeter\target>java -jar greeter-1-jar-with-dependencies.jar Hello, Modules! |
Код примера доступен на github