3.7 聚合
聚合,指分散的聚集到一起,即部分组成整体。
3.7.1 Maven 中的聚合
使用一个总工程将各个模块工程汇集起来,作为一个整体对应完整的项目,实际就是 module
标签。
- 项目:整体
- 模块:部分
3.7.2 继承和聚合的对应关系
从继承关系角度来看:
- 父工程
- 子工程
从聚合关系角度来看:
- 总工程
- 模块工程
3.7.3 聚合的配置
在总工程中配置 modules 即可:
<modules> <module>demo-module</module> </modules>
3.7.4 依赖循环问题
如果 A 工程依赖 B 工程,B 工程依赖 C 工程,C 工程又反过来依赖 A 工程,那么在执行构建操作时会报下面的错误:
DANGER [ERROR] [ERROR] The projects in the reactor contain a cyclic reference:
这个错误的含义是:循环引用。
3.8 全局变量
全局变量的定义格式:${变量名}
对于:自定义全局变量,一般用来自定义版本号。做法是:先使用全局变量定义,再使用${变量名}
3.9 指定资源插件
一般来说,如果不指定任何资源插件,src/main/java
和 src/test/java
两个目录中的所有*.java文件会被编译,并将结果分别存放到target/classes
和targe/test-classes
目录中
但是这两个目录中的其他文件都会被忽略掉,因此,如果我们需要使用其他文件,在运行时就会报错。 解决办法是指定资源插件,将以下内容放到<buid>标签中,将其他文件也进行编译
<build> <!--把src/main/java目录中的.xml文件包含到输出结果中,输出到classes目录中--> <resources> <resource> <directory>src/main/java</directory> <!--所在的目录--> <includes> <!--包括目录下的.properties.xml文件都会扫描到--> <include>**/*.properties</include> <include>**/*.xml</include> </includes> <!--表示不用过滤器,因为.property已经起到过滤的作用了--> <filtering>false</filtering> </resource> </resources> </build>
4. build 标签
在实际使用 Maven 的过程中,我们会发现 build 标签有时候有,有时候没,这是怎么回事呢?其实通过有效 POM 我们能够看到,build 标签的相关配置其实一直都在,只是在我们需要定制构建过程的时候才会通过配置 build 标签覆盖默认值或补充配置。这一点我们可以通过打印有效 POM 来看到。
打印有效 pom
mvn help:effective-pom
当默认配置无法满足需求的定制构建的时候,就需要使用 build 标签。
4.1 build 标签的组成
build 标签的子标签大致包含三个主体部分:
4.1.1 定义约定的目录结构
<sourceDirectory>D:\product\maven-demo-parent\demo-module\src\main\java</sourceDirectory> <scriptSourceDirectory>D:\product\maven-demo-parent\demo-module\src\main\scripts</scriptSourceDirectory> <testSourceDirectory>D:\product\maven-demo-parent\demo-module\src\test\java</testSourceDirectory> <outputDirectory>D:\product\maven-demo-parent\demo-module\target\classes</outputDirectory> <testOutputDirectory>D:\product\maven-demo-parent\demo-module\target\test-classes</testOutputDirectory> <resources> <resource> <directory>D:\product\maven-demo-parent\demo-module\src\main\resources</directory> </resource> </resources> <testResources> <testResource> <directory>D:\product\maven-demo-parent\demo-module\src\test\resources</directory> </testResource> </testResources> <directory>D:\product\maven-demo-parent\demo-module\target</directory> <finalName>demo-module-1.0-SNAPSHOT</finalName>
各个目录的作用如下:
目录名 | 作用 |
sourceDirectory | 主体源程序存放目录 |
scriptSourceDirectory | 脚本源程序存放目录 |
testSourceDirectory | 测试源程序存放目录 |
outputDirectory | 主体源程序编译结果输出目录 |
testOutputDirectory | 测试源程序编译结果输出目录 |
resources | 主体资源文件存放目录 |
testResources | 测试资源文件存放目录 |
directory | 构建结果输出目录 |
4.1.2 备用插件管理
pluginManagement 标签存放着几个极少用到的插件:
- maven-antrun-plugin
- maven-assembly-plugin
- maven-dependency-plugin
- maven-release-plugin
通过 pluginManagement 标签管理起来的插件就像 dependencyManagement 一样,子工程使用时可以省略版本号,起到在父工程中统一管理版本的效果。
4.1.3 生命周期插件
plugins 标签存放的是默认生命周期中实际会用到的插件,这些插件想必大家都不陌生,所以抛开插件本身不谈,plugin 标签的结构如下:
<plugin> <artifactId>maven-compiler-plugin</artifactId> <version>3.1</version> <executions> <execution> <id>default-compile</id> <phase>compile</phase> <goals> <goal>compile</goal> </goals> </execution> <execution> <id>default-testCompile</id> <phase>test-compile</phase> <goals> <goal>testCompile</goal> </goals> </execution> </executions> </plugin>
① 坐标部分
artifactId 和 version 标签定义了插件的坐标,作为 Maven 的自带插件这里省略了 groupId。
② 执行部分
executions 标签内可以配置多个 execution 标签,execution 标签内:
- id:指定唯一标识
- phase:关联的生命周期阶段
- goals/goal:关联指定生命周期的目标
goals 标签中可以配置多个 goal 标签,表示一个生命周期环节可以对应当前插件的多个目标。
4.2 典型应用:指定 JDK 版本
前面我们在 settings.xml 中配置了 JDK 版本,那么将来把 Maven 工程部署都服务器上,脱离了 settings.xml 配置,如何保证程序正常运行呢?思路就是我们直接把 JDK 版本信息告诉负责编译操作的 maven-compiler-plugin 插件,让它在构建过程中,按照我们指定的信息工作。如下:
<!-- build 标签:意思是告诉 Maven,你的构建行为,我要开始定制了! --> <build> <!-- plugins 标签:Maven 你给我听好了,你给我构建的时候要用到这些插件! --> <plugins> <!-- plugin 标签:这是我要指定的一个具体的插件 --> <plugin> <!-- 插件的坐标。此处引用的 maven-compiler-plugin 插件不是第三方的,是一个 Maven 自带的插件。 --> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.1</version> <!-- configuration 标签:配置 maven-compiler-plugin 插件 --> <configuration> <!-- 具体配置信息会因为插件不同、需求不同而有所差异 --> <source>1.8</source> <target>1.8</target> <encoding>UTF-8</encoding> </configuration> </plugin> </plugins> </build>
- settings.xml 中配置:仅在本地生效,如果脱离当前 settings.xml 能够覆盖的范围,则无法生效。
- 在当前 Maven 工程 pom.xml 中配置:无论在哪个环境执行编译等构建操作都有效。
4.3 典型应用:SpringBoot 定制化打包
很显然 spring-boot-maven-plugin 并不是 Maven 自带的插件,而是 SpringBoot 提供的,用来改变 Maven 默认的构建行为。具体来说是改变打包的行为。默认情况下 Maven 调用 maven-jar-plugin 插件的 jar 目标,生成普通的 jar 包。
普通 jar 包没法使用 java -jar xxx.jar 这样的命令来启动、运行,但是 SpringBoot 的设计理念就是每一个『微服务』导出为一个 jar 包,这个 jar 包可以使用 java -jar xxx.jar 这样的命令直接启动运行。
这样一来,打包的方式肯定要进行调整。所以 SpringBoot 提供了 spring-boot-maven-plugin 这个插件来定制打包行为。
<build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <version>2.5.5</version> </plugin> </plugins> </build>
5. 依赖配置补充
管理依赖最基本的办法是继承父工程,但是和 Java 类一样,Maven 也是单继承的。如果不同体系的依赖信息封装在不同 POM 中了,没办法继承多个父工程怎么办?这时就可以使用 import 依赖范围。
5.1 import
典型案例当然是在项目中引入 SpringBoot、SpringCloud 依赖:
<dependencyManagement> <dependencies> <!-- SpringCloud 微服务 --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>${spring-cloud.version}</version> <type>pom</type> <scope>import</scope> </dependency> <!-- SpringCloud Alibaba 微服务 --> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-alibaba-dependencies</artifactId> <version>${spring-cloud-alibaba.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement>
import 依赖范围使用要求:
- 打包类型必须是 pom
- 必须放在 dependencyManagement 中
官网说明如下:
This scope is only supported on a dependency of type
pom
in the<dependencyManagement>
section. It indicates the dependency is to be replaced with the effective list of dependencies in the specified POM’s<dependencyManagement>
section. Since they are replaced, dependencies with a scope ofimport
do not actually participate in limiting the transitivity of a dependency.
5.2 system
以 Windows 系统环境下开发为例,假设现在 D:\product\maven-demo-parent\demo-module\target\demo-module-1.0-SNAPSHOT.jar
想要引入到我们的项目中,此时我们就可以将依赖配置为 system 范围:
<dependency> <groupId>net.javatv.maven</groupId> <artifactId>demo-module</artifactId> <version>1.0-SNAPSHOT</version> <systemPath>D:\product\maven-demo-parent\demo-module\target\demo-module-1.0-SNAPSHOT.jar</systemPath> <scope>system</scope> </dependency>
但是很明显:这样引入依赖完全不具有可移植性,所以不要使用。
5.3 runtime
专门用于编译时不需要,但是运行时需要的 jar 包。比如:编译时我们根据接口调用方法,但是实际运行时需要的是接口的实现类。典型案例是:
<!--热部署 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <scope>runtime</scope> <optional>true</optional> </dependency>
6. profile
6.1 profile 概述
这里我们可以对接 profile 这个单词中『侧面』这个含义:项目的每一个运行环境,相当于是项目整体的一个侧面。
通常情况下,我们项目至少有三种运行环境:
- 开发环境:供不同开发工程师开发的各个模块之间互相调用、访问;内部使用
- 测试环境:供测试工程师对项目的各个模块进行功能测试;内部使用
- 生产环境:供最终用户访问——所以这是正式的运行环境,对外提供服务
而我们这里的『环境』仍然只是一个笼统的说法,实际工作中一整套运行环境会包含很多种不同服务器:
- MySQL
- Redis
- ElasticSearch
- RabbitMQ
- FastDFS
- Nginx
- Tomcat
- ……
就拿其中的 MySQL 来说,不同环境下的访问参数肯定完全不同,可是代码只有一套。如果在 jdbc.properties 里面来回改,那就太麻烦了,而且很容易遗漏或写错,增加调试的难度和工作量。所以最好的办法就是把适用于各种不同环境的配置信息分别准备好,部署哪个环境就激活哪个配置。
在 Maven 中,使用 profile 机制来管理不同环境下的配置信息。但是解决同类问题的类似机制在其他框架中也有,而且从模块划分的角度来说,持久化层的信息放在构建工具中配置也违反了『高内聚,低耦合』的原则。
实际上,即使我们在 pom.xml 中不配置 profile 标签,也已经用到 profile了。为什么呢?因为根标签 project 下所有标签相当于都是在设定默认的 profile。这样一来我们也就很容易理解下面这句话:project 标签下除了 modelVersion 和坐标标签之外,其它标签都可以配置到 profile 中。
6.2 profile 配置
6.2.1 外部视角:配置文件
从外部视角来看,profile 可以在下面两种配置文件中配置:
- settings.xml:全局生效。其中我们最熟悉的就是配置 JDK 1.8。
- pom.xml:当前 POM 生效
6.2.2 内部实现:具体标签
从内部视角来看,配置 profile 有如下语法要求:
① profiles/profile 标签
- 由于 profile 天然代表众多可选配置中的一个所以由复数形式的 profiles 标签统一管理。
- 由于 profile 标签覆盖了 pom.xml 中的默认配置,所以 profiles 标签通常是 pom.xml 中的最后一个标签。
② id 标签
每个 profile 都必须有一个 id 标签,指定该 profile 的唯一标识。这个 id 标签的值会在命令行调用 profile 时被用到。这个命令格式是:
-D<profile id>
③ 其它允许出现的标签
一个 profile 可以覆盖项目的最终名称、项目依赖、插件配置等各个方面以影响构建行为。
- build
- defaultGoal
- finalName
- resources
- testResources
- plugins
- reporting
- modules
- dependencies
- dependencyManagement
- repositories
- pluginRepositories
- properties
6.3 激活 profile
① 默认配置默认被激活
前面提到了,POM 中没有在 profile 标签里的就是默认的 profile,当然默认被激活。
② 基于环境信息激活
环境信息包含:JDK 版本、操作系统参数、文件、属性等各个方面。一个 profile 一旦被激活,那么它定义的所有配置都会覆盖原来 POM 中对应层次的元素。可参考下面的标签结构:
<profile> <id>dev</id> <activation> <!-- 配置是否默认激活 --> <activeByDefault>false</activeByDefault> <jdk>1.5</jdk> <os> <name>Windows XP</name> <family>Windows</family> <arch>x86</arch> <version>5.1.2600</version> </os> <property> <name>mavenVersion</name> <value>2.0.5</value> </property> <file> <exists>file2.properties</exists> <missing>file1.properties</missing> </file> </activation> </profile>
这里有个问题是:多个激活条件之间是什么关系呢?
- Maven 3.2.2 之前:遇到第一个满足的条件即可激活——或的关系。
- Maven 3.2.2 开始:各条件均需满足——且的关系。
下面我们来看一个具体例子。假设有如下 profile 配置,在 JDK 版本为 1.6 时被激活:
<profiles> <profile> <id>JDK1.6</id> <activation> <!-- 指定激活条件为:JDK 1.6 --> <jdk>1.6</jdk> </activation> …… </profile> </profiles>
这里需要指出的是:Maven 会自动检测当前环境安装的 JDK 版本,只要 JDK 版本是以 1.6 开头都算符合条件。下面几个例子都符合:
- 1.6.0_03
- 1.6.0_02
- ……
6.4 Maven profile 多环境管理
在开发过程中,我们的软件会面对不同的运行环境,比如开发环境、测试环境、生产环境,而我们的软件在不同的环境中,有的配置可能会不一样,比如数据源配置、日志文件配置、以及一些软件运行过程中的基本配置,那每次我们将软件部署到不同的环境时,都需要修改相应的配置文件,这样来回修改,很容易出错,而且浪费劳动力。
因此我们可以利用 Maven 的 profile 来进行定义多个 profile,然后每个profile对应不同的激活条件和配置信息,从而达到不同环境使用不同配置信息的效果。
<build> <!-- profile对资源的操作 --> <resources> <resource> <directory>src/main/resources</directory> <!-- 先排除所有环境相关的配置文件 --> <excludes> <exclude>application*.yml</exclude> </excludes> </resource> <resource> <directory>src/main/resources</directory> <!-- 是否替换 @xx@ 表示的maven properties属性值 --> <!--通过开启 filtering,maven 会将文件中的 @xx@ 替换 profile 中定义的 xx 变量/属性--> <filtering>true</filtering> <includes> <include>application.yml</include> <include>application-${profileActive}.yml</include> </includes> </resource> </resources> </build> <!--多环境文件配置--> <profiles> <!--开发环境--> <profile> <id>dev</id> <activation> <!--默认激活--> <activeByDefault>true</activeByDefault> </activation> <properties> <profileActive>dev</profileActive> </properties> </profile> <!--测试环境--> <profile> <id>test</id> <properties> <profileActive>test</profileActive> </properties> </profile> <!--正式环境--> <profile> <id>prod</id> <properties> <profileActive>prod</profileActive> </properties> </profile> </profiles>
在 idea 中可以看到,因此,当你需要打包哪一个环境的就勾选即可:
同时,SpringBoot 天然支持多环境配置,一般来说,application.yml
存放公共的配置,application-dev.yml
、application-test.yml
、application.prod.yml
分别存放三个环境的配置。如下:
application.yml
中配置spring.profiles.active=prod
(或者dev、test)指定使用的配置文件,如下:
注:profileActive
,就是上面我们自定义的标签。
然后当我们勾选哪一个环境,打包的配置文件就是那一个环境:
同时我们再在 resource 标签下看到 includes 和 excludes 标签。它们的作用是:
- includes:指定执行 resource 阶段时要包含到目标位置的资源
- excludes:指定执行 resource 阶段时要排除的资源