Перейти к основному содержимому

Многомодульное приложение Maven с модулями Java

· 8 мин. чтения

1. Обзор

Система Java Platform Module System (JPMS) обеспечивает большую надежность, лучшее разделение задач и более надежную инкапсуляцию в Java-приложениях. Однако это не инструмент сборки, поэтому ему не хватает возможности автоматического управления зависимостями проекта.

Конечно, мы можем задаться вопросом, можем ли мы использовать хорошо зарекомендовавшие себя инструменты сборки, такие как Maven или Gradle , в модульных приложениях.

На самом деле, мы можем! В этом руководстве мы узнаем, как создать многомодульное приложение Maven с использованием модулей Java .

2. Инкапсуляция модулей Maven в модули Java

Поскольку модульность и управление зависимостями не являются взаимоисключающими понятиями в Java, мы можем легко интегрировать JPMS , например, с Maven, используя лучшее из обоих миров.

В стандартный многомодульный проект Maven мы добавляем один или несколько дочерних модулей Maven, помещая их в корневую папку проекта и объявляя их в родительском POM в разделе <modules> .

В свою очередь, мы редактируем POM каждого дочернего модуля и указываем его зависимости через стандартные координаты < groupId> , < artifactId> и < version> .

Механизм реактора в Maven, отвечающий за работу с многомодульными проектами, заботится о построении всего проекта в правильном порядке.

В этом случае мы в основном будем использовать ту же методологию проектирования, но с одним тонким, но фундаментальным вариантом: мы обернем каждый модуль Maven в модуль Java, добавив к нему файл дескриптора модуля , module-info.java .

3. Родительский модуль Maven

Чтобы продемонстрировать, как модульность и управление зависимостями прекрасно работают вместе, мы создадим базовый демонстрационный многомодульный проект Maven, функциональность которого будет сужена до простого извлечения некоторых объектов предметной области из уровня постоянства .

Чтобы не усложнять код, мы будем использовать обычную карту в качестве базовой структуры данных для хранения объектов предметной области. Конечно, в дальнейшем мы можем легко перейти на полноценную реляционную базу данных.

Начнем с определения родительского модуля Maven. Для этого создадим корневую директорию проекта с именем, например, multimodulemavenproject (но это может быть что угодно) и добавим в нее родительский файл pom.xml :

<groupId>com.foreach.multimodulemavenproject</groupId>
<artifactId>multimodulemavenproject</artifactId>
<version>1.0</version>
<packaging>pom</packaging>
<name>multimodulemavenproject</name>

<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.0</version>
<configuration>
<source>11</source>
<target>11</target>
</configuration>
</plugin>
</plugins>
</pluginManagement>
</build>

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>

В определении родительского POM стоит отметить несколько деталей.

Во- первых, поскольку мы используем Java 11, нам потребуется по крайней мере Maven 3.5.0 в нашей системе , так как Maven поддерживает Java 9 и выше, начиная с этой версии .

И нам также понадобится как минимум версия 3.8.0 плагина компилятора Maven . Поэтому давайте обязательно проверим последнюю версию плагина на Maven Central .

4. Модули Child Maven

Обратите внимание, что до этого момента родительский POM не объявлял никаких дочерних модулей .

Поскольку наш демонстрационный проект будет извлекать некоторые объекты предметной области из уровня сохраняемости, мы создадим четыре дочерних модуля Maven:

  1. entitymodule : будет содержать простой класс предметной области
  2. daomodule : будет содержать интерфейс, необходимый для доступа к слою сохраняемости (базовый контракт DAO )
  3. userdaomodule : будет включать реализацию интерфейса daomodule
  4. mainappmodule : точка входа в проект

4.1. Модуль Maven entitymodule

Теперь давайте добавим первый дочерний модуль Maven, который включает только базовый класс предметной области.

В корневом каталоге проекта давайте создадим структуру каталогов entitymodule/src/main/java/com/foreach/entity и добавим класс User :

public class User {

private final String name;

// standard constructor / getter / toString

}

Далее давайте подключим файл модуля pom.xml :

<parent>
<groupId>com.foreach.multimodulemavenproject</groupId>
<artifactId>multimodulemavenproject</artifactId>
<version>1.0</version>
</parent>

<groupId>com.foreach.entitymodule</groupId>
<artifactId>entitymodule</artifactId>
<version>1.0</version>
<packaging>jar</packaging>
<name>entitymodule</name>

Как мы видим, модуль Entity не имеет никаких зависимостей от других модулей и не требует дополнительных артефактов Maven, так как включает только класс User .

Теперь нам нужно инкапсулировать модуль Maven в модуль Java . Для этого просто поместим следующий файл дескриптора модуля ( module-info.java ) в каталог entitymodule/src/main/java :

module com.foreach.entitymodule {
exports com.foreach.entitymodule;
}

Наконец, давайте добавим дочерний модуль Maven в родительский POM:

<modules>
<module>entitymodule</module>
</modules>

4.2. Модуль даомодуля Maven

Давайте создадим новый модуль Maven, который будет содержать простой интерфейс. Это удобно для определения абстрактного контракта для выборки универсальных типов из уровня постоянства.

На самом деле есть очень веская причина поместить этот интерфейс в отдельный модуль Java. Таким образом, у нас есть абстрактный контракт с высокой степенью развязки, который легко использовать повторно в разных контекстах. По сути, это альтернативная реализация принципа инверсии зависимостей , которая дает более гибкий дизайн.

Поэтому давайте создадим структуру каталогов daomodule/src/main/java/com/foreach/dao в корневом каталоге проекта и добавим в нее интерфейс Dao<T> :

public interface Dao<T> {

Optional<T> findById(int id);

List<T> findAll();

}

Теперь давайте определим файл модуля pom.xml :

<parent>
// parent coordinates
</parent>

<groupId>com.foreach.daomodule</groupId>
<artifactId>daomodule</artifactId>
<version>1.0</version>
<packaging>jar</packaging>
<name>daomodule</name>

Новый модуль также не требует других модулей или артефактов, поэтому мы просто завершим его в модуль Java. Давайте создадим дескриптор модуля в каталоге daomodule/src/main/java :

module com.foreach.daomodule {
exports com.foreach.daomodule;
}

Наконец, давайте добавим модуль в родительский POM:

<modules>
<module>entitymodule</module>
<module>daomodule</module>
</modules>

4.3. Модуль userdaomodule Maven

Далее давайте определим модуль Maven, содержащий реализацию интерфейса Dao .

В корневом каталоге проекта создадим структуру каталогов userdaomodule/src/main/java/com/foreach/userdao и добавим в нее следующий класс UserDao :

public class UserDao implements Dao<User> {

private final Map<Integer, User> users;

// standard constructor

@Override
public Optional<User> findById(int id) {
return Optional.ofNullable(users.get(id));
}

@Override
public List<User> findAll() {
return new ArrayList<>(users.values());
}
}

Проще говоря, класс UserDao предоставляет базовый API, который позволяет нам извлекать объекты User из уровня постоянства.

Для простоты мы использовали карту в качестве резервной структуры данных для сохранения объектов предметной области. Конечно, можно обеспечить более тщательную реализацию, использующую, например, менеджер сущностей Hibernate .

Теперь давайте определим POM модуля Maven:

<parent>
// parent coordinates
</parent>

<groupId>com.foreach.userdaomodule</groupId>
<artifactId>userdaomodule</artifactId>
<version>1.0</version>
<packaging>jar</packaging>
<name>userdaomodule</name>

<dependencies>
<dependency>
<groupId>com.foreach.entitymodule</groupId>
<artifactId>entitymodule</artifactId>
<version>1.0</version>
</dependency>
<dependency>
<groupId>com.foreach.daomodule</groupId>
<artifactId>daomodule</artifactId>
<version>1.0</version>
</dependency>
</dependencies>

В этом случае все немного по-другому, так как для модуля userdaomodule требуются модули entitymodule и daomodule . Вот почему мы добавили их как зависимости в файл pom.xml .

Нам все еще нужно инкапсулировать этот модуль Maven в модуль Java. Итак, давайте добавим следующий дескриптор модуля в каталог userdaomodule/src/main/java :

module com.foreach.userdaomodule {
requires com.foreach.entitymodule;
requires com.foreach.daomodule;
provides com.foreach.daomodule.Dao with com.foreach.userdaomodule.UserDao;
exports com.foreach.userdaomodule;
}

Наконец, нам нужно добавить этот новый модуль в родительский POM:

<modules>
<module>entitymodule</module>
<module>daomodule</module>
<module>userdaomodule</module>
</modules>

С точки зрения высокого уровня легко увидеть, что файл pom.xml и дескриптор модуля играют разные роли . Тем не менее, они прекрасно дополняют друг друга.

Допустим, нам нужно обновить версии артефактов Maven entitymodule и daomodule . Мы можем легко сделать это, не изменяя зависимости в дескрипторе модуля. Maven позаботится о том, чтобы включить для нас нужные артефакты.

Точно так же мы можем изменить реализацию службы, которую предоставляет модуль, изменив директиву «provides..with» в дескрипторе модуля.

Мы много выигрываем, когда используем модули Maven и Java вместе. Первый обеспечивает функциональность автоматического централизованного управления зависимостями, а второй предоставляет внутренние преимущества модульности .

4.4. Модуль mainappmodule Maven

Кроме того, нам нужно определить модуль Maven, содержащий основной класс проекта.

Как и раньше, давайте создадим структуру каталогов mainappmodule/src/main/java/mainapp в корневом каталоге и добавим в нее следующий класс Application :

public class Application {

public static void main(String[] args) {
Map<Integer, User> users = new HashMap<>();
users.put(1, new User("Julie"));
users.put(2, new User("David"));
Dao userDao = new UserDao(users);
userDao.findAll().forEach(System.out::println);
}
}

Метод main() класса Application довольно прост. Во-первых, он заполняет HashMap парой объектов User . Затем он использует экземпляр UserDao для извлечения их из карты, а затем отображает их на консоли. ``

Кроме того, нам также необходимо определить файл модуля pom.xml :

<parent>
// parent coordinates
</parent>

<groupId>com.foreach.mainappmodule</groupId>
<artifactId>mainappmodule</artifactId>
<version>1.0</version>
<packaging>jar</packaging>
<name>mainappmodule</name>

<dependencies>
<dependency>
<groupId>com.foreach.entitymodule</groupId>
<artifactId>entitymodule</artifactId>
<version>1.0</version>
</dependency>
<dependency>
<groupId>com.foreach.daomodule</groupId>
<artifactId>daomodule</artifactId>
<version>1.0</version>
</dependency>
<dependency>
<groupId>com.foreach.userdaomodule</groupId>
<artifactId>userdaomodule</artifactId>
<version>1.0</version>
</dependency>
</dependencies>

Зависимости модуля говорят сами за себя. Итак, нам просто нужно поместить модуль внутри модуля Java. Поэтому в структуре каталогов mainappmodule/src/main/java давайте включим дескриптор модуля:

module com.foreach.mainappmodule {
requires com.foreach.entitypmodule;
requires com.foreach.userdaopmodule;
requires com.foreach.daopmodule;
uses com.foreach.daopmodule.Dao;
}

Наконец, давайте добавим этот модуль в родительский POM:

<modules>
<module>entitymodule</module>
<module>daomodule</module>
<module>userdaomodule</module>
<module>mainappmodule</module>
</modules>

Со всеми дочерними модулями Maven, которые уже установлены и аккуратно инкапсулированы в модули Java, вот как выглядит структура проекта:

multimodulemavenproject (the root directory)
pom.xml
|-- entitymodule
|-- src
|-- main
| -- java
module-info.java
|-- com
|-- foreach
|-- entity
User.class
pom.xml
|-- daomodule
|-- src
|-- main
| -- java
module-info.java
|-- com
|-- foreach
|-- dao
Dao.class
pom.xml
|-- userdaomodule
|-- src
|-- main
| -- java
module-info.java
|-- com
|-- foreach
|-- userdao
UserDao.class
pom.xml
|-- mainappmodule
|-- src
|-- main
| -- java
module-info.java
|-- com
|-- foreach
|-- mainapp
Application.class
pom.xml

5. Запуск приложения

Наконец, давайте запустим приложение либо из нашей IDE, либо из консоли.

Как и следовало ожидать, мы должны увидеть пару объектов User , выведенных на консоль при запуске приложения:

User{name=Julie}
User{name=David}

6. Заключение

В этом руководстве мы прагматично узнали, как заставить Maven и JPMS работать бок о бок при разработке базового многомодульного проекта Maven, в котором используются модули Java .

Как обычно, все примеры кода, показанные в этом руководстве, доступны на GitHub .