第8章 聚合与继承
Maven的聚合特性能够把项目的各个模块聚合在一起构建,而maven的继承特性则能帮助抽取各模块相同的依赖和插件等配置,在简化POM的同时,还能促进各个模块配置的一致性。
8.1 mirana-dal
mirana-dal模块负责mirana的数据的持久化,以json文件的形式保存数据,并支持账户的创建、读取、更新、删除等操作。
【由于腾讯云那个mysql很难装,暂时又不想深入学习mongo,因此这个地方直接使用文件系统作为数据库,顺便学习java的IO知道,代码参考spring-core里面的io部分】
相关类和方法有
- java.util.Properties(在项目内部)
- io和nio相关知识(更多见NIO)
<?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">
<parent>
<artifactId>mirana-parent</artifactId>
<groupId>com.ssozh</groupId>
<version>1.0.0-SNAPSHOT</version>
</parent>
<build>
<testResources>
<testResource>
<directory>src/test/resources</directory>
<filtering>true</filtering>
</testResource>
</testResources>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>${maven.compiler.source}</source>
<target>${maven.compiler.target}</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<version>3.2.0</version>
<configuration>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
</plugins>
</build>
</project>
该模块坐标为com.ssozh:mirana-parent:1.0.0-SNAPSHOT
。和其他模块有相同的groupId,artifactId有相同的前缀。
一般来说,一个项目的子模块都应该使用相同的groupid,如果在一起开发和发布,还应该使用相同的version。artifactId也应该有相同的前缀,以方便其他项目区分。
build元素他包含了一个testResource
子元素,这是为了开启资源过滤。【用于单元测试】。下面还包含两个插件,一个是maven-compiler-plugin
用于支持java1.8(实际上该版本在超级POM中有设定,而这个1.8是在springboot-start-parent中设定的),此外还配置了maven-resources-plugin
使用了UTF-8编码处理资源文件。
8.2 聚合
到目前为止,mirana这个项目分别在biz层实现了createGroup方法,和dal层的createGroup方法。这时,一个简单的需求就会自然而然地显示出来:我们会想要一次构建两个项目,而不是到两个模块的目录下分别执行mvn命令。
为了能够使用一条命令就能够构建mirana-api、mirana-controller、mirana-common、mirana-dal等多个模块,我们需要额外创建一个名为mirana-parent
的模块,然后通过该模块构建整个项目的所有模块。mirana-parent
本身作为一个maven项目,它必须要有自己的POM,不过同时作为一个聚合项目,其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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.4</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.ssozh</groupId>
<artifactId>mirana-parent</artifactId>
<version>1.0.0-SNAPSHOT</version>
<name>mirana-parent</name>
<description>Demo project for Spring Boot</description>
<packaging>pom</packaging>
<modules>
<module>assembly</module>
<module>controller</module>
<module>service</module>
<module>biz</module>
<module>dal</module>
<module>common</module>
<module>integration</module>
<module>api</module>
<module>starter</module>
<module>test</module>
</modules>
</project>
这里有几个特殊的地方:
- packaging:pom,聚合模块的打包方式必须是pom,否则就无法构建
- modules:实现聚合的最核心位置,用户可以通过自一个打包方式为pom的maven项目中声明任意数量的module元素来实现模块的聚合。
- 一般来讲,模块所处的目录名称应该遇其artifactId一致,如果不一致,则需要将聚合pom中的
<module>
改成相应的目录名。 - 聚合模块与其他模块的目录结构并非一定要是父子关系,也可以使用平行目录结构,这个时候,
<module>
也应该指向正确的模块目录 mvn clean install
的过程:- 解析聚合模块的POM,分析要构建的模块
- 计算出一个反应堆构建顺序(reactor build order)
- 这个就是依赖关系:parent->api->common->integration->dal->biz->service->controller->starter->assembly
- 根据这个顺序依次构建各个模块。
- 其他:上述过程中输出的是各个模块的名称,而不是artifactId。
8.3 继承
可以通过继承抽取出重复的配置,maven也有这种继承机制。
作为父模块的POM和聚合模块的POM一样都必须是POM的打包类型。由于父模块不仅仅是为了帮助消除配置的重复,因为他本身不包含POM以外的项目文件,也就不需要src/main/java
之类的文件夹了。
relativePath
表示父模块POM的相对路径,默认值是../pom.xml
,其他情况需要手动写入。
如果开发团队需要从源码库中迁出一个包含父子关系的Maven项目,则需要设置正确的<relativePath>
。
8.3.1可继承的POM元素
从上一节我们看到,groupId和version是可以被继承的,以下是完整的继承列表:
- groupId:项目组Id,坐标核心元素。
- version:项目版本,坐标核心元素。
- properties:自定义Maven属性
- dependencies:项目的依赖配置
- dependencyManagement:项目的依赖管理配置
- repositories:项目的仓库配置
- build:包括项目的源码目录配置、输出目录配置、插件配置、插件管理配置等。
- reporting:包括项目的报告输出目录配置、报告插件配置等。
- description:项目的描述信息
- organization:项目的组织信息。
- inceptionYear:项目的创始年份
- url:项目的url地址
- developers、contributors:项目的开发者贡献值信息
- distributionManagement:项目的部署配置
- ciManagement:项目的持续集成系统信息
- scm:项目的版本控制系统信息
8.3.2 依赖管理(常用)
Maven提供的dependencyManagement元素能够让子模块依赖父模块的依赖配置,又能保证子模块依赖使用的灵活性。在dependencyManagement元素下的依赖声明不会引入实际的依赖,不过它能够约束dependencies下的依赖使用。
<properties>
<java.version>1.8</java.version>
<mirana.parent.version>1.0.0-SNAPSHOT</mirana.parent.version>
<mirana.api.version>1.0.0-SNAPSHOT</mirana.api.version>
<fastjson.version>1.2.76</fastjson.version>
<guava.version>28.2-android</guava.version>
<mapstruct.version>1.4.2.Final</mapstruct.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.ssozh</groupId>
<artifactId>mirana-api</artifactId>
<version>${mirana.api.version}</version>
</dependency>
<dependency>
<groupId>com.ssozh</groupId>
<artifactId>mirana-starter</artifactId>
<version>${mirana.parent.version}</version>
</dependency>
<!-- ................. -->
</dependencies>
</dependencyManagement>
这样在实际test中使用的时候,直接省略了相关的version
和scope
信息。这些信息可以省略是因为子项目继承了父项目中的dependencyManagement
配置。 这样虽然不能减少太多的POM配置,但是在父POM中声明是能够规范统一项目依赖的版本,子模块在使用依赖的时候就无须声明版本,也就不会发生多个子模块使用依赖版本不一致的情况,从而降低依赖冲突的几率。
另外,对于前面介绍的import
的依赖范围。使用该范围的依赖通常指向一个POM,作用是将目标POM中的dependencyManagement
配置导入并合并到当前POM的dependencyManagement
元素中。如果想要在另外一个模块中使用与某个模块玩去哪一样的dependencyManagement
配置,除了复制配置或者继承这两种方式外,还可以使用import范围依赖将这一配置导入。
8.3.2 插件管理
Maven提供了dependencyManagement
元素帮助管理依赖,Maven也提供了pluginManagement
元素帮助管理插件。在该元素中配置的依赖不会造成实际的插件调用行为,当POM中配置了真正的plugin元素,并且GAV与pluginManagement中配置的插件匹配时,pluginManagement的配置才会影响实际的插件行为。
例如父项目中可以这么写:
<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<version>3.2.1</version>
</plugin>
</plugins>
</pluginManagement>
</build>
8.4 聚合与继承的关系
多模块Maven项目中的聚合与继承其实是两个概念,其目的是完全不同的。
聚合 | 继承 | |
---|---|---|
概念和目的 | 为了方便快速构建项目 | 为了消除重复配置 |
使用方法 | <modules> 来聚合项目 |
子项目<parent> 和<relativePath> 。父项目的<xxxManagement> |
现有的实际项目中,往往一个POM既是聚合POM,又是父POM,这么做主要是为了方便。
8.5 约定优于配置
Maven会假设用户的项目是这样的:
- 源码目录为
src/main/java/
- 编译输出目录为
target/classes
- 打包方式为jar
- 包输出目录为
target/
遵循约定虽然损失了一定的灵活性,用户不能随意安排目录结构。如果想要修改 也可以在<build>
中进行修改:
<build>
<sourceDirectory>src/java</sourceDirectory>
</build>
实际上,任何一个Maven项目都是继承的超级POM。这点有点类似于任何一个java类都是隐式继承于Object类一样。在maven3中位置位于$MAVEN_HOMEmaven-model-buildersrcmain
esourcesorgapachemavenmodelpom-4.0.0.xml
具体看相关的代码:
<build>
<directory>${project.basedir}/target</directory>
<outputDirectory>${project.build.directory}/classes</outputDirectory>
<finalName>${project.artifactId}-${project.version}</finalName>
<testOutputDirectory>${project.build.directory}/test-classes</testOutputDirectory>
<sourceDirectory>${project.basedir}/src/main/java</sourceDirectory>
<scriptSourceDirectory>${project.basedir}/src/main/scripts</scriptSourceDirectory>
<testSourceDirectory>${project.basedir}/src/test/java</testSourceDirectory>
<resources>
<resource>
<directory>${project.basedir}/src/main/resources</directory>
</resource>
</resources>
<testResources>
<testResource>
<directory>${project.basedir}/src/test/resources</directory>
</testResource>
</testResources>
<pluginManagement>
<!-- NOTE: These plugins will be removed from future versions of the super POM -->
<!-- They are kept for the moment as they are very unlikely to conflict with lifecycle mappings (MNG-4453) -->
<plugins>
<plugin>
<artifactId>maven-antrun-plugin</artifactId>
<version>1.3</version>
</plugin>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<version>2.2-beta-5</version>
</plugin>
<plugin>
<artifactId>maven-dependency-plugin</artifactId>
<version>2.8</version>
</plugin>
<plugin>
<artifactId>maven-release-plugin</artifactId>
<version>2.5.3</version>
</plugin>
</plugins>
</pluginManagement>
</build>
8.6 反应堆
在一个多模块的Maven项目中,反应堆(reactor)是指所有组成的一个构建结构。对于单模块的项目,反应堆就是该模块本身,对于多模块而言,反应堆就包含了各模块之间继承与依赖的关系,从而计自动计算出合理的模块构建顺序。
8.6.1 反应堆的构建顺序
模块间的依赖关系会将反应堆构建成一个有向非循环图(directed acyclic graph,DAG)各个模块是该图的节点,依赖关系构成了有向边。这个图不允许出现循环,因此,当出现模块A依赖模块B,而B又依赖A的情况时,Maven就会报错。
8.6.2 裁剪反应堆
有些时候,用户会想要仅仅构建完整反应堆中的某些模块,换句话说,用户需要实时地裁剪反应堆。
maven提供很多的命令行选择支持裁剪反应堆,输出mvn -h 可以看到这些选项:
- -am:--also-make通收件所列模块的依赖模块
- -amd:--alse-make-dependents同时构建依赖于所列模块的模块
- -pl:
--projects<args>
构建指定的模块,模块间用逗号分隔 - -rf:
-resume-from <args>
从指定的模块回复反应堆
具体例子:
# 构建完整反应堆
mvn clean install
# 使用-pl选项指定构建某几个模块
mvn clean install -pl mirana-dal, mirana-biz
## 使用-am选项考研同时构建所列模块的依赖模块
mvn clean install -pl mirana-dal -am
## 使用-amd选项考研同时构建依赖于所列模块的模块
mvn clean install -pl mirana-dal -amd
## 使用—rf选项可以在完整的反应堆顺序基础上指定从哪个模块开始构建。例如:
mvn clean install -rf mirana-dal
在开发过程中,灵活应用上述4个参数,可以帮助我们跳过无需构建的模块,从而加速构建。【目前】我们的实际项目中没有用过这些
8.7 小结
本章介绍了聚合和继承两个特性,然后介绍了优化大于配置,最后介绍了反应堆 和相关的命令参数用于裁剪反应堆。
其他
-
junit注解
@Before
可以用在执行测试用例之前,执行该方法。可以用来初始化springframework的IoC容器。@Test
注解标注的就是要测试的方法。
-
关于Maven中Model.class是如何通过
maven-model
中的maven.mdo
生成的? -
既然Maven不允许循环依赖,spring中通过
@Resource
的循环依赖是什么样的呢?