《Maven 基础教程》系列,包含以下 5 篇文章:
Maven 基础教程(一):基础介绍、开发环境配置
Maven 基础教程(二):Maven 的使用
Maven 基础教程(三):build、profile
Maven 基础教程(四):搭建 Maven 私服 Nexus
Maven 基础教程(五): jar 包冲突问题
4.build 标签
在实际使用 Maven 的过程中,我们会发现 build 标签有时候有,有时候没,这是怎么回事呢?其实通过有效 POM 我们能够看到,build 标签的相关配置其实一直都在,只是在我们需要定制构建过程的时候才会通过配置 build 标签覆盖默认值或补充配置。这一点我们可以通过打印有效 POM 来看到。
打印有效 pom
bash
mvn help:effective-pom
当默认配置无法满足需求的定制构建的时候,就需要使用 build 标签。
4.1 build 标签的组成
build 标签的子标签大致包含三个主体部分:
1️⃣ 定义约定的目录结构
2️⃣ 备用插件管理
3️⃣ 生命周期插件
4.1.1 定义约定的目录结构
xml
<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 标签的结构如下:
xml
<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 插件,让它在构建过程中,按照我们指定的信息工作**。如下:
xml
<!-- 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 这个插件来定制打包行为。
xml
<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 依赖:
bash
<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 中
5.2 system
以 Windows 系统环境下开发为例,假设现在 D:\product\maven-demo-parent\demo-module\target\demo-module-1.0-SNAPSHOT.jar 想要引入到我们的项目中,此时我们就可以将依赖配置为 system 范围:
xml
<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>
bash
<dependency>
<groupId>aspose-cad</groupId>
<artifactId>aspose-cad</artifactId>
<version>20.10</version>
<scope>system</scope>
<systemPath>${pom.basedir}/src/main/resources/lib/aspose-cad-20.10.jar</systemPath>
</dependency>
在Maven 打包需要把外部依赖包打进jar包时候需要 再 spring-boot-maven-plugin 插件中 true 标签设置为ture 打进依赖包
bash
<build>
<finalName>${project.artifactId}</finalName>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<mainClass>avicit.AvicitVpdmApplication</mainClass>
<!--设置为true,以便把本地的system的jar也包括进来-->
<includeSystemScope>true</includeSystemScope>
</configuration>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
5.3 runtime
专门用于编译时不需要,但是运行时需要的 jar 包。比如:编译时我们根据接口调用方法,但是实际运行时需要的是接口的实现类。典型案例是:
xml
<!--热部署 -->
<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 有如下语法要求:
1️⃣ profiles / profile 标签
- 由于 profile 天然代表众多可选配置中的一个,所以由复数形式的 profiles 标签统一管理。
- 由于 profile 标签覆盖了 pom.xml 中的默认配置,所以 profiles 标签通常是 pom.xml 中的最后一个标签。
2️⃣ id 标签
每个 profile 都必须有一个 id 标签,指定该 profile 的唯一标识。这个 id 标签的值会在命令行调用 profile 时被用到。这个命令格式是:
bash
-D<profile id>
其它允许出现的标签
一个 profile 可以覆盖项目的 最终名称、项目依赖、插件配置 等各个方面以影响构建行为。
- build
- defaultGoal
- finalName
- resources
- testResources
- plugins
- reporting
- modules
- dependencies
- dependencyManagement
- repositories
- pluginRepositories
- properties
6.3 激活 profile
1️⃣ 默认配置默认被激活
前面提到了,POM 中没有在 profile 标签里的就是默认的 profile,当然默认被激活。
2️⃣ 基于环境信息激活
环境信息包含:JDK 版本、操作系统参数、文件、属性 等各个方面。一个 profile 一旦被激活,那么它定义的所有配置都会覆盖原来 POM 中对应层次的元素。可参考下面的标签结构:
xml
<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 时被激活:
xml
<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 对应不同的激活条件和配置信息,从而达到不同环境使用不同配置信息的效果。
xml
<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 阶段时要排除的资源。