在这个技术飞速发展的时代,各类用户对软件的要求越来越高,软件本身也变得越来越复杂。因此,软件设计人员往往会采用各种方式对软件划分模块,以得到更清晰的设计及更高的重用性。当把Maven应用到实际项目中的时候,也需要将项目分成不同的模块。
Maven的聚合特性能够把项目的各个模块聚合在一起构建,而Maven的继承特性则能帮助抽取各模块相同的依赖和插件等配置,在简化POM的同时,还能促进各个模块配置的一致性。
1、聚合
1.1 项目准备
首先,在IDEA中,新建一个MavenMultiTest工程,位于目录/Users/chengxia/Developer/Java/MavenMultiTest/
,然后将工程自带的同名module删除。然后,分别新建如下两个Module
1.1.1 module1
新建如下module1,位于目录/Users/chengxia/Developer/Java/MavenMultiTest/module1
,结构如下图:
module1/pom.xml:
<?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.lfqy.maventest</groupId>
<artifactId>module1</artifactId>
<version>1.0-SNAPSHOT</version>
</project>
com.lfqy.maventest.module1.HelloWorld1:
package com.lfqy.maventest.module1;
/**
* Created by chengxia on 2019/9/18.
*/
public class HelloWorld1 {
public static void main(String []args){
System.out.println("Hello World!");
for(String arg : args){
System.out.println("Hello, " + arg + "!");
}
System.out.println("Hello World!");
}
}
这样创建完成之后,通过命令行,切换到目录/Users/chengxia/Developer/Java/MavenMultiTest/module1
,执行编译命令:
$ mvn compile
[INFO] Scanning for projects...
[INFO]
[INFO] ---------------------< com.lfqy.maventest:module1 >---------------------
[INFO] Building module1 1.0-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ module1 ---
[WARNING] Using platform encoding (UTF-8 actually) to copy filtered resources, i.e. build is platform dependent!
[INFO] Copying 0 resource
[INFO]
[INFO] --- maven-compiler-plugin:3.1:compile (default-compile) @ module1 ---
[INFO] Nothing to compile - all classes are up to date
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 1.062 s
[INFO] Finished at: 2019-09-18T08:04:07+08:00
[INFO] ------------------------------------------------------------------------
$
接下来也可以通过maven运行其中的带main函数的类。
如果不像main函数传入参数,可以执行:
$ mvn exec:java -Dexec.mainClass="com.lfqy.maventest.module1.HelloWorld1"
[INFO] Scanning for projects...
[INFO]
[INFO] ---------------------< com.lfqy.maventest:module1 >---------------------
[INFO] Building module1 1.0-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- exec-maven-plugin:1.6.0:java (default-cli) @ module1 ---
Hello World!
Hello World!
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 1.007 s
[INFO] Finished at: 2019-09-18T08:06:14+08:00
[INFO] ------------------------------------------------------------------------
$
向main函数传入两个参数,执行:
$ mvn exec:java -Dexec.mainClass="com.lfqy.maventest.module1.HelloWorld1" -Dexec.args="Qiuqiu Paopao"
[INFO] Scanning for projects...
[INFO]
[INFO] ---------------------< com.lfqy.maventest:module1 >---------------------
[INFO] Building module1 1.0-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- exec-maven-plugin:1.6.0:java (default-cli) @ module1 ---
Hello World!
Hello, Qiuqiu!
Hello, Paopao!
Hello World!
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 0.911 s
[INFO] Finished at: 2019-09-18T08:07:33+08:00
[INFO] ------------------------------------------------------------------------
$
1.1.2 module2
新建如下module2,位于目录/Users/chengxia/Developer/Java/MavenMultiTest/module2
,结构如下图:
module2/pom.xml:
<?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.lfqy.maventest</groupId>
<artifactId>module2</artifactId>
<version>1.0-SNAPSHOT</version>
</project>
com.lfqy.maventest.module2.HelloWorld2:
package com.lfqy.maventest.module2;
/**
* Created by chengxia on 2019/9/18.
*/
public class HelloWorld2 {
public static void main(String []args){
System.out.println("Hello Universe!");
for(String arg : args){
System.out.println("Hello, " + arg + "!");
}
System.out.println("Hello Universe!");
}
}
编译之后执行如下:
$ mvn exec:java -Dexec.mainClass="com.lfqy.maventest.module2.HelloWorld2" -Dexec.args="Qiuqiu Paopao"
[INFO] Scanning for projects...
[INFO]
[INFO] ---------------------< com.lfqy.maventest:module2 >---------------------
[INFO] Building module2 1.0-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- exec-maven-plugin:1.6.0:java (default-cli) @ module2 ---
Hello Universe!
Hello, Qiuqiu!
Hello, Paopao!
Hello Universe!
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 0.896 s
[INFO] Finished at: 2019-09-18T08:36:48+08:00
[INFO] ------------------------------------------------------------------------
$
1.2 聚合配置
接下来,我们新建一个聚合项目module-aggregator
,这个module的目录位于/Users/chengxia/Developer/Java/MavenMultiTest/
。
一般来说,module所在的目录应该和artifactid一致,不过这不是maven的强制要求。
目录结构如下:
由于这是一个专为聚合而存在的项目,所以,其中没有任何代码,只需要关注其pom配置。
pom.xml:
<?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.lfqy.maventest</groupId>
<artifactId>module-aggregator</artifactId>
<version>1.0-SNAPSHOT</version>
<name>Module Aggregator</name><!-- 一个更易辨识的别名 -->
<packaging>pom</packaging><!-- packaging默认值是jar,对于聚合项目,其值必须为POM。否则无法构建-->
<modules>
<module>module1</module>
<module>module2</module>
</modules>
</project>
这里需要注意modules标签下的module配置,其中的内容是目录名,不是子模块的artifactid。因此,如果子模块不在聚合模块的根目录下,而是和聚合模块是平行目录,这里需要配置成../module1
和../module2
。
最后,在目录/Users/chengxia/Developer/Java/MavenMultiTest/
下执行构件命令,可以实现这两个项目的共同构建:
$ mvn compile
[INFO] Scanning for projects...
[INFO] ------------------------------------------------------------------------
[INFO] Reactor Build Order:
[INFO]
[INFO] module1 [jar]
[INFO] module2 [jar]
[INFO] Module Aggregator [pom]
[INFO]
[INFO] ---------------------< com.lfqy.maventest:module1 >---------------------
[INFO] Building module1 1.0-SNAPSHOT [1/3]
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ module1 ---
[WARNING] Using platform encoding (UTF-8 actually) to copy filtered resources, i.e. build is platform dependent!
[INFO] Copying 0 resource
[INFO]
[INFO] --- maven-compiler-plugin:3.1:compile (default-compile) @ module1 ---
[INFO] Changes detected - recompiling the module!
[WARNING] File encoding has not been set, using platform encoding UTF-8, i.e. build is platform dependent!
[INFO] Compiling 1 source file to /Users/chengxia/Developer/Java/MavenMultiTest/module1/target/classes
[INFO]
[INFO] ---------------------< com.lfqy.maventest:module2 >---------------------
[INFO] Building module2 1.0-SNAPSHOT [2/3]
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ module2 ---
[WARNING] Using platform encoding (UTF-8 actually) to copy filtered resources, i.e. build is platform dependent!
[INFO] Copying 0 resource
[INFO]
[INFO] --- maven-compiler-plugin:3.1:compile (default-compile) @ module2 ---
[INFO] Changes detected - recompiling the module!
[WARNING] File encoding has not been set, using platform encoding UTF-8, i.e. build is platform dependent!
[INFO] Compiling 1 source file to /Users/chengxia/Developer/Java/MavenMultiTest/module2/target/classes
[INFO]
[INFO] ----------------< com.lfqy.maventest:module-aggregator >----------------
[INFO] Building Module Aggregator 1.0-SNAPSHOT [3/3]
[INFO] --------------------------------[ pom ]---------------------------------
[INFO] ------------------------------------------------------------------------
[INFO] Reactor Summary for Module Aggregator 1.0-SNAPSHOT:
[INFO]
[INFO] module1 ............................................ SUCCESS [ 1.707 s]
[INFO] module2 ............................................ SUCCESS [ 0.078 s]
[INFO] Module Aggregator .................................. SUCCESS [ 0.002 s]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 1.930 s
[INFO] Finished at: 2019-09-18T08:59:54+08:00
[INFO] ------------------------------------------------------------------------
$
PS:至于如何看构建、清理等mvn操作是否成功,除了看控制台的输出之外,可以在IDEA中看target目录:构建成功之后,target目录生成,清理成功之后target目录消失。
由于前面建立的聚合项目,纯粹是为了聚合两个项目,实现它们的共同构建。所以,可以将这个项目中的src等代码相关的目录删除,不会影响构建。删除之后的目录结构如下:
2、继承
在前面例子的基础上,创建一个新模块module-parent
作为module1和module2模块的父模块,这样,通过继承,可以将module1和module2中的一些配置抽象到父模块中,避免重复配置,方便管理。项目结构如下:
module-parent/pom.xml:
<?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.lfqy.maventest</groupId>
<artifactId>module-parent</artifactId>
<version>1.0-SNAPSHOT</version>
<!-- 作为父模块,打包方式也必须是pom -->
<packaging>pom</packaging>
<name>Module Parent</name>
</project>
注意,父模块的打包方式也必须是pom。
修改module1的pom文件,让它继承父模块的配置。
module1/pom.xml:
<?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>
<parent>
<groupId>com.lfqy.maventest</groupId>
<artifactId>module-parent</artifactId>
<version>1.0-SNAPSHOT</version>
<!-- 配置父模块pom文件的位置,默认是../pom.xml -->
<relativePath>../module-parent/pom.xml</relativePath>
</parent>
<artifactId>module1</artifactId>
<!-- 注释掉如下的groupid和version定义,让其从父模块继承 -->
<!--<groupId>com.lfqy.maventest</groupId>-->
<!--<version>1.0-SNAPSHOT</version>-->
<name>Module 1</name>
</project>
对于module2,也做类似的修改。
module2/pom.xml:
<?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>
<parent>
<groupId>com.lfqy.maventest</groupId>
<artifactId>module-parent</artifactId>
<version>1.0-SNAPSHOT</version>
<!-- 配置父模块pom文件的位置,默认是../pom.xml -->
<relativePath>../module-parent/pom.xml</relativePath>
</parent>
<artifactId>module2</artifactId>
<!-- 注释掉如下的groupid和version定义,让其从父模块继承 -->
<!--<groupId>com.lfqy.maventest</groupId>-->
<!--<version>1.0-SNAPSHOT</version>-->
<name>Module 2</name>
</project>
执行这一步,就基本完成了。可以将两个子模块单独构件,也可以通过前面创建的聚合项目一起构建。
我们也可以将这个父项目也添加到前面的聚合项目中,这样父项目也可以一起构件。修改聚合项目的pom配置文件如下。
pom.xml(目录/Users/chengxia/Developer/Java/MavenMultiTest/pom.xml
):
<?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.lfqy.maventest</groupId>
<artifactId>module-aggregator</artifactId>
<version>1.0-SNAPSHOT</version>
<name>Module Aggregator</name><!-- 一个更易辨识的别名 -->
<packaging>pom</packaging><!-- packaging默认值是jar,对于聚合项目,其值必须为POM。否则无法构建-->
<modules>
<module>module1</module>
<module>module2</module>
<module>module-parent</module>
</modules>
</project>
这样,在聚合项目的根目录执行构件,输出如下。
$ mvn compile
[INFO] Scanning for projects...
[INFO] ------------------------------------------------------------------------
[INFO] Reactor Build Order:
[INFO]
[INFO] Module Parent [pom]
[INFO] Module 1 [jar]
[INFO] Module 2 [jar]
[INFO] Module Aggregator [pom]
[INFO]
[INFO] ------------------< com.lfqy.maventest:module-parent >------------------
[INFO] Building Module Parent 1.0-SNAPSHOT [1/4]
[INFO] --------------------------------[ pom ]---------------------------------
[INFO]
[INFO] ---------------------< com.lfqy.maventest:module1 >---------------------
[INFO] Building Module 1 1.0-SNAPSHOT [2/4]
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ module1 ---
[WARNING] Using platform encoding (UTF-8 actually) to copy filtered resources, i.e. build is platform dependent!
[INFO] Copying 0 resource
[INFO]
[INFO] --- maven-compiler-plugin:3.1:compile (default-compile) @ module1 ---
[INFO] Changes detected - recompiling the module!
[WARNING] File encoding has not been set, using platform encoding UTF-8, i.e. build is platform dependent!
[INFO] Compiling 1 source file to /Users/chengxia/Developer/Java/MavenMultiTest/module1/target/classes
[INFO]
[INFO] ---------------------< com.lfqy.maventest:module2 >---------------------
[INFO] Building Module 2 1.0-SNAPSHOT [3/4]
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ module2 ---
[WARNING] Using platform encoding (UTF-8 actually) to copy filtered resources, i.e. build is platform dependent!
[INFO] Copying 0 resource
[INFO]
[INFO] --- maven-compiler-plugin:3.1:compile (default-compile) @ module2 ---
[INFO] Changes detected - recompiling the module!
[WARNING] File encoding has not been set, using platform encoding UTF-8, i.e. build is platform dependent!
[INFO] Compiling 1 source file to /Users/chengxia/Developer/Java/MavenMultiTest/module2/target/classes
[INFO]
[INFO] ----------------< com.lfqy.maventest:module-aggregator >----------------
[INFO] Building Module Aggregator 1.0-SNAPSHOT [4/4]
[INFO] --------------------------------[ pom ]---------------------------------
[INFO] ------------------------------------------------------------------------
[INFO] Reactor Summary for Module Aggregator 1.0-SNAPSHOT:
[INFO]
[INFO] Module Parent ...................................... SUCCESS [ 0.008 s]
[INFO] Module 1 ........................................... SUCCESS [ 1.404 s]
[INFO] Module 2 ........................................... SUCCESS [ 0.077 s]
[INFO] Module Aggregator .................................. SUCCESS [ 0.001 s]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 1.680 s
[INFO] Finished at: 2019-09-19T08:37:49+08:00
[INFO] ------------------------------------------------------------------------
$
但是,由于这里父项目中实际上没有需要构件的代码,所以这一步只是演示,意义不大。
3、反应堆和构建顺序
在一个多模块的Maven项目中,反应堆(Reactor)是指所有模块组成的一个构建结构。对于单模块的项目,反应堆就是该模块本身,但对于多模块项目来说,反应堆就包含了各模块之间继承与依赖的关系,从而能够自动计算出合理的模块构建顺序。
构建顺序是这样形成的:Maven按序读取POM,如果该POM没有依赖模块,那么就构建该模块,否则就先构建其依赖模块,如果该依赖还依赖于其他模块,则进一步先构建依赖的依赖。该例中,module1它依赖于module-parent
模块,必须先构建module-parent
,然后再构建module1,然后到module2的时候,由于其依赖模块已经被构建,因此直接构建它。
模块间的依赖关系会将反应堆构成一个有向非循环图(Directed Acyclic Graph, DAG),各个模块是该图的节点,依赖关系构成了有向边。这个图不允许出现循环,因此,当出现模块A依赖于B,而B又依赖于A的情况时,Maven就会报错。
参考资料
- 《Maven实战》徐晓斌
- 使用Maven运行Java main的3种方式