原文地址:http://maven.apache.org/guides/introduction/introduction-to-profiles.html
简介
Apache Maven2.0竭尽所能让构建是可移植的。这意味着在pom.xml中配置构建内容,避免引用其它的文件(包括:继承、依赖和其它的方式),并使用本地repository来存储元数据的方式,来确保它的这一特性。
然而,有时无法做到彻底的兼容。可能我们会需要使用插件(plugins)来配置一些本地文件系统路径,也可能会有一些在依赖上的细微差别,项目的artifact也可能发生一些变化。甚至会需要根据不同的构建环境,在构造周期中(build lifecycle),引入一些不同的插件。
为处理这些场景,Maven2.0引入了build profile的概念。Profiles通过使用POM的一个子结点来指定一些内容,并可以通过多种方式来触发。Profiles通过设置一组相同名称,不同值的额外目标环境参数来达到这个目的(比如:在开发、测试和生产环境下的应用的根目录)。同样的,profiles可以方便的指定同团队成员的构建时得到不同的结果。同时,如果处理得当,在使用profiles时可以不破环项目的可移植性。在构建时,调用同一POM文件,使用-f参数,调用不同的参数和配置执行构建过程,可以更方便的进行维护。
profile的类型和定义位置
- 每个Project
定义在pom.xml文件中 - 每个用户
定义在Maven-setting中(%USER_HOME%/.m2/settings.xml) - 全局的
定义在global Maven-settings中(%M2_HOME%/conf/settings.xml)
如何触发profile
一个Profile可以通过以下方式触发:
- 明确指定
- 通过Maven配置
- 通过环境变量
- 操作系统设置
- 判断文件是否存在
详细说明
可以通过-P命令行参数来指定生效的Profiles。
该参数值为使用逗号分隔的profile-id。该参数可以指定为在当前配置中或在settings.xml文件的<activePorfile>
元素中的内容profile.
mvn groupId:artifactId:goal -P profile-1,profile-2
Profiles可以在Maven Settings的<activeProfiles>
中配置。该配置中包含一个或多个<activeProfile>
元素,每一个都包含一个profile-id节点。
<setting>
...
<activeProfiles>
<activeProfile>profile-1</activeProfile>
</activeProfiles>
...
</setting>
定义在此处的Profiles将默认在每一个project中使用。
Profiles可以根据检测构建环境来自动触发。这些内容也配置在<activation>
中。通常,使用这种配置来限制JDK的版本,检查系统变量在存在和变量的值。下在这个示例将在JDK1.4及以上版本中触发:
<profiles>
<profile>
<activation>
<jdk>1.4</jdk>
</activation>
...
</profile>
</profiles>
从Maven2.1开始,可以指定范围(Enforcer Version Range Syntax),下面的示例表示JDK版本为1.3, 1.4和1.5时触发:
<profiles>
<profile>
<activation>
<jdk>[1.3,1.6)</jdk>
</activation>
...
</profile>
</profiles>
下面这个示例则根据操作系统来决定是否生效:
<profiles>
<profile>
<activation>
<os>
<name>Windows XP</name>
<family>Windows</family>
<arch>x86</arch>
<version>5.1.2600</version>
</os>
</activation>
...
</profile>
</profiles>
下面这个示例则在系统指定debug为任意值时生效:
<profiles>
<profile>
<activation>
<property>
<name>debug</name>
</property>
</activation>
...
</profile>
</profiles>
下面这个示例在指定environment为test值时生效:
<profiles>
<profile>
<activation>
<property>
<name>environment</name>
<value>test</value>
</property>
</activation>
...
</profile>
</profiles>
可以使用如下命令使上述profile生效:
mvn groupId:artifactId:goal -Denvironment=test
从Maven3.0开始,POM中的profiles可以通过settings.xml中的激活的profiles中的参数,来决定是否激活。
下面这个示例在缺少文件时触发:
<profiles>
<profile>
<activation>
<file>
<missing>target/generated-sources/axistools/wsdl2java/org/apache/maven</missing>
</file>
</activation>
...
</profile>
</profiles>
从Maven2.0.9开始,可以使用标签<exists>
和<missing>
。支持一些系统变量,如
user.home和环境变量
{env.HOME}。注意,定义在POM自身中的一些变量无法在此使用,如${project.build.director}。
下面这个示例将默认被调用:
<profiles>
<profile>
<id>profile-1</id>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
...
</profile>
</profiles>
该profile将自动的被调用,在没有指定其它profile的情况下。通过命令行或者激活配置指定profile时,所有的默认profile将不会被触发。
禁止profile
从Maven2.0.10开始,可以使用’!’或’-‘来禁止profile的触发:
mvn groupId:artifactId:goal -P !profile-1,!profile-2
可用使用该处理来禁止默认的profile和通过配置来激活的profile。
哪些内容可以使用哪种类型的profile来定制
我们已经说明过在哪里指定profiles和如何激活它们,它们对熟悉如何指定profile内容有密切的联系。与其它内容一样,profile的配置也不简单。
根据配置profile位置的不同,配置选项也有一定的区别。
在外部配置Profiles
严格意义上来说,在外部指定profiles(如settings.xml,profiles.xml)是不可移植的。任何可能高度改变构建结果的配置都被限制仅可配置在POM文件自身中。如一些改变代码仓库的配置,但不可改变构建输出的路径。因此,我们仅可以在这部分配置中修改<repositories>
和<pluginRepositories>
和一些附加的<properties>
。
<properties>
可以指定一些自定义的键值,并在处理POM时使用。如可以指定${profile.provided.path}。
在POM中定义Profiles
在POM中指定profiles,会有更多的选项。相对应的,在这种类型的profile中,只可以修改本项目和子模块。使用这种方式配置,可以更好的保持可移植性,可以增加更多的说明,让使用者使用这些内容。
在POM中定义的Profiles可以使用以下的元素:
<repositories>
<pluginRepositories>
<dependencies>
<plugins>
<properties>
<reporting>
<modules>
<dependencyManagement>
<distributionManagement>
- 和一些build的子元素:
<defaultGoal>
<resource>
<testResources>
<finalName>
<profile>
以外的POM元素
profiles无法修改在结点之外的POM元素,因为当POM发布到存储仓库时,运行时的修改是无法保证同步的,可以导致构建时得到完全不一样的结果。尽管使用这些外部的配置,产生的问题是有限的。另一个原因是POM可能复用父POM的一些信息。
外部的文件,如settings.xml和profiles.xml同样不可修改POM-profiles之外的内容。让我们详细的说明下这个过程。当一个有效的POM上传到远程的仓库时,任何人都可能下载并构建这个Maven项目。假如,我们可以依赖一些重要的profiles,或者一些元素在settings.xml中。这时,一些我们无法预期的人就无法构建项目。同时我们需要考虑如何将settings.xml共享给其它人。请记住,过多的配置文件容易引起混乱,并给维护带来困难。如果它是构建的数据,则应当被包含在POM中。Maven2的一个目标就是将构建所需的内容维持在一个文件中,或者POM文件层次结构中。
Profile 陷阱
我们已经提到使用profile可能对项目可移植性破坏的可能性。也给出了破坏项目可移植性的案例。接下来会再使用一个章节来讨论,如何避免使用Profile的误区。
在使用Profiles时应当注意两个问题。一是外部参数,通常在是在plugin配置中。这些会破坏项目的可移植性。另一个是自定义profile定义不完整。
外部参数
外部参数指在pom.xml之外定义一些参数。最常见的是POM中plugin配置。当缺少这些细小的参数时,构建会失败。例如,在settings.xml中指定appserver paths
,并在pom中使用该字段,则团队中其它成员没有在settings.xml中指定该参数时,会构建失败。如下:
pom.xml
<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>
~/.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>
当你配置好这些内容后,可以成功构建integration-test。但当你同事构建integration-test时,会失败。
此时,你的项目完全不可移植。使用在pom.xml中内置profile的方式则可以解决这个问题。同时Maven提供的丰富的项目层级支持,可以在团队级别的配置或类似文件中,通过<pluginManagement>
标签来指定这些配置。
另外的答案可能是开发环境的规范。然而,它可能会妥协于Maven给生产力带来的提升。
不完整的自定义profile设置
除了上述可移植性的破坏,你的配置文件很可能无法涵盖所有的场景。如下面的pom.xml片段:
<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中的profiles:
<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>
上面这个profile与上一个示例非常类似,并包含一些差异:显然与开发环境相关的配置,引入了一个新的profileappserverConfig-dev-2
。在“env = dev”时,appserverConfig-dev将被触发。“env = dev-2”时,将触发appserverConfig-dev-2。执行:
mvn -Denv=dev-2 integration-test
将成功构建,properties将应用appserverConfig-dev-2
中设置的值。执行:
mvn -Denv=dev integration-test
将成功构建,并应用appserverConfig-dev
中的参数值。然而,执行:
mvn -Denv=production integration-test
时,会构建失败。因为${appserver.home}
没有设置有效的路径,无法发布和测试Web应用。在编写profiles时,没有考虑到”production”这种场景。与”production”类似,”test”甚至”local”这些场景,都可能出现在integration-test的构建周期中。不完整的指定,可以有效的限制可用的目标场景。但你的团队和你的上司,可能无法注意到这些东西。当你配置profiles时,确保已经考虑到所有场景。
为不同的场景使用不同的Profile是让新团队成员遵循这些内容的关键,并让新成员快速的适应。再一次说明,确保考虑了所有的场景。
在构建时,如何指定生效的profiles
决定生效的profiles将有助于知晓构建时运行的详细profiles。我们可以使用Maven Help Plugin来获知哪个profiles将在构建时生效。
mvn help:active-profiles
mvn help:active-profiles -Denv=dev
让我们使用更多的例子,来理解active-profiles。
从最后一个例子中,可以注意到有两个profiles,分别命名为:appserverConfig-dev
和appserverConfig-dev-2
,并为参数设置了不同的值。如果我们继续执行:
mvn help:active-profiles -Denv=dev
将会是设置”env=dev”参数时激活的profiles id的列表,如:
The following profiles are active:
- appserverConfig-dev (source: pom)
如果在settings.xml中定义了profile,并设置为激活的profiles,执行后将显示:
mvn help:active-profiles
结果:
The following profiles are active:
- appserverConfig (source: settings.xml)
就像之前提到的,定义在settings.xml中的profile会被自动激活。所以我们没有指定任何激活的参数,仍然有一个激活的profile被列出。
现在,如果在settings.xml中设置了一个激活的profile,并且触发一个在POM中的profile,你认为构建时哪些profile将会生效?
mvn help:active-profiles -P appserverConfig-dev
结果如下:
The following profiles are active:
- appserverConfig-dev (source: pom)
- appserverConfig (srouce: settings.xml)
虽然列出了两个激活的profiles,我们还是无法确认哪个一profile被调用了。可以使用如下命令查看构建效果:
mvn help:effective-pom -P appserverConfig-dev
将输出生效的构建pom内容到控制台。记住,配置在settings.xml中的比配置的POM本身里的profile拥有更高的优先级。因此,此处将使用appserverConfig而不是appserverConfig-dev。如果想将输出内容写入到文件中,可以使用命令行参数-Doutput=effective-pom.xml
。
命令约定
到现在,你应该意识到profiles是一种原生的方式用来解决,不同目标环境下,需使用不同的路径下的配置文件来构建的问题。上文中,我们讨论了如何使用profiles来解决这些问题,以及完整考虑不同场景profiles的重要性。
同时,如何组织和管理这些profiles也是十分重要的。一个优异的程序员会努力去编写能自我解释的代码,同样,让你的profile id能准确表达它的作用,也是十分重要的。一个比较好的方式是,使用触发它的系统变量作为它的命名,例如:env-dev, env-test和env-prod。如此,就能直接的表明什么时候该触发哪一个profile。当你在测试环境时,你应当激活env-test:
mvn -Denv=test <phase>