1. Overview
1.概述
The Java Platform Module System (JPMS) adds more reliability, better separation of concerns, and stronger encapsulation to Java applications. However, it’s not a build tool, hence it lacks the ability for automatically managing project dependencies.
Java 平台模块系统(JPMS)为 Java 应用程序增加了更多的可靠性、更好的关注点分离和更强的封装。然而,它并不是一个构建工具,因此它缺乏自动管理项目依赖关系的能力。
Of course, we may wonder whether if we can use well-established build tools, like Maven or Gradle, in modularized applications.
当然,我们可能会想,是否可以在模块化应用中使用成熟的构建工具,如Maven或Gradle。
Actually, we can! In this tutorial, we’ll learn how to create a multi-module Maven application using Java modules.
事实上,我们可以在本教程中,我们将学习如何使用Java模块创建一个多模块Maven应用程序。
2. Encapsulating Maven Modules in Java Modules
2.在Java模块中封装Maven模块
Since modularity and dependency management are not mutually exclusive concepts in Java, we can seamlessly integrate the JPMS, for instance, with Maven, thus leveraging the best of both worlds.
由于模块化和依赖性管理在Java中并不是相互排斥的概念,我们可以将JPMS与Maven等无缝集成,从而利用两个世界的优点。
In a standard multi-module Maven project, we add one or more child Maven modules by placing them under the project’s root folder and declaring them in the parent POM, within the <modules> section.
在一个标准的多模块Maven项目中,我们添加一个或多个子Maven模块,将它们放在项目的根文件夹下,并在父POM的<modules>部分中声明。
In turn, we edit each child module’s POM and specify its dependencies via the standard <groupId>, <artifactId> and <version> coordinates.
反过来,我们编辑每个子模块的POM,并通过标准的<groupId>、<artifactId>和<version>坐标指定其依赖性。
The reactor mechanism in Maven — responsible for handling multi-module projects — takes care of building the whole project in the right order.
Maven中的reactor机制–负责处理多模块项目–负责按正确顺序构建整个项目。
In this case, we’ll basically use the same design methodology, but with one subtle yet fundamental variant: we’ll wrap each Maven module into a Java module by adding to it the module descriptor file, module-info.java.
在这种情况下,我们基本上会采用相同的设计方法,但有一个细微但基本的变化。我们将把每个Maven模块包装成一个Java模块,在其中加入模块描述符文件,module-info.java。
3. The Parent Maven Module
3.母体Maven模块
To demonstrate how modularity and dependency management work great together, we’ll build a basic demo multi-module Maven project, whose functionality will be narrowed to just fetching some domain objects from a persistence layer.
为了展示模块化和依赖性管理如何协同工作,我们将建立一个基本的多模块Maven演示项目,其功能将缩小到只从持久层中获取一些领域对象。
To keep the code simple, we’ll use a plain Map as the underlying data structure for storing the domain objects. Of course, we can easily switch further down the road to a fully-fledged relational database.
为了保持代码简单,我们将使用一个普通的Map作为底层数据结构来存储域对象。当然,我们可以很容易地进一步切换到一个完全成熟的关系型数据库。
Let’s start by defining the parent Maven module. To accomplish this, let’s create a root project directory called, for instance, multimodulemavenproject (but it could be anything else), and add to it the parent pom.xml file:
让我们从定义父Maven模块开始。为实现这一目标,我们创建一个根项目目录,例如,名为multimodulemavenproject(但也可以是其他什么),并在其中添加父模块pom.xml文件。
<groupId>com.baeldung.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>
There are a few details worth noting in the definition of the parent POM.
在母体POM的定义中,有几个细节值得注意。
First off, since we’re using Java 11, we’ll need at least Maven 3.5.0 on our system, as Maven supports Java 9 and higher from that version onward.
首先,由于我们使用的是Java 11,我们的系统至少需要Maven 3.5.0,因为Maven从该版本开始支持Java 9及以上版本。
And, we’ll also need at least version 3.8.0 of the Maven compiler plugin. Therefore, let’s make sure to check the latest version of the plugin on Maven Central.
而且,我们还需要至少3.8.0版本的Maven编译器插件。因此,我们要确保检查Maven Central上最新的插件版本。
4. The Child Maven Modules
4.儿童Maven模块
Notice that up to this point, the parent POM doesn’t declare any child modules.
注意,到此为止,父POM没有声明任何子模块。
Since our demo project will fetch some domain objects from the persistence layer, we’ll create four child Maven modules:
由于我们的演示项目将从持久层获取一些领域对象,我们将创建四个Maven子模块。
- entitymodule: will contain a simple domain class
- daomodule: will hold the interface required for accessing the persistence layer (a basic DAO contract)
- userdaomodule: will include an implementation of the daomodule‘s interface
- mainappmodule: the project’s entry point
4.1. The entitymodule Maven Module
4.1.entitymodule Maven模块
Now, let’s add the first child Maven module, which just includes a basic domain class.
现在,让我们添加第一个Maven模块,它只包括一个基本的域类。
Under the project’s root directory, let’s create the entitymodule/src/main/java/com/baeldung/entity directory structure and add a User class:
在项目的根目录下,让我们创建entitymodule/src/main/java/com/baeldung/entity目录结构并添加一个User类。
public class User {
private final String name;
// standard constructor / getter / toString
}
Next, let’s include the module’s pom.xml file:
接下来,让我们包括模块的pom.xml文件。
<parent>
<groupId>com.baeldung.multimodulemavenproject</groupId>
<artifactId>multimodulemavenproject</artifactId>
<version>1.0</version>
</parent>
<groupId>com.baeldung.entitymodule</groupId>
<artifactId>entitymodule</artifactId>
<version>1.0</version>
<packaging>jar</packaging>
<name>entitymodule</name>
As we can see, the Entity module doesn’t have any dependencies to other modules, nor does it require additional Maven artifacts, as it only includes the User class.
我们可以看到,Entity模块没有依赖其他模块,也不需要额外的Maven工件,因为它只包括User类。
Now, we need to encapsulate the Maven module into a Java module. To achieve this, let’s simply place the following module descriptor file (module-info.java) under the entitymodule/src/main/java directory:
现在,我们需要将Maven模块封装成一个Java模块。为实现这一目标,我们只需在entitymodule/src/main/java目录下放置以下模块描述符文件(module-info.java)。
module com.baeldung.entitymodule {
exports com.baeldung.entitymodule;
}
Finally, let’s add the child Maven module to the parent POM:
最后,让我们把子Maven模块添加到父POM中。
<modules>
<module>entitymodule</module>
</modules>
4.2. The daomodule Maven Module
4.2.daomodule Maven模块
Let’s create a new Maven module that will contain a simple interface. This is convenient for defining an abstract contract for fetching generic types from the persistence layer.
让我们创建一个新的Maven模块,它将包含一个简单的接口。这便于定义一个抽象的契约,从持久层获取通用类型。
As a matter of fact, there’s a very compelling reason to place this interface in a separate Java module. By doing so, we have an abstract, highly-decoupled contract, which is easy to reuse in different contexts. At the core, this is an alternative implementation of the Dependency Inversion Principle, which yields a more flexible design.
事实上,有一个非常令人信服的理由,那就是将这个接口放在一个单独的 Java 模块中。通过这样做,我们有了一个抽象的、高度解耦的契约,它很容易在不同的上下文中重用。核心是,这是依赖反转原则的替代实现,它产生了一个更灵活的设计。
Therefore, let’s create the daomodule/src/main/java/com/baeldung/dao directory structure under the project’s root directory, and add to it the Dao<T> interface:
因此,让我们在项目的根目录下创建daomodule/src/main/java/com/baeldung/dao目录结构,并在其中添加Dao<T>接口。
public interface Dao<T> {
Optional<T> findById(int id);
List<T> findAll();
}
Now, let’s define the module’s pom.xml file:
现在,我们来定义模块的pom.xml文件。
<parent>
// parent coordinates
</parent>
<groupId>com.baeldung.daomodule</groupId>
<artifactId>daomodule</artifactId>
<version>1.0</version>
<packaging>jar</packaging>
<name>daomodule</name>
The new module doesn’t require other modules or artifacts either, so we’ll just wrap it up into a Java module. Let’s create the module descriptor under the daomodule/src/main/java directory:
这个新模块也不需要其他模块或工件,所以我们就把它包装成一个Java模块。让我们在daomodule/src/main/java目录下创建模块描述符。
module com.baeldung.daomodule {
exports com.baeldung.daomodule;
}
Finally, let’s add the module to the parent POM:
最后,让我们把模块添加到父POM中。
<modules>
<module>entitymodule</module>
<module>daomodule</module>
</modules>
4.3. The userdaomodule Maven Module
4.3.userdaomodule Maven模块
Next, let’s define the Maven module that holds an implementation of the Dao interface.
接下来,让我们定义持有Dao接口实现的Maven模块。
Under the project’s root directory, let’s create the userdaomodule/src/main/java/com/baeldung/userdao directory structure, and add to it the following UserDao class:
在项目的根目录下,让我们创建userdaomodule/src/main/java/com/baeldung/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());
}
}
Simply put, the UserDao class provides a basic API that allows us to fetch User objects from the persistence layer.
简单地说,UserDao类提供了一个基本的API,允许我们从持久层获取User对象。
To keep things simple, we used a Map as the backing data structure for persisting the domain objects. Of course, it’s possible to provide a more thorough implementation that uses, for instance, Hibernate’s entity manager.
为了保持简单,我们使用了一个Map作为持久化域对象的支持数据结构。当然,我们也可以提供一个更彻底的实现,例如,使用Hibernate的实体管理器。
Now, let’s define the Maven module’s POM:
现在,我们来定义Maven模块的POM。
<parent>
// parent coordinates
</parent>
<groupId>com.baeldung.userdaomodule</groupId>
<artifactId>userdaomodule</artifactId>
<version>1.0</version>
<packaging>jar</packaging>
<name>userdaomodule</name>
<dependencies>
<dependency>
<groupId>com.baeldung.entitymodule</groupId>
<artifactId>entitymodule</artifactId>
<version>1.0</version>
</dependency>
<dependency>
<groupId>com.baeldung.daomodule</groupId>
<artifactId>daomodule</artifactId>
<version>1.0</version>
</dependency>
</dependencies>
In this case, things are slightly different, as the userdaomodule module requires the entitymodule and daomodule modules. That’s why we added them as dependencies in the pom.xml file.
在这种情况下,事情略有不同,因为userdaomodule模块需要entitymodule和daomodule模块。这就是为什么我们在pom.xml文件中把它们作为依赖项加入。
We still need to encapsulate this Maven module into a Java module. So, let’s add the following module descriptor under the userdaomodule/src/main/java directory:
我们仍然需要将这个Maven模块封装成一个Java模块。因此,让我们在userdaomodule/src/main/java目录下添加以下模块描述符。
module com.baeldung.userdaomodule {
requires com.baeldung.entitymodule;
requires com.baeldung.daomodule;
provides com.baeldung.daomodule.Dao with com.baeldung.userdaomodule.UserDao;
exports com.baeldung.userdaomodule;
}
Finally, we need to add this new module to the parent POM:
最后,我们需要将这个新模块添加到父级POM中。
<modules>
<module>entitymodule</module>
<module>daomodule</module>
<module>userdaomodule</module>
</modules>
From a high-level view, it’s easy to see that the pom.xml file and the module descriptor play different roles. Even so, they complement each other nicely.
从高层来看,我们很容易看到,pom.xml文件和模块描述符扮演着不同的角色。即便如此,它们也能很好地相互补充。
Let’s say that we need to update the versions of the entitymodule and daomodule Maven artifacts. We can easily do this without having to change the dependencies in the module descriptor. Maven will take care of including the right artifacts for us.
假设我们需要更新entitymodule和daomoduleMaven神器的版本。我们无需改变模块描述符中的依赖关系就能轻松做到这一点。Maven会负责为我们提供正确的工件。
Similarly, we can change the service implementation that the module provides by modifying the “provides..with” directive in the module descriptor.
同样,我们可以通过修改模块描述符中的“provides…with”指令来改变模块提供的服务实现。
We gain a lot when we use Maven and Java modules together. The former brings the functionality of automatic, centralized dependency management, while the latter provides the intrinsic benefits of modularity.
当我们同时使用Maven和Java模块时,我们会收获很多。前者带来了自动、集中的依赖管理功能,而后者则提供了模块化的内在好处。
4.4. The mainappmodule Maven Module
4.4.mainappmodule Maven模块
Additionally, we need to define the Maven module that contains the project’s main class.
此外,我们需要定义包含项目主类的Maven模块。
As we did before, let’s create the mainappmodule/src/main/java/mainapp directory structure under the root directory, and add to it the following Application class:
正如我们之前所做的,让我们在根目录下创建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);
}
}
The Application class’s main() method is quite simple. First, it populates a HashMap with a couple of User objects. Next, it uses a UserDao instance for fetching them from the Map, and then it displays them to the console.
Application类的main()方法相当简单。首先,它用几个User对象填充了一个HashMap。接下来,它使用一个UserDao实例从Map中获取它们,然后将它们显示到控制台。
In addition, we also need to define the module’s pom.xml file:
此外,我们还需要定义该模块的pom.xml文件。
<parent>
// parent coordinates
</parent>
<groupId>com.baeldung.mainappmodule</groupId>
<artifactId>mainappmodule</artifactId>
<version>1.0</version>
<packaging>jar</packaging>
<name>mainappmodule</name>
<dependencies>
<dependency>
<groupId>com.baeldung.entitymodule</groupId>
<artifactId>entitymodule</artifactId>
<version>1.0</version>
</dependency>
<dependency>
<groupId>com.baeldung.daomodule</groupId>
<artifactId>daomodule</artifactId>
<version>1.0</version>
</dependency>
<dependency>
<groupId>com.baeldung.userdaomodule</groupId>
<artifactId>userdaomodule</artifactId>
<version>1.0</version>
</dependency>
</dependencies>
The module’s dependencies are pretty self-explanatory. So, we just need to place the module inside a Java module. Therefore, under the mainappmodule/src/main/java directory structure, let’s include the module descriptor:
该模块的依赖性是不言自明的。所以,我们只需要把模块放在一个Java模块里。因此,在mainappmodule/src/main/java目录结构下,让我们包含模块描述符。
module com.baeldung.mainappmodule {
requires com.baeldung.entitypmodule;
requires com.baeldung.userdaopmodule;
requires com.baeldung.daopmodule;
uses com.baeldung.daopmodule.Dao;
}
Finally, let’s add this module to the parent POM:
最后,让我们把这个模块添加到父POM中。
<modules>
<module>entitymodule</module>
<module>daomodule</module>
<module>userdaomodule</module>
<module>mainappmodule</module>
</modules>
With all the child Maven modules already in place, and neatly encapsulated in Java modules, here’s how the project’s structure looks:
所有的Maven子模块已经就位,并被整齐地封装在Java模块中,以下是项目的结构图。
multimodulemavenproject (the root directory)
pom.xml
|-- entitymodule
|-- src
|-- main
| -- java
module-info.java
|-- com
|-- baeldung
|-- entity
User.class
pom.xml
|-- daomodule
|-- src
|-- main
| -- java
module-info.java
|-- com
|-- baeldung
|-- dao
Dao.class
pom.xml
|-- userdaomodule
|-- src
|-- main
| -- java
module-info.java
|-- com
|-- baeldung
|-- userdao
UserDao.class
pom.xml
|-- mainappmodule
|-- src
|-- main
| -- java
module-info.java
|-- com
|-- baeldung
|-- mainapp
Application.class
pom.xml
5. Running the Application
5.运行应用程序
Finally, let’s run the application, either from within our IDE or from a console.
最后,让我们运行这个应用程序,可以从我们的IDE内或从控制台运行。
As we might expect, we should see a couple of User objects printed out to the console when the application starts up:
正如我们所期望的,当应用程序启动时,我们应该看到有几个User对象被打印到控制台。
User{name=Julie}
User{name=David}
6. Conclusion
6.结论
In this tutorial, we learned in a pragmatic way how to put Maven and the JPMS to work side-by-side, in the development of a basic multi-module Maven project that uses Java modules.
在本教程中,我们以务实的方式学习了在开发一个使用Java模块的基本多模块Maven项目时,如何让Maven和JPMS并肩工作。
As usual, all the code samples shown in this tutorial are available over on GitHub.
像往常一样,本教程中显示的所有代码样本都可以在GitHub上获得。