Apache Maven的定义:Maven是一个项目管理工具,它包含了一个项目对象模型 (Project Object Model),一组标准集合,一个项目生命周期(Project Lifecycle),一个依赖管理系统(Dependency ManagementSystem),和用来运行定义在生命周期阶段(phase)中插件(plugin)目标(goal)的逻辑。当你使用Maven的时候,你用一个明确定义的项目对象模型来描述你的项目,然后Maven可以应用横切的逻辑,这些逻辑来自一组共享的(或者自定义的)插件。
约定优于配置(Convention Over Configuration)
系统,类库,框架应该假定合理的默认值,而非要求提供不必要的配置。
在大部分情况下,你会发现使用框架提供的默认值会让你的项目运行的更快。
在没有自定义的情况下,目录结构是源代码src/main/java、资源文件src/main/resources、测试代码src/test、JAR文件target/
Maven对约定优于配置的应用不仅仅是简单的目录位置,Maven 的核心插件使用了一组通用的约定,以用来编译源代码,打包可分发的构件,生成 web站点,还有许多其他的过程。
如果你不想遵循约定,Maven也会允许你自定义默认值来适应你的需求。
一个一般的接口
在 Maven为构建软件提供一个一般的接口之前,每个单独的项目都专门有人来管理一个完全自定义的构建系统。开发人员必须在开发软件之外去学习每个他们要参与的新项目的构建系统的特点。
虽然 Maven有很多优点,包括依赖管理和通过插件重用一般的构建逻辑,但它成功的最核心原因是它定义了构建软件的一般的接口。
基于Maven插件的全局性重用
Maven大部分的智能是由插件实现的,而插件从Maven仓库获得。Maven从远程仓库获取依赖和插件的这一事实允许了构建逻辑的全局性重用。
Maven的核心财产是声明性构建,依赖管理,仓库管理,基于插件的高度重用。
比较Maven和Ant
• Ant没有正式的约定一个一般项目的目录结构,你必须明确的告诉 Ant 哪里去找源代码,哪里放置输出。随着时间的推移,非正式的约定出现了,但是它们还没有在产品中模式化。
• Ant是程序化的,你必须明确的告诉 Ant 做什么,什么时候做。你必须告诉它去编译,然后复制,然后压缩。
• Ant没有生命周期,你必须定义目标和目标之间的依赖。你必须手工为每个目标附上一个任务序列。
• Maven拥有约定,因为你遵循了约定,它已经知道你的源代码在哪里。它把字节码放到 target/classes ,然后在 target 生成一个 JAR 文件。
•Maven 是声明式的。你需要做的只是创建一个 pom.xml 文件然后将源代码放到默认的目录。Maven 会帮你处理其它的事情。
•Maven 有一个生命周期,当你运行 mvn install 的时候被调用。这条命令告诉 Maven 执行一系列的有序的步骤,直到到达你指定的生命周期。遍历生命周期旅途中的一个影响就是,Maven运行了许多默认的插件目标,这些目标完成了像编译和创建一个 JAR 文件这样的工作。
创建一个简单的项目 MavenArchetype插件(archetype 原型;)
$ mvn archetype:create-DgroupId=org.sonatype.mavenbook.ch03 \
-DartifactId=simple \
-DpackageName=org.sonatype.mavenbook
mvn是Maven2的命令,archetype:create称为一个Maven目标 (goal)。如果你熟悉ApacheAnt,一个Maven目标类似于一个Ant目标 (target)。
项目对象模型 (Project Object Model)
groupId,artifactId,packaging, version——是Maven的坐标(coordinates),它们唯一标识了一个项目。
Maven插件和目标 (Plugins and Goals)
一个Maven插件是一个或者多个目标的集合。核心的插件,像Jar插件,它包含了一组创建JAR文件的目标,Compiler插件,它包含了一组编译源代码和测试代码的目标,或者Surefire插件,它包含一组运行单元测试和生成测试报告的目标。专门的插件包括:Hibernate3插件,用来集成流行的持久化框架Hibernate。Maven也提供了自定义插件的能力。
一个目标是一个明确的任务,它可以作为单独的目标运行,或者作为一个大的构建的一部分和其它目标一起运行。目标通过配置属性来定制行为。
Maven的核心对你项目构建中特定的任务几乎毫无所知。它把这些任务代理给了Maven插件,像Compiler插件和Jar插件,它们在需要的时候被下载下来并且定时的从Maven中央仓库更新。当你下载Maven的时候,你得到的是一个包含了基本躯壳的Maven核心,它知道如何解析命令行,管理classpath,解析POM文件,在需要的时候下载Maven插件。通过保持Compiler插件和Maven核心分离,并且提供更新机制,用户很容易能使用编译器最新的版本。通过这种方式,Maven插件提供了通用构建逻辑的全局重用性。(如果你不喜欢这Compiler插件,你可以用你的实现来覆盖它)。
Maven生命周期 (Lifecycle)
$ mvn package 命令行并没有指定一个插件目标,而是指定了一个Maven生命周期阶段,每个阶段可能绑定了零个或者多个目标,jar:jar目标被就绑定到了打包阶段。
Maven执行一个阶段的时候,它首先会有序的执行前面的所有阶段,到命令行指定的那个阶段为止。
也可以显式的指定一系列插件目标,以得到同样的结果:
mvn resources:resources\
compiler:compile \
resources:testResources\
compiler:testCompile \
surefire:test \
jar:jar
test 目标执行项目中所有能在src/test/java 找到的并且文件名与 **/Test*.java, **/*Test.java 和 **/*TestCase.java匹配的所有单元测试。在 Maven Surefire 插件执行 JUnit 测试的时候,它同时也在 target/surefire-reports 目录下生成XML 和常规文本报告。 如果你的测试失败了,你可以去查看这个目录,里面有你单元测试生成的异常堆栈信息和错误信息。
如果有失败的单元测试,但你仍然希望产生构建输出,你就必须告诉Maven让它忽略测试失败。当Maven遇到一个测试失败,它默认的行为是停止当前的构建。如果你希望继续构建项目,即使 Surefire插件遇到了失败的单元测试,你就需要设置Surefire的testFailureIgnore 这个配置属性为 true。
跳过单元测试。可能你有一个很大的系统,单元测试需要花好多分钟来完成,而你不想在生成最终输出前等单元测试完成。你可能工作在一个遗留系统上面,这个系统有一系列失败的单元测试,你可能仅仅想要生成一个 JAR 而不是去修复所有的单元测试。 Maven提供了跳过单元测试的能力,只需要使用 Surefire 插件的 skip 参数。
也可以从命令行通过 -D 参数设置。
$ mvn test-Dmaven.test.failure.ignore=true
$ mvn install-Dmaven.test.skip=true
Maven坐标 (Coordinates)
Maven坐标通常用冒号来作为分隔符来书写,像这样的格式:groupId:artifactId:packaging:version。
groupId 团体,公司,组织,以组织的逆向域名(reverse domain name)开头。
artifactId 在groupId下的表示一个单独项目的唯一标识符。
version一个项目的特定版本。正在开发中的项目可以给版本加上一个“SNAPSHOT”的标记。
packaging项目的打包格式也是Maven坐标的重要组成部分,但它不是项目唯一标识符的一个部分。一个项目的groupId:artifactId:version使之成为一个独一无二的项目。
Maven仓库(Repositories)
Maven下载包的大小相当的小(1.8兆),其中一个原因是这个初始Maven不包括很多插件。Maven自带了一个用来下载Maven核心插件和依赖的远程仓库地址(http://repo1.maven.org/maven2)。
第一次运行Maven当触发resources:resource目标的时候,它首先会做的事情是去下载最新版本的Resources插件。在Maven中,构件和插件是在它们被需要的时候从远程的仓库取来的。
运行mvn install命令,Maven会把我们项目的构件安装到本地仓库。
Maven依赖管理 (Dependency Management)
一个复杂的项目将会包含很多依赖,也有可能包含依赖于其它构件的依赖。这是Maven最强大的特征之一,它支持了传递性依(transitivedependencies)。假如你的项目依赖于一个库,而这个库又依赖于五个或者十个其它的库(就像Spring或者Hibernate那样)。你不必找出所有这些依赖然后把它们写在你的pom.xml里,你只需要加上你直接依赖的那些库,Maven会隐式的把这些库间接依赖的库也加入到你的项目
中。
通过本地仓库中的文件可以看到,Maven不只下载了JAR文件(junit-3.8.1.jar),还下载了一个POM文件(junit-3.8.1.pom)。Maven同时下载构件和POM文件的这种行为,对Maven支持传递性依赖来说非常重要。在Maven中一个依赖不仅仅是一个JAR,它还包括POM文件,这个POM声明了对其它构件的依赖。
Maven也提供了不同的依赖范围(dependencyscope)。当一个依赖的范围是test的时候,说明它在Compiler插件运行compile目标的时候是不可用的。它只有在运行compiler:testCompile和surefire:test目标的时候才会被加入到classpath中。
当为项目创建JAR文件的时候,它的依赖不会被捆绑在生成的构件中,他们只是用来编译。当用Maven来创建WAR或者EAR,你可以配置Maven让它在生成的构件中捆绑依赖,你也可以配置Maven,使用provided范围,让它排除WAR文件中特定的依赖。provided范围告诉Maven一个依赖在编译的时候需要,但是它不应该被捆绑在构建的输出中。当你开发web应用的时候provided范围变得十分有用,你需要通过ServletAPI来编译你的代码,但是你不希望Servlet API的JAR文件包含在你web应用的WEB-INF/lib目录中。provided 范围告诉 Maven jar 文件已经由容器提供了,不需要包含在 war 中。
站点生成和报告 (Site Generation and Reporting)
$ mvnsite Site生命周期只关心处理在src/site目录下的site内容,还有生成报告。运行这个命令之后,将会在target/site目录下看到一个项目web站点。载入target/site/index.html将会看到项目站点的基本外貌。
Jaxen XPath
Velocity 模板 、FreeMarker
使用Commons IO的IOUtils类来把文件转化为String。IOUtils提供了许多很有帮助的静态方法,能帮助摆脱繁琐的I/O操作。
浏览项目依赖
查看已解决的依赖 $ mvn dependency:resolve
查看依赖树 $ mvn dependency:tree
一个简单的web应用
…
默认的WAR文件是target/simple-webapp-1.0-SNAPSHOT.war。通过在项目的构建配置中加入 finalName来定义生成的WAR文件的名称,package阶段生成的WAR文件为target/simple-webapp.war。
一个多模块项目
一个多模块项目通过一个父POM(也称为顶层POM)引用一个或多个子模块来定义。
这个父项目不像之前的项目那样创建一个JAR或者一个WAR,它仅仅是一个引用其它Maven项目的POM。
像这样仅仅提供项目对象模型的项目,正确的打包类型是pom。
子模块在modules元素中定义,每个module元素对应一个simple-parent/目录下的子目录。Maven去这些子目录寻找pom.xml文件,并且在构建simp-parent的时候,它会将这些子模块包含到要构建的项目中。
最后,定义了一些将会被所有子模块继承的设置。simple-parent的build部分配置了编译的目标是Java5 JVM。
dependencies元素将JUnit3.8.1添加为一个全局的依赖。build配置和dependencies都会被所有的子模块继承。使用POM继承允许你添加一些全局的依赖如JUnit和Log4J。
子模块使用一组Maven坐标引用一个父POM。子模块中我们不再需要重新定义groupId和version,它们都从父项目继承了。
simple-webapp模块依赖于simple-weather模块
当Maven执行一个带有子模块的项目的时候,Maven首先载入父POM,然后定位所有的子模块POM。Maven然后将所有这些项目的POM放入到一个称为Maven反应堆(Reactor)的东西中,由它负责分析模块之间的依赖关系。这个反应堆处理组件的排序,以确保相互独立的模块能以适当的顺序被编译和安装。
一旦这个多模块项目已经通过从父项目simple-parent执行mvn cleaninstall安装好了,你可以切换至simple-webapp项目,然后运行Jetty插件的Run目标(启动服务器)。Jetty启动之后,就可以访问simple-webapp项目了。
多模块企业级项目
Maven很强大,能根据你的需要变得很简单或者很复杂。因此,通常完成同样一个任务有很多种方法,而且通常没有一个“最正确”的方式来配置你的Maven项目。
POM清理
寻找一个POM中的重复,或者多个兄弟POM中的重复。
当你开始一个项目,或者项目进化得很快,有一些依赖和插件的重复是可以接受的,但随着项目的成熟以及模块的增多,你需要花一些时间来重构共同的依赖和配置点。随着项目的变大,使你的POM更高效的帮助你管理复杂度。不管什么时候遇到一些重复的信息片段,通常都有更好的配置方式。
优化依赖
重复依赖很难保证一个大项目中的版本一致性。需要在父POM中巩固版本和依赖声明。
1)找出所有被用于一个以上模块的依赖,然后将其向上移到父POM的dependencyManagement片段;
2)在这些依赖配置被上移之后,我们需要为每个POM移除这些依赖的版本,否则它们会覆盖定义在父项目中的dependencyManagement;
3)修复hibernate-annotations和hibernate-commons-annotations的版本重复问题,因为这两个版本应该是一致的,我们通过创建一个称为hibernateannotations-version的属性;
使用Maven Dependency插件进行优化
$ mvndependency:analyze
$ mvn dependency:tree