bboyjing's blog

Maven学习笔记四【Profiles】

本节来学习下Maven的Profiles

介绍Build Profiles

Apache Maven 2.0竭尽全力确保构建是可移植的。除其他之外,这意味着允许在POM中进行构建配置,避免所有文件系统的引用。并且更加依赖本地存储库来存储实现这一点所需的元数据。

然而,有时可移植性并不是完全可能的。某些条件下,插件可能需要配置本地文件系统路径。在其他情况下,需要一个稍微不同的依赖集,并且项目的artifact名称可能需要稍微调整一下。在另一种情况下,甚至可能需要在构建生命周期中包含一个完整的插件,这取决于所检测到的构建环境。

为了解决这些情况,Maven 2.0引入了构建概要文件的概念。配置文件使用POM本身可用元素的子集(外加一个额外的部分)来指定,并以各种方式触发。他们在构建时修改POM,并用于互补的集合中,以便为一组目标环境提供等效但不同的参数(例如,在开发、测试和生产环境中提供appserver根的路径)。因此,概要文件很容易导致来自团队不同成员的构建结果不同。但是,如果使用得当,可以在使用概要文件的同时仍然保持项目的可移植性。这也将最小化maven -f选项的使用,该选项允许用户使用不同的参数或配置创建另一个POM,从而使其更易于维护,因为它只在一个POM上运行。

配置文件的不同类型以及定义在哪里

如何触发Profile

配置文件可以通过以下几种方式触发/激活:

  • Explicitly - 显示地
  • Through Maven settings - 通过Maven设置
  • Based on environment variables - 基于环境变量
  • OS settings - 操作系统设置
  • Present or missing files - 提交或丢失文件

配置文件激活的详细信息

可以使用-P CLI选项显式指定概要文件。此选项接受一个参数,该参数是要使用的概要文件id的逗号分隔列表。当指定此选项时,除了激活已配置的概要文件外,还将激活选项参数中指定的概要文件。

1
mvn groupId:artifactId:goal -P profile-1,profile-2

可以通过<activeProfiles>标签在maven设置中激活概要文件。如下,接受<activeProfiles>元素地列表,每个元素都包含一个概要文件id:

1
2
3
4
5
6
7
<settings>
...
<activeProfiles>
<activeProfile>profile-1</activeProfile>
</activeProfiles>
...
</settings>

<activeProfiles>标签中列出的配置文件将在每次项目使用时被默认激活。

可以根据构建环境的检测状态自动触发概要文件。这些触发器是通过概要文件中<activation>部分指定的。 目前,这种检测仅限于JDK版本的前缀匹配、系统属性的存在与否或系统属性的值。下面有些例子:

当JDK的版本以”1.4”开头时,以下配置将触发配置文件:

1
2
3
4
5
6
7
8
<profiles>
<profile>
<activation>
<jdk>1.4</jdk>
</activation>
...
</profile>
</profiles>

范围匹配也可以在Maven 2.1中使用:

1
2
3
4
5
6
7
8
<profiles>
<profile>
<activation>
<jdk>[1.3,1.6)</jdk>
</activation>
...
</profile>
</profiles>

下面将基于操作系统设置激活。有关操作系统值的更多细节,请参见Maven execucer插件。

1
2
3
4
5
6
7
8
9
10
11
12
13
<profiles>
<profile>
<activation>
<os>
<name>Windows XP</name>
<family>Windows</family>
<arch>x86</arch>
<version>5.1.2600</version>
</os>
</activation>
...
</profile>
</profiles>

当系统属性”debug”指定为任意值时,将激活以下配置文件:

1
2
3
4
5
6
7
8
9
10
<profiles>
<profile>
<activation>
<property>
<name>debug</name>
</property>
</activation>
...
</profile>
</profiles>

当系统属性”debug”没有被定义时,将激活以下配置文件:

1
2
3
4
5
6
7
8
9
10
<profiles>
<profile>
<activation>
<property>
<name>!debug</name>
</property>
</activation>
...
</profile>
</profiles>

如果没有定义系统属性”debug”,或者定义的值不为”true”,则会激活以下概要文件;

1
2
3
4
5
6
7
8
9
10
11
<profiles>
<profile>
<activation>
<property>
<name>debug</name>
<value>!true</value>
</property>
</activation>
...
</profile>
</profiles>

要激活它,你需要在命令行中输入其中一个:

1
2
mvn groupId:artifactId:goal
mvn groupId:artifactId:goal -Ddebug=false

下个示例,当环境变量”environment”被指定到”test”时,出发概要文件:

1
2
3
4
5
6
7
8
9
10
11
<profiles>
<profile>
<activation>
<property>
<name>environment</name>
<value>test</value>
</property>
</activation>
...
</profile>
</profiles>

要激活它,可以在命令行输入:

1
mvn groupId:artifactId:goal -Denvironment=test

从Maven 3.0开始,POM中的配置文件也可以基于settings.xml中配置的active profiles属性被激活。

注意:像FOO这样的环境变量可以作为表单env.FOO的属性使用。进一步注意,环境变量名在Windows上被规范化为所有大写。

当生成文件target/generated-sources/axistools/wsdl2java/org/apache/maven丢失时,下面示例将触发概要文件:

1
2
3
4
5
6
7
8
9
10
<profiles>
<profile>
<activation>
<file>
<missing>target/generated-sources/axistools/wsdl2java/org/apache/maven</missing>
</file>
</activation>
...
</profile>
</profiles>

在Maven 2.0.9中,可以插入标记<exists><missing>。支持的变量是系统属性,如${user.home}${env.HOME}。请注意,POM本身定义的属性和值在这里不能用于插值。例如上面的示例触发器,不能使用${project.build.directory},只能硬编码路径。

配置文件也可以默认激活,使用如下配置:

1
2
3
4
5
6
7
8
9
<profiles>
<profile>
<id>profile-1</id>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
...
</profile>
</profiles>

这个概要文件将作用于所有构建,除非使用上面列举的方法之一激活同意POM中的另一个概要文件。当POM中的配置文件在命令行上或通过其激活配置文件被激活时,默认激活的所有配置文件将自动停用。

停用配置文件

从Maven 2.0.10开始,可以使用命令行停用一个或多个概要文件,方法是在它们的标识符前加上字符’!’或’-‘:

1
mvn groupId:artifactId:goal -P !profile-1,!profile-2

这可以用于停用标记为activeByDefault的配置文件,或者通过其激活配置激活的配置文件。

POM的哪些区域可以根据每种类型的概要文件进行定制

既然我们已经讨论了在哪里指定概要文件,以及如何激活它们,那么我们可以在概要文件中指定什么将是非常有用的。与配置文件的其他方面一样,这个答案并不简单。

根据选择配置概要文件的位置,可以访问不同的POM配置选项。

外部文件中的配置文件

在外部文件中指定配置文件(也就是说在settings.xml或 profiles.xml文件中)是不可移植的。任何看起来很有可能更改构建结果的内容都仅限于POM中的内联概要文件。像存储库列表这样的东西,可能仅仅是已批准项目的专有存储库,并且不会改变构建的结果。因此,将只能修改< repository >< pluginrepository >部分,外加一个额外的<properties>部分。

<properties>部分允许指定自由形式的键-值对,这些键-值对将包含在POM的插值过程中。这允许以${profile. provied .path}的形式指定插件配置。

POM中的配置文件

另一方面,如果概要文件可以在POM中合理地指定,那么将有更多的选择空间。当然,代价是只能修改当前项目以及子模块。由于这些概要文件是内联指定的,因此有更好的机会保持可移植性。因此,有理由说可以向它们添加更多的信息,而不会有其他用户无法获得这些信息的风险。

POM中指定的配置文件可以修改以下POM元素:

  • <repositories>
  • <pluginRepositories>
  • <dependencies>
  • <plugins>
  • <properties> (实际上在主POM中不可用,但是在后台使用)
  • <modules>
  • <reporting>
  • <dependencyManagement>
  • <distributionManagement>
  • <build> 元素的子集,包含如下:
    • <defaultGoal>
    • <resources>
    • <testResources>
    • <finalName>

<profiles>之外的POM元素

不允许在POM-profiles之外修改某些POM元素,因为当POM部署到存储库系统时,这些运行时修改将不会被分发,从而使该人员对该项目的构建与其他人完全不同。虽然可以在一定程度上通过为外部概要文件提供的选项做到这一点,但是风险是有限的。另一个原因是这个POM信息有时会从父POM中重用。

外部文件,如 settings.xml and profiles.xml,也不支持POM-profiles之外的元素。让我们以这个场景为详细说明。当有效的POM部署到远程存储库时,任何人都可以从存储库中提取它的信息,并使用它直接构建Maven项目。现在,想象一下,如果我们可以在依赖项中设置对构建非常重要的概要文件,或者在settings.xml文件中的POM-profiles之外的任何其他元素设置概要文件。那么,很可能我们不能期望其他人使用存储库中的POM并能够构建它。我们还必须考虑如何与其他人共享settings.xml文件。注意,太多的文件配置是非常令人困惑的,并且难以维护。底线是,既然它是构建数据,所以就应该在POM中。Maven 2的目标之一是将运行构建所需的所有信息合并到单个文件中,或POM的文件层次结构中。

配置文件陷阱

我们已经提到,向构建中添加概要文件可能会破坏项目的可移植性。我们甚至强调了概要文件可能破坏项目可移植性的情况。但是,有必要重申这些观点,以便更连贯地讨论在使用概要文件时要避免的一些缺陷。

在使用概要文件时,有两个主要问题需要记住。首先是外部属性,通常用于插件配置。这些可能会破坏项目的可移植性。另一个更微妙的方面是对一组自然概要文件的不完整规范。

外部属性

外部属性定义涉及pom.xml文件之外,但没有在其内部相应的配置文件中定义的任何属性值。POM中属性最明显的用法是在插件配置中。虽然没有属性也有可能破坏项目的可移植性,但是这些东西可能具有导致构建失败的微妙影响。例如,在settings.xml的概要文件中指定appserver路径。当团队中另一个用户视图在没有类似的settings.xml的情况下构建时,可呢个导致集成测试失败。考虑如下web项目的pom.xml片段:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<project>
...
<build>
<plugins>
<plugin>
<groupId>org.myco.plugins</groupId>
<artifactId>spiffy-integrationTest-plugin</artifactId>
<version>1.0</version>
<configuration>
<appserverHome>${appserver.home}</appserverHome>
</configuration>
</plugin>
...
</plugins>
</build>
...
</project>

现在,在本地${user.home}/.m2/settings.xml中,将有如下配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<settings>
...
<profiles>
<profile>
<id>appserverConfig</id>
<properties>
<appserver.home>/path/to/appserver</appserver.home>
</properties>
</profile>
</profiles>
<activeProfiles>
<activeProfile>appserverConfig</activeProfile>
</activeProfiles>
...
</settings>

构建集成测试生命周期阶段时,因为您提供的路径允许测试插件安装和测试这个web应用程序,集成测试通过。

但是,当其他同事试图构建集成测试时,他的构建非常失败,抱怨它不能解决插件配置参数<appserverHome>,或者更糟糕的是,该参数值-字面意思${appserver.home}无效。

恭喜,现在的项目是不可移植的。将这个配置内联到你的pom.xml中,可以帮助缓解这一问题,但它有一个明显的缺点:现在每个项目层次结构必须指定此信息。由于Maven为项目继承提供了良好的支持,所以可以将这种配置保留在团队级POM的<pluginManagement>部分或类似的部分中,并简单地继承路径。

另一个不太吸引人的答案可能是开发环境的标准化。然而,这往往会损害Maven能够提供的生产力增益。

自然轮廓集的不完整规范

除了上述可移植性破坏因素之外,概要文件很容易无法涵盖所有情况。当你这样做的时候,你通常会离开你的目标环境。让我们以pom.xml为例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<project>
...
<build>
<plugins>
<plugin>
<groupId>org.myco.plugins</groupId>
<artifactId>spiffy-integrationTest-plugin</artifactId>
<version>1.0</version>
<configuration>
<appserverHome>${appserver.home}</appserverHome>
</configuration>
</plugin>
...
</plugins>
</build>
...
</project>

现在,考虑以下概要文件,它将在pom.xml中内联指定:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<project>
...
<profiles>
<profile>
<id>appserverConfig-dev</id>
<activation>
<property>
<name>env</name>
<value>dev</value>
</property>
</activation>
<properties>
<appserver.home>/path/to/dev/appserver</appserver.home>
</properties>
</profile>
<profile>
<id>appserverConfig-dev-2</id>
<activation>
<property>
<name>env</name>
<value>dev-2</value>
</property>
</activation>
<properties>
<appserver.home>/path/to/another/dev/appserver2</appserver.home>
</properties>
</profile>
</profiles>
..
</project>

执行如下命令:

1
mvn -Denv=dev-2 integration-test

构建成功,并且应用ID为appserverConfig-dev-2概要文件给出的属性。

执行如下命令:

1
mvn -Denv=dev integration-test

构建成功,并且应用ID为appserverConfig-dev概要文件给出的属性。

但是,执行如下命令:

1
mvn -Denv=production integration-test

为什么不会构建成功呢。因为所得到的${appserver.home}的非插值字面值不是部署和测试web应用的有效路径。在编写配置文件时,我们没有考虑生产环境的情况。生产环境、以及测试甚至是本地环境构成了一组自然的目标环境,我们可能希望为其构建集成测试生命周期阶段。这个自然集的不完整规范意味着我们将有效的目标环境有效地限制到开发环境。团队的其他成员可能看不到其中的奥妙。所以当构建概要文件来处理诸如此类的情况时,请确保处理整个目标集。

另外,用户特定的配置文件也可以以类似的方式工作。这意味着,当团队添加新开发人员时,以用户为主键配置的不同环境的概要文件就会生效。猜想这对新手来说是个有用的训练,但如果用这种方式把它们扔给狼群就不好了。同样,一定要考虑整个配置文件集。

如何知道在构建过程中哪些概要文件是有用的

确定活动概要文件将帮助用户了解在构建期间执行了哪些特定概要文件。我们可以使用Maven Help插件来判断在构建过程中哪些配置文件是有效的。

1
mvn help:active-profiles

我们弄个小例子来消化下,沿用之前的my-app项目,修改pom.xml文件,添加如下内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
~❯ vim ~/my-app/pom.xml
<project>
...
<profiles>
<profile>
<id>appserverConfig-dev</id>
<activation>
<property>
<name>env</name>
<value>dev</value>
</property>
</activation>
<properties>
<appserver.home>/path/to/dev/appserver</appserver.home>
</properties>
</profile>
<profile>
<id>appserverConfig-dev-2</id>
<activation>
<property>
<name>env</name>
<value>dev-2</value>
</property>
</activation>
<properties>
<appserver.home>/path/to/another/dev/appserver2</appserver.home>
</properties>
</profile>
</profiles>
..
</project>

执行如下命令:

1
2
3
4
5
~❯ cd my-app/
~❯ mvn help:active-profiles -Denv=dev
输出将会包含如下内容:
The following profiles are active:
- appserverConfig-dev (source: com.mycompany.app:my-app:1.0-SNAPSHOT)

从上面的结果可以看出profile的ID,以及其来源。

下面我们在settings.xml文件中声明了一个概要文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
~❯ vim ~/.m2/settings.xml
<settings>
...
<profiles>
<profile>
<id>appserverConfig</id>
<properties>
<appserver.home>/path/to/appserver</appserver.home>
</properties>
</profile>
</profiles>
<activeProfiles>
<activeProfile>appserverConfig</activeProfile>
</activeProfiles>
...
</settings>

测试下:

1
2
3
4
5
~❯ cd my-app/
~/my-app❯ mvn help:active-profiles
输出将会包含如下内容:
The following profiles are active:
- appserverConfig (source: external)

如上所示,输出正式settings.xml中配置的profile ID,而且source的值变成external,表示是外部配置的。

如果在setting.xml中设置了默认激活的配置文件,但是pom文件中也激活了配置文件,哪个配置文件会对构建产生影响呢:

1
2
3
4
5
6
~❯ cd my-app/
~/my-app❯ mvn help:active-profiles -P appserverConfig-dev
输出将包含如下内容:
The following profiles are active:
- appserverConfig (source: external)
- appserverConfig-dev (source: com.mycompany.app:my-app:1.0-SNAPSHOT)

尽管它列出了两个活动概要文件,但我们不确定其中哪个已经应用。要查看对构建执行的影响:

1
2
3
4
5
6
7
~❯ cd my-app/
~/my-app❯ mvn help:effective-pom -P appserverConfig-dev
输出包含如下内容:
...
<properties>
<appserver.home>/path/to/appserver</appserver.home>
</properties>

这会将此构建配置的有效POM打印到控制台。 从输出可以看出<appserver.home>的值是settings.xml中设置的。所以settings.xml比pom.xml中配置的概要文件拥有更高的优先级。

通过命令行参数-Doutput=effective-pom.xml,可以将上述输出内容重定向到指定的文件中。

命名约定

到目前为止,我们已经注意到概要文件是解决针对不同目标环境的不同构建配置需求问题的一种自然的方法。上面,我们讨论了解决这种情况的“自然配置文件集”的概念,以及考虑需要的整个配置文件集的重要性。

然而,如何组织和管理该集合的演化问题也很重要。正如优秀的开发人员努力编写自我文档化的代码一样,重要的是概要ID能够表示它们的预期用途。这样做的一个好方法是使用公共系统属性触发器作为概要文件名称的一部分。这可能导致诸如env-dev、env-test和env-prod之类的名称用于由系统属性env触发的概要文件。这样的系统在如何激活针对特定环境的构建方面留下了非常直观的提示。因此,要激活测试环境的构建,只需要通过发出以下命令来激活env-test:

1
mvn -Denv=test <phase>