Maven实战-灵活的构建

avatar
作者
猴君
阅读量:0

文章目录


优秀的构建系统必须足够灵活,能够让项目在不同的环境下都能成功地构建。例如,典型的项目都会有开发环境、测试环境和产品环境,这些环境的数据库配置不尽相同,那么项目构建的时候就需要能够识别所在的环境并使用正确的配置。还有一种常见的情况是,项目开发了大量的集成测试,这些测试运行起来非常耗时,不适合在每次构建项目的时候都运行,因此需要一种手段能让我们在特定的时候才激活这些集成测试。Maven 为了支持构建的灵活性,内置了三大特性,即 属性、Profile 和资源过滤。这里介绍如何合理使用这些特性来帮助项目自如地应对各种环境。

Maven属性

最常见的使用 Maven 属性的方式,是在POM文件中,通过 <properties> 元素自定义一个或多个 Maven 属性,然后在POM的其他地方使用 ${属性名称} 的方式引用该属性,这种做法的最大意义在于消除重复。如下:

<properties>     <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>     <java.version>17</java.version> </properties> 

这不是 Maven 属性的全部,事实上这只是6类 Maven 属性中的一类(自定义属性)而已。这6类属性分别为如下。

1️⃣内置属性

主要有两个常用内置属性:

  • ${basedir}:表示项目根目录,即包含 POM 文件的这层目录。
  • ${version}:表示项目版本。

2️⃣POM属性

用户可以使用该类属性引用 POM 文件中对应元素的值。例如 ${project.artifactId} 就对应表示了 <project> 中 <artifactId> 元素的值。常用的 POM 属性个有如下:

  • ${project.build.sourceDirectory}:项目的主源码目录,默认为 src/main/java。
  • ${project.build.testSourceDirectory}:项目的测试主源码目录,默认为 src/test/java。
  • ${project.build.directory}:项目构建输出目录,默认为 target/。
  • ${project.outputDirectory}:项目主代码编译输出目录,默认为 target/classes/。
  • ${project.testOutputDirectory}:项目测试代码编译输出目录,默认为 target/test-classes/。
  • ${project.groupId}:项目的 groupId。
  • ${project.artifactId}:项目的 artifactId。
  • ${project.version}:项目的 version,与 ${version} 等价。
  • ${project.build.finalName}:项目打包输出文件的名称,默认为 ${project.artifactId}-${project.version}。

这些属性都对应了一个 POM 元素,它们中一些属性的默认值都是在超级POM中定义的。

3️⃣自定义属性

用户可以在 POM 文件中的 <properties> 元素下自定义 Maven 属性,例如:

<properties>     <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>     <java.version>17</java.version> </properties> 

然后在 POM 中的其他方,使用 ${java.version} 的时候就会被替换成 17。

4️⃣Settings属性

与 POM 属性同理,但是这里用户是使用以 settings. 开头的属性引用 setings.xml 文件中 XML 元素的值,如常用的 ${settings.localRepository} 指向用户本地仓库的地址。

5️⃣Java系统属性

所有 Java 系统属性都可以使用 Maven 属性引用,例如 ${user.home} 指向了用户目录。用户可以使用 mvn help:system 查看所有的Java系统属性。

6️⃣环境变量属性

所有环境变量都可以使用以 env. 开头的 Maven 属性引用。例如 ${env.JAVA_HOME} 指代了JAVA_HOME 环境变量的值。用户可以使用 mvn help:system 查看所有的环境变量。

正确使用这些 Maven 属性可以帮助我们简化 POM 的配置和维护工作。Maven 属性能让我们在 POM 中方便地引用项目环境和构建环境的各种十分有用的值,这是创建灵活构建的基础。

构建环境的差异

在不同的环境中,项目的源码应该使用不同的方式进行构建,最常见的就是数据库配置了。例如在开发的过程中,有些项目会在 src/main/resources/ 目录下放置带有如下内容的数据库配置文件:

database.jdbc.driverClass = com.mysql.jdbc.Driver database.jdbc.connectionURL = jdbc:mysql://localhost:3306/test database.jdbc.username = dev database.jdbc.password = dev-pwd 

这本没什么问题,可当测试人员想要构建项目产品并进行测试的时候,他们往往需要使用不同的数据库。这时的数据库配置文件可能是这样的:

database.jdbc.driverClass = com.mysql.jdbc.Driver database.jdbc.connectionURL = jdbc:mysql://192.168.100:3306/test database.jdbc.username = test database.jdbc.password = test-pwd 

连接数据库的 URL、用户名和密码都发生了变化,类似地,当项目被发布到产品环境的时候,所使用的数据库配置又是另外一套了。这个时候,比较原始的做法是,使用与开发环境一样的构建,然后在测试或者发布产品之前再手动更改这些配置。这是可行的,也是比较常见的,但肯定不是最好的方法。手动往往就意味着低效和错误,因此需要找到一种方法,使它能够自动地应对构建环境的差异。

Maven 的答案是针对不同的环境生成不同的构件。也就是说,在构建项目的过程中 Maven 就已经将这种差异处理好了。

资源过滤

为了应对环境的变化,首先需要使用 Maven 属性将这些将会发生变化的部分提取出来。在上面的数据库配置中,连接数据库使用的驱动类、URL、用户名和密码都可能发生变化,因此用Maven属性取代它们:

database.jdbc.driverClass = ${db.driver} database.jdbc.connectionURL = ${db.url} database.jdbc.username = ${db.username} database.jdbc.password = ${db.password} 

这里定义了4个 Maven 属性,它们的命名是任意的,读者可以根据自己的实际情况定义最合适的属性名称。既然使用了 Maven 属性,就应该在某个地方定义它们。上面已经介绍过如何自定义 Maven 属性,这里要做的是使用一个额外的 profile 将其包裹,代码如下:

<profiles>     <profile>         <id>dev</id>         <properties>             <db.driver>com.mysql.jdbc.Driver</db.driver>             <db.url>jdbc:mysql:localhost://8081/test</db.url>             <db.username>dev</db.username>             <db.password>dev-pwd</db.password>         </properties>     </profile> </profiles> 

如上,在 POM 中定义的 Maven 属性与直接在 POM 的 properties 元素下定义并无二致,这里只是使用了一个 id 为 dev 的 profile,其目的是将开发环境下的配置与其他环境区别开来。关于 profile,会在后面详细讲解。

有了属性定义,配置文件中也使用了这些属性,一切OK了吗?还不行。要留意的是,Maven 属性默认只有在 POM 中才会被解析。也就是说,${db.usemame} 放到 POM 中会变成 dev,但是如果放到 src/main/resources/ 目录下的文件中,构建的时候它将仍然还是 ${db.usemame}。因此,需要让 Maven 解析资源文件中的 Maven 属性。

资源文件的处理其实是 maven-resources-plugin 做的事情,它默认的行为只是将项目主资源文件复制到主代码编译输出目录中,将测试资源文件复制到测试代码编译输出目录中。不过只要通过一些简单的 POM 配置,该插件就能够解析资源文件中的 Maven 属性,即开启资源过滤

Maven 默认的主资源目录和测试资源目录的定义是在超级 POM 中。要为资源目录开启过滤,只要在此基础上添加一行 filtering配置即可,代码如下:

<resources>     <resource>         <directory>${project.basedir}/src/main/resources</directory>         <filtering>true</filtering>     </resource> </resources> 

类似的,为测试目录开启资源过滤,代码如下:

<testResources>     <testResource>         <directory>${project.basedir}/src/test/resources</directory>         <filtering>true</filtering>     </testResource> </testResources> 

你可能会意识到,主资源目录和测试资源目录都可以超过一个,虽然会破坏 Maven 的约定,但 Maven 允许用户声明多个资源目录,并且为每个资源目录提供不同的过滤配置,如下:

<resources>     <resource>         <directory>${project.basedir}/src/main/resources</directory>         <filtering>true</filtering>     </resource>         <resource>         <directory>${project.basedir}/src/main/sql</directory>         <filtering>false</filtering>     </resource> </resources> 

如上,resources 开启资源过滤,而 sql 目录没有开启。

到目前为止一切基本就绪了,我们将数据库配置的变化部分提取成了 Maven 属性,在 POM 的profile 中定义了这些属性的值,并且为资源目录开启了属性过滤。最后,只需要在命令行激活 profile,Maven 就能够在构建项目的时候使用 profile 中属性值替换数据库配置文件中的属性引用。运行命令如下:

mvn clean install -Pdev  

mvn 的 -P 参数表示在命令行激活一个 profle。这里激活了 id 为 dev 的 profile。构建完成后,输出目录中的数据库配置就是开发环境的配置了,如下:

database.jdbc.driverClass = com.mysql.jdbc.Driver database.jdbc.connectionURL = jdbc:mysql:localhost://8081/test database.jdbc.username = dev database.jdbc.password = dev-pwd 

Mave Profile

从上面的内容可以看到,不同环境的构建很可能是不同的,典型的情况就是数据库的配置。除此之外,有些环境可能需要配置插件使用本地文件,或者使用特殊版本的依赖,或者需要一个特殊的构件名称。要想使得一个构建不做任何修改就能在任何环境下运行,往往是不可能的。为了能让构建在各个环境下方便地移植,Maven 引入了 profile 的概念。profile 能够在构建的时候修改 POM 的一个子集,或者添加额外的配置元素。用户可以使用很多方式激活 profile,以实现构建在不同环境下的移植。

1️⃣针对不同环境的profile

继续以上面介绍的数据库差异为例,引人了一个针对开发环境的 profile(id是dev),类似地,可以加入测试环境和产品环境的 profile,如下:

<profiles>     <profile>         <id>dev</id>         <properties>             <db.driver>com.mysql.jdbc.Driver</db.driver>             <db.url>jdbc:mysql://localhost:8081/test</db.url>             <db.username>dev</db.username>             <db.password>dev-pwd</db.password>         </properties>     </profile>     <profile>         <id>test</id>         <properties>             <db.driver>com.mysql.jdbc.Driver</db.driver>             <db.url>jdbc:mysql://192.168.1.100:3306/test</db.url>             <db.username>test</db.username>             <db.password>test-pwd</db.password>         </properties>     </profile> </profiles> 

同样的属性在两个 profile 中的值是不一样的,dev 的 profile 提供了开发环境数据库的配置,而 test profle 提供的是测试环境数据库的配置。类似地,还可以添加一个基于产品环境数据库配置的 profile。由于篇幅原因,在此不再赘述。现在,开发人员可以在使用 mvn 命令的时候在后面加上 -Pdev 激活 dev profile,而测试人员可以使用 -Ptest 激活 test profile。

2️⃣激活profile

为了尽可能方便用户,Maven 支持很多种激活 profile 的方式。

1.命令行激活

用户可以使用 mvn 命令行参数- P 加上 profle的 id 来激活 profile,多个 id 之间以逗号分隔。例如,下面的命令激活了 dev-x 和 dev-y 两个 profile:

mvn clean install -Pdev-x,dev-y 

2.settings文件显式激活

如果用户希望某个 profile 默认一直处于激活状态,就可以配置 settings.xml 文件的 activeProfles 元素,表示其配置的 profle 对于所有项目都处于激活状态,如下:

<settings> ... 	<activeProfiles> 		<activeProfile>dev-x</activeProfile> 	</activeProfiles> ... </settings> 

3.系统属性激活

用户可以配置当某系统属性存在的时候,自动激活 profile,如下:

<profiles>     <profile>     	<activation> 			<property> 				<name>test<name> 			</property> 		</activation>         ...     </profile> </profiles> 

可以进一步配置当某系统属性 test 存在,且值等于 x 的时候激活 profile,如下:

<profiles>     <profile>     	<activation> 			<property> 				<name>test<name> 				<value>x</value> 			</property> 		</activation>         ...     </profile> </profiles> 

最后,不要忘了,用户可以在命令行声明系统属性。例如:

mvn clean install -Dtest=x 

因此,这其实也是一种从命令行激活 profile的方法,而且多个 profile 完全可以使用同一个系统属性来激活。

4.操作系统环境激活

Profile 还可以自动根据操作系统环境激活,如果构建在不同的操作系统有差异,用户完全可以将这些差异写进 profile,然后配置它们自动基于操作系统环境激活,如下所示。

<profiles>     <profile>     	<activation> 			<os> 				<name>Windows XP<name> 				<family>Windows</family> 				<arch>x86<arch> 				<version>5.1.2600</version> 			</os> 		</activation>         ...     </profile> </profiles> 

这里 family 的值包括 Windows、UNIX 和 Mac 等,而其他几项 name、arch、version,用户可以通过查看环境中的系统属性os.name、os.arch、os.version 获得。

5.文件存在与否激活

Maven 能够根据项目中某个文件存在与否来决定是否激活 profile,如下所示。

<profiles>     <profile>     	<activation> 			<file> 				<missing>x.properties</missing> 				<exists>y.properties</exists> 			</file> 		</activation>         ...     </profile> </profiles> 

6.默认激活

用户可以在定义 profile 的时候指定其默认激活,如下所示。

<profiles>     <profile>     	<id>dev</dev>     	<activation>     		<activeByDefault>true</activeByDefault>     	</activation>     	...     </profile> </profiles> 

使用 activeByDefault 元素用户可以指定 profile 自动激活。不过需要注意的是,如果 POM 中有任何一个 profle 通过以上其他任意一种方式被激活了,所有的默认激活配置都会失效。如果项目中有很多的 profile,它们的激活方式各异,用户怎么知道哪些 profile 被激活了呢?maven-help-plugin 提供了一个目标帮助用户了解当前激活的 profile:

mvn help:active-profiles 

maven-help-plugin 还有另外一个目标用来列出当前所有的 profile:

mvn help:all-profiles 

3️⃣profile的种类

根据具体的需要,可以在以下位置声明 profile:

  • pom.xml:很显然,pom 文件中声明的 profile 只对当前项目有效。
  • 用户settings.xml:用户目录下,.m2/settings.xml 中的 profile 对本机上该用户所有的 Maven 项目有效。
  • 全局settings.xml:Maven安装目录下,conf/settings.xml 中的 profile 对本机上所有的 Maven 项目有效。
  • profiles.xml(Maven 2):还可以在项目根目录下使用一个额外的 profiles.xml 文件来声明 profile,不过该特性已经在 Maven3 中被移除。建议用户将这类 profile 移到 settings.xml中。

为了不影响其他用户且方便升级 Maven,用户应该选择配置用户范围的 settings.xml,避免修改全局范围的 settings.xml文件。也正是因为这个原因,一般不会在全局的 settings.xml 文件中添加 profile。像 profiles.xml 这样的文件,默认是不会被 Maven 安装到本地仓库,或者部署到远程仓库的。因此一般来说应该避免使用,Maven3 也不再支持该特性。但如果在用Maven2,而且需要为几十或者上百个客户执行不同的构建,往 POM 中放置这么多的 profile 可能就不太好。这时可以选择使用 profles.xml。

不同类型的 profile 中可以声明的 POM 元素也是不同的,pom.xml 中的 profile 能够随着pom.xml 一起被提交到代码仓库中、被 Maven 安装到本地仓库中、被部署到远程 Maven 仓库中。换言之,可以保证该 profile 伴随着某个特定的 pom.xml 一起存在,因此它可以修改或者增加很多 POM 元素,如下。

<project>     <repositories></repositories>     <pluginRepositories></pluginRepositories>     <distributionManagement></distributionManagement>     <dependencies></dependencies>     <dependencyManagement></dependencyManagement>     <modules></modules>     <properties></properties>     <reporting></reporting>     <build>         <plugins></plugins>         <defaultGoal></defaultGoal>         <resources></resources>         <testResources></testResources>         <finalName></finalName>     </build> </project> 

可以看到,可供 pom 中 profile 使用的元素非常多,在 pom profile 中用户可以修改或添加仓库、插件仓库以及部署仓库地址;可以修改或者添加项目依赖;可以修改聚合项目的聚合配置;可以自由添加或修改Maven属性;添加或修改项目报告配置;pom profile 还可以添加或修改插件配置、项目资源目录和测试资源目录配置以及项目构件的默认名称。

与 pom.xml 中的 profile 对应的,是其他三种外部的 profile,由于无法保证它们能够随着特定的 pom.xml 一起被分发,因此 Maven 不允许它们添加或者修改绝大部分的 pom 元素

举个简单的例子。假设用户 Jack 在自己的 settings.xml 文件中配置了一个 profile ,为了让项目A构建成功,Jack 在这个 profile 中声明几个依赖和几个插件,然后通过激活该 profle 将项目构建成功了。但是,当其他人获得项目A的源码后,它们并没有 Jack settings.xml 中的 profile ,因此它们无法构建项目,这就导致了构建的移植性问题。为了避免这种问题的出现,Maven 不允许用户在 settings.xml 的 profile 中声明依赖或者插件。事实上,在 pom.xml 外部的 profile 只能够声明如下所示几个元素。

<project>     <repositories></repositories>     <pluginRepositories></pluginRepositories>     <properties></properties> </project> 

现在不用担心 POM 外部的 profile 会对项目产生太大的影响了,事实上这样的 profile 仅仅能用来影响到项目的仓库和 Maven 属性。

广告一刻

为您即时展示最新活动产品广告消息,让您随时掌握产品活动新动态!