标准的重要性已不用过多强调,想象一下,如果不是所有程序员都基于HTTP协议开发Web应用,互联网会乱成怎样。各个版本的IE、Firefox等浏览器之间的差别已经让很多开发者头痛不已。而Java成功的重要原因之一就是他能屏蔽大部分操作系统的差异,XML流行的原因之一是所有语言都接受它。Maven当然还不能和这些即成功又成熟的技术相比,但Maven的用户都应该清楚,Maven提倡“约定优于配置”,这是Maven最核心的核心理念之一。
那么为什么要使用约定而不是自己更灵活的配置呢?原因之一是,使用约定可以大量减少配置。先看一个简单地Ant配置文件,如下所示。
<project name="my-project" default="dist" basedir=".">
<description>
simple example build file
</description>
<!- 设置构建的全局属性 -->
<property name="src" location="src/main/java" />
<property name="build" location="target/classes" />
<property name="dist" location="target"/>
<target name="init">
<!- 创建时间戳 -->
<tstamp />
<!- 创建编译使用的构建目录 -->
<mkdir dir="${build}" />
</target>
<target name="compile" depends="init" description="compile the source">
<!- 将java代码从目录${src}编译至${build} -->
<javac srcdir="${src}" destdir="${build}" />
</target>
<target name="dist" depends="compile" description="generate the distribution">
<!-创建分发目录-->
<mkdir dir="${dist}/lib" />
<!- 将${build}目录的所有内容打包至MyProject-${DSTAMP}.jar file-->
<jar jarfile="${dist}/lib/MyProject-${DSTAMP}.jar" basedir="${build}"/>
</target>
<target name="clean" description="clean up">
<!- 删除${build}和${dist}目录树 -->
<delete dir="${build}" />
<delete dir="${dist}"/>
</target>
</project>
这段代码做的事情就是清除构建目录、创建目录、编译代码、复制依赖至目标目录,最后打包。这时一个项目构建要完成的最基本的事情,不过为此还是需要写很多的XML配置:源码目录是什么、编译目标目录是什么、分发目录是什么,等等。用户还需要记住各种Ant任务命令,如delete、mkdir、javac和jar。
做同样的事情,Maven需要什么配置呢?Maven只需要一个最简单的POM,见下面。
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>com.test</groupId>
<artifactId>my-project</artifactId>
<version>1.0</version>
</project>
这段配置简单的令人惊奇,但为了获得这样简洁的配置,用户是需要付出一定的代价的,那就是遵循Maven的约定。Maven会假设用户的项目是这样的:
- 源码目录为src/main/java
- 编译输出目录为target/classes/
- 打包方式为jar
- 包输出目录为target/
遵循约定虽然损失了一定的灵活性,用户不能随意安排目录结构,但是却能减少配置。更重要的是,遵循约定能够帮助用户遵守构建标准。
如果没有约定,10个项目可能使用10种不同的项目目录结构,这意味着交流学习成本的增加,当新成员加入项目的时候,他就不得不花时间去学习这种构建配置。而有了Maven的约定,大家都知道什么目录放什么内容。此外,与Ant的自定义目标名称不同,Maven在命令行暴露的用户接口是统一的,像mvn clean install 这样的命令可以用来构建几乎任何的Maven项目。
也许这时候会问,如果我不想遵守约定该怎么办?这时,请首先问自己三遍,你真的需要这么做吗?如果仅仅是因为喜好,就不要耍个性,个性往往意味着牺牲通用性,意味着增加无谓的复杂度,例如,Maven允许你自定义源码目录,如下所示。
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>com.test</groupId>
<artifactId>my-project</artifactId>
<version>1.0</version>
<build>
<sourceDirectory>src/java</sourceDirectory>
</build>
</project>
该例中源码目录就成了src/java而不是默认的src/main/java。但这往往会造成交流问题,习惯Maven的人会奇怪,源代码去哪里了?当这种自定义大量存在的时候,交流成本就会大大提高。只有在一些特殊的情况下,这种自定义配置的方式才应该被正确使用以解决实际问题。例如你在处理遗留代码,并且没有办法更改原来的目录结构,这个时候就只能让Maven妥协。
任何一个Maven项目都隐式的继承自超级POM,这有点类似于任何一个Java类都隐式的继承于Object类。因此,大量超级POM的配置都会被所有Maven项目继承,这些配置也就成为了Maven所提倡的约定。
对于Maven 3,超级POM在文件 $ MAVEN_HOME/lib/maven-model-builder-x.x.x.jar中的org/apache/maven/model/pom-4.0.0.xml路径下。对于Maven2,超级POM在文件$ MAVEN_HOME/lib/maven-x.x.x-uber.jar中的org/apache/maven/project/pom-4.0.0.xml目录下。这里的x.x.x表示Maven的具体版本。
Maven设定核心插件的原因是防止由于插件版本的变化而造成构建不稳定。
超级POM实际上很简单,但从POM我们就能知晓Maven约定的由来,不仅理解了什么是约定,为什么要遵循约定,还能明白约定是如何实现的。