что такое многомодульный проект
Многомодульный maven проект
Maven позволяет собирать проект из нескольких модулей. Каждый программный модуль включает свой проектный файл pom.xml. Один из проектных pom.xml файлов является корневым. Корневой pom.xml позволяет объединить все модули в единый проект. При этом в корневой проектный файл можно вынести общие для всех модулей свойства. А каждый модульный pom.xml должен включать параметры GAV (groupId, artifactId, version) корневого pom.xml.
Общие положения разработки многомодульного maven-приложения рассмотрены на странице Наследование проектов в maven. В данной статье рассмотрим пример сборки многомодульного приложения. В качестве «подопытного» приложения используем пример, представленный на странице Pluggable решение. На выходе данного примера мы должны получить 3 архивных и один исполняемый jar-файлов. Главный исполняемый jar-модуль динамически «при необходимости» загружает остальные архивные jar’ники. Данный «подопытный» пример был использован для «оборачивания» jar’ника в exe-файл с использованием maven-плагина launch4j.
Описание многомодульного проекта
На скриншоте представлена структура проекта pluggable, включающая следующие проектные модули :
• hello-plugin1 | – динамически загружаемый плагин №1 (hello1.jar); |
• hello-plugin2 | – динамически загружаемый плагин №2 (hello2.jar); |
• plugin-api | – интерфейсы описания плагинов (plugin-api.jar); |
• plugin-loader | – главный исполняемый jar модуль. |
Дополнительные поддиректории проекта, используемые для размещения jar-модулей :
• commons | – поддиректория размещения архивного jar-модуля описания интерфейса плагинов; |
• plugins | – поддиректория размещения jar-модулей (плагинов); |
Главный исполняемый модуль plugin-loader.jar размещается в корневой директории проекта, где размещается и проектный/корневой pom.xml. Файл run.bat можно использовать для старта plugin-loader.jar из консоли в Windows.
Примечание : в исходные коды классов внесены изменения, связанные с из размещением в пакетах. В исходном примере все классы располагаются в «корне».
Начнем рассмотрение примера с корневого многомодульного pom.xml.
Листинг многомодульного корневого pom.xml
Корневой pom.xml включает параметры GAV (groupId, artifactId, Version), общую для всех модулей проекта секцию
Следует отметить, что порядок включения программных модулей проекта составлен таким образом, что сначала представлены исполняемый модуль plugin-loader.jar и плагины (hello-plugin1.jar, hello-plugin2.jar), после чего следует интерфейсный модуль plugin-api.jar. Если собирать проект по-отдельности, то модуль plugin-api.jar должен быть собран в первую очередь и размещен в репозитории командой «mvn install». В этом случае зависимые модули plugin-loader.jar и плагины (hello-plugin1, hello-plugin2) собрались бы нормально. Ну, а мы в этом примере посмотрим, как поступит Maven в случае, если порядок описания модулей для сборки «нарушен».
Модуль описания интерфейсов плагинов plugin-api.jar
На следующем скриншоте представлена структура проекта plugin-api. Интерфейсные классы Plugin, PluginContext располагаются в пакете «ru.plugin».
Листинг pom.xml
Проектный pom.xml модуля plugin-api.jar включает GAV-параметры, секцию описания родительского GAV (
Модуль описания плагина hello-plugin1.jar
Структура проекта hello-plugin1 представлена на следующем скриншоте.
Класс HelloPlugin, расположенный в пакете «ru.plugins», реализует свойства интерфейса Plugin. При инициализации класса в методе init определяется значение контекста PluginContext родительского/вызвавшего объекта. Метод invoke выводит в консоль сообщение и изменяет надпись на кнопке родительского объекта.
Листинг pom.xml
Проектный pom.xml модуля hello-plugin1.jar включает GAV-параметры, секцию описания родительского GAV (
Примечание : второй плагин hello-plugin2 структурно ничем не отличается от hello-plugin1. Отличия касаются текста сообщения в консоли, надписи на кнопке и параметров GAV в pom.xml.
Проектный pom.xml модуля plugin-loader
Проектный pom.xml включает GAV-параметры jar-модуля, секцию описания родительского GAV (
Многомодульный Java-проект с Gradle. Шаг за шагом
Очень много статей о Gradle написано. И со своей стороны хотелось бы добавить в копилку такую пошаговую инструкцию, прочтение которой, я надеюсь, позволит тем, кто плохо знаком с Gradle, “распробовать” и продолжить самостоятельно изучать этот инструмент.
Данная статья не будет подробно описывать такие темы, как плагины gradle (plugin), задачи (task), зависимости (dependencies), автоматическое тестирование и прочие прелести этого сборщика проектов. Во-первых, каждая тема заслуживает отдельной статьи или даже серии статей, а во-вторых, на эти темы уже есть статьи на хабре, например: Gradle: Tasks Are Code, Gradle: Better Way To Build. А еще на официальном сайте Gradle есть прекрасно написанный Gradle User Guide. Я же cфокусирую внимание на непосредственном решении поставленной задачи, и все сопутствующие темы будут описаны в рамках этой самой задачи.
Сначала определимся с целью, что же мы хотим получить на выходе? А цель указана в заголовке статьи. Мы хотим получить проект с несколькими модулями, который собирается с помощью Gradle. И так, приступим.
Шаг 1. Установка gradle
Примечение: Если выхотите просто “поиграть” с gradle, скачав файлы для статьи, или вам достались чужие исходники с волшебным файлом gradlew (gradlew.bat) в корне проекта, то устанавливать gradle не обязательно.
Gradle можно поставить, скачав последнюю версию со страницы загрузок Gradle или воспользовавшись менеджером пакетов в вашей любимой ОС (прим. Я ставил на Mac OS через brew и на Debian через apt-get из стандартных источников)
Результат первого шага:
Шаг 2. Пустой проект, плагины (plugin), обертка (wrapper)
Создадим папку проекта и в ее корне сделаем файл build.gradle со следующим содержимым:
Итоги второго шага (вывод сокращен):
Шаг 3. Заполняем пробелы
Для сравнения аналогичный блок в maven:
Итоги третьего шага:
Видно, что скачивается недостающая библиотека, и продемонстрировано ее использование.
Шаг 4. Достижение цели
Дополнение от MiniM: В gradle символ «:» используется вместо «/» и для более ветвистой структуры ссылки на проект могут выглядеть так «:loaders:xml-loader»
Итог четвертого шага:
Шаг 5 (заключительный). Убираем мусор
Основная цель достигнута, но на данном этапе могли возникнуть вполне закономерные вопросы о дублировании информации в build файлах, более глубокой настройке gradle, а также о том, что изучать дальше. Для самостоятельного изучения, я советую ознакомиться с содержимым ссылок в конце статьи. А пока, давайте приведем в порядок наши build файлы, создав build.gradle в корне проекта и изменив содержимое остальных build файлов
На этом я закончу. Надеюсь, данная статья вызвала интерес у людей, не знакомых с Gradle, и побудила к более подробному изучению и последующему использованию в своих проектах этого инструмента.
Многомодульные проекты maven
Со временем все программные проекты разрастаются. То, что начиналось как довольно жирный Hello World, весьма скоро обзаводится отдельным фронтендом, парочкой batch процессов, тремя видами RPC и общим кодом доступа к данным. И вот, в какой-то момент времени, возникает желание распилить этого монстра на неколько раздельных maven проектов, которые будут существовать независимо друг от друга.
Однако на пути к светлому многоартефактному будущему имеются некоторые препятствия — артефакты имеют зависимости друг от друга, требуют использования одной и той же версии какой-то библиотеки, должны собираться все вместе и так далее. К счастью, в maven есть механизм для автоматического решения этих проблем — многомодульные проекты.
Многомодульный проект проще всего представить себе как дерево — у него есть общий корень, который ничего не делает, а лишь описывает общие параметры, и листья, которые наследуют эти общие параметры. Листья могут иметь свои листья и так далее, пока память не кончится 🙂
Родительский модуль
Родительский модуль состоит из одного лишь pom файла, в котором описаны его дочерние модули:
Многомодульный проект с Maven
1. Обзор
В этом руководстве мы покажем, как создать многомодульный проект с Maven.
Сначала мы обсудим, что такое многомодульный проект, и рассмотрим преимущества использования этого подхода. Затем мы настроим наш образец проекта. Чтобы получить хорошее представление о Maven, ознакомьтесь с этим руководством.
2. Многомодульный проект Maven
3. Преимущества использования мультимодулей
Существенным преимуществом использования этого подхода является то, что мы можем уменьшить дублирование.
Допустим, у нас есть приложение, которое состоит из нескольких модулей, пусть это будет интерфейсный модуль и серверный модуль. Теперь мы работаем над обоими и меняем функциональность, которая влияет на них обоих. В этом случае без специального инструмента сборки нам придется собрать оба компонента по отдельности или написать скрипт, который скомпилирует код, запустит тесты и покажет результаты. Затем, когда мы получим еще больше модулей в проекте, им станет труднее управлять и поддерживать.
Кроме того, в реальном мире проектам могут потребоваться определенные плагины Maven для выполнения различных операций во время жизненного цикла сборки, совместного использования зависимостей и профилей или включения других проектов спецификации.
4. Родительский POM
Maven поддерживает наследование таким образом, что каждый файл pom.xml имеет неявный родительский POM, он называется Super POM и может находиться в двоичных файлах Maven. Эти два файла объединены Maven и образуют эффективный POM.
Помимо наследования, Maven предоставляет понятие агрегирования. Родительский POM, использующий эту функцию, называется агрегированным POM . По сути, этот тип POM явно объявляет свои модули в файле pom.xml.
5. Подмодули
6. Сборка приложения
Теперь, когда мы понимаем подмодули и иерархию Maven, давайте создадим пример приложения, чтобы продемонстрировать их. Мы будем использовать интерфейс командной строки Maven для создания наших проектов.
Это приложение будет состоять из трех модулей, которые будут представлять:
Поскольку мы сосредоточимся на Maven, реализация этих сервисов останется неопределенной.
6.1. Создание родительского POM
Сначала создадим родительский проект :
Как только родительский элемент создан, мы должны открыть файл pom.xml, расположенный в родительском каталоге, и изменить упаковку на pom.
Теперь, когда наш агрегатор готов, мы можем сгенерировать наши подмодули.
6.2. Создание подмодулей
Теперь наш родительский объект явно объявляет агрегированные модули.
Затем при запуске команды mvn package в родительском каталоге проекта Maven соберет и протестирует все три модуля.
В конце концов, если мы хотим поделиться всей конфигурацией с нашими подмодулями, в их файлах pom.xml нам нужно будет объявить родительский элемент :
Отметим, что у подмодулей может быть только один родитель. Однако мы можем импортировать множество спецификаций. Более подробную информацию о файлах BOM можно найти в этой статье.
6.3. Создание проекта
Теперь мы можем собрать сразу все три модуля. В каталоге родительского проекта запустите:
Это построит все модули, мы должны увидеть следующий вывод команды:
The Reactor lists the parent-project, but as it’s pom type it’s excluded and the build results in three separate .jar files for all other modules. In that case, build occurs in three of them.
7. Conclusion
In this tutorial, we discussed the benefits of using Maven multi-modules. Also, we distinguished between regular Maven’s parent POM and aggregate POM. In the end, we showed how to set up a simple multi-module to start to play with.
Maven is a great tool but it is complex on its own. If you’d like to find more details about Maven, have a look at the Sonatype Maven reference or Apache Maven guides. If you seek advanced usages of Maven multi-modules set up, have a look how Spring Boot project leverages the usage of it.
Все примеры кода на Baeldung созданы с использованием Maven, поэтому вы можете легко проверить наш веб-сайт проекта GitHub, чтобы увидеть различные конфигурации Maven.
Конфигурация многомодульных проектов
Предыстория
Иногда, когда я прокрастинирую, я занимаюсь уборкой: чищу стол, раскладываю вещи, прибираюсь в комнате. По сути, привожу окружающую среду в порядок — это заряжает энергией и настраивает на рабочий лад. С программированием у меня та же ситуация, только я чищу проект: провожу рефакторинги, делаю различные инструменты и всячески стараюсь упростить жизнь себе и коллегам.
Некоторое время назад мы в команде Android решили сделать один из наших проектов — Кошелек — многомодульным. Это привело как к ряду преимуществ, так и проблем, одна из которых — необходимость конфигурировать каждый модуль заново. Конечно, можно просто копировать конфигурацию из модуля в модуль, но если мы захотим что-то поменять, то придется перебрать все модули.
Мне это не нравится, команде это не нравится, и вот какие шаги мы предприняли, чтобы упростить нашу жизнь и сделать конфигурации проще в сопровождении.
Первая итерация — вынос версий библиотек
На самом деле это уже было в проекте до меня, и вы, возможно, знаете этот подход. Я часто вижу, как разработчики пользуются им.
Что мне нравится в этом подходе: он крайне простой и помогает версиям не разъезжаться. Но у него есть минусы: надо следить, чтобы разработчики использовали версии из этого набора, и это не сильно упрощает создание новых модулей, потому что всё равно приходится копировать еще много чего.
Вторая итерация — project.subprojects
Моя любознательность, помноноженная на нежелание копировать код и разбираться с настройкой каждого модуля, привела меня к следующему шагу: я вспомнил, что в корневом build.gradle есть блок, который генерируется по умолчанию — allprojects.
Я сходил в документацию и нашел, что в него можно передать блок кода, который будет конфигурировать этот проект и все вложенные проекты. Но это не совсем то, что было нужно, поэтому я пролистал дальше и нашел subprojects — метод для конфигурации сразу всех вложенных проектов. Пришлось добавить немного проверок, и вот что получилось.
Теперь для любого модуля с подключенным плагином com.android.application или com.android.library мы можем настраивать что угодно: подключаемые плагины, конфигурации плагинов, зависимости.
Все было бы отлично, если бы не пара проблем: если мы захотим в модуле переопределить какие-то параметры, заданные в subprojects, то у нас это не получится, потому что конфигурация модуля происходит до применения subprojects (спасибо afterEvaluate). А еще если мы захотим не применять это автоматическое конфигурирование в отдельных модулях, то в блоке subprojects начнет появляться много дополнительных проверок. Поэтому я стал думать дальше.
Третья итерация — buildSrc и plugin
До этого момента я уже несколько раз слышал про buildSrc и видел примеры, в которых buildSrc использовали как альтернативу первому шагу из этой статьи. А еще я слышал про gradle plugin’ы, поэтому стал копать в этом направлении. Все оказалось очень просто: у Gradle есть документация по разработке кастомных плагинов, в которой все написано.
Немного разобравшись, я сделал плагин, который может настраивать все что нужно с возможностью изменять при необходимости.
Теперь конфигурация нового проекта выглядит как apply plugin: ‘ru.yandex.money.module’ и все. Можно вносить свои дополнения в блок android или dependencies, можно добавлять плагины или настраивать их, но главное, что новый модуль конфигурируется одной строкой, а его конфигурация всегда актуальна и продуктовому разработчику больше не надо думать про настройку.
Из минусов я бы отметил то, что для этого решения нужны дополнительное время и изучение материала, но, с моей точки зрения, оно того стоит. Если вы захотите в будущем выносить плагин, как отдельный проект, то я бы не рекомендовал настраивать зависимости между модулями в плагине.
Важный момент: если вы используете android gradle plugin ниже 4.0, то некоторые вещи очень сложно сделать в kotlin-скриптах — по крайней мере, блок android проще конфигурировать в groovy-скриптах. Там есть проблема с тем, что некоторые типы недоступны при компиляции, а groovy — динамически типизированный, и ему это не важно =)
Дальше — standalone plugin или монорепо
Конечно же, третий шаг — это еще не всё. Нет предела совершенству, поэтому есть варианты, куда двигаться дальше.
Первый вариант — standalone plugin для gradle. После третьего шага это уже не так сложно: надо создать отдельный проект, перенести туда код и настроить публикацию.
Плюсы: плагин можно шарить между несколькими проектами, что упростит жизнь не в одном проекте, а в экосистеме.
Минусы: версионирование — при обновлении плагина придется обновлять и проверять его работоспособность в нескольких проектах сразу, а это может занять время. Кстати, на эту тему у моих коллег из бэкенд-разработки есть отличное решение, ключевое слово — modernizer — инструмент, который сам ходит по репозиториям и обновляет зависимости. Не буду на этом долго задерживаться, пусть лучше они сами расскажут.
Монорепо — это звучит громко, но у меня нет опыта работы с ним, а есть только соображения, что один проект, вроде buildSrc, можно использовать сразу в нескольких других проектах, и это могло бы помочь решить вопрос с версионированием. Если вдруг у тебя есть опыт работы с монорепо, то поделись в комментариях, чтобы я и другие читатели могли что-то узнать про это.
Итого
В новом проекте делай сразу третий шаг — buildSrc и plugin — проще будет всем, тем более, что код я приложил. А второй шаг — project.subprojects — используй для того, чтобы подключать общие модули между собой.
Если у тебя есть что добавить или возразить, пиши в комментарии или ищи меня в соцсетях.