Maven简介
- 主要用于基于Java平台的项目构建、依赖管理和项目信息管理
- 约定优于配置(Convention Over Configuration),使项目构建标准化,消除重复及琐碎的任务,且提供中央仓库,通过坐标系统来有序地管理以来
- 核心是POM文件
本文是对《Maven实战》的学习和总结
坐标
没有Maven的情况下,开发中需要某个依赖时,得去它的官网上寻找,依赖多了之后,就得花费大量时间寻找和下载依赖,没有统一的规范。而Maven定义了一组规则:世界上任何一个构件都可以用Maven坐标唯一标识,包括groupId、artifactId、version。packaging、classifier。Maven提供一个中央仓库,包含了世界上大部分流行的开源项目构件,只要提供正确的坐标,Maven就会自动下载需要的构件。
坐标定义:
<groupId>org.sonatype.nexus</groupId>
<artifactId>nexus-indexer</artifactId>
<version>2.0.0</version>
<packaging>jar</packaging>
- groupId定义了项目属于哪个组,通常和域名反向对应(和项目所在组织或公司相关),如googlecode的myapp项目,groupId就是com.googlecode.myapp
- artifactId定义当前项目在组中唯一的ID,推荐用实际项目名称做前缀,即artifactId=项目名+模块名,因为生成的构件会以
artifactId-version.packageing
命名,假如多个项目都有core模块,就好区分了,如foo-core-1.2.jar、ba-core-1.2.jar。 - version指定了项目当前的版本,如1.0-SNAPSHOT指快照,说明该项目还处在开发中,是不稳定的版本
- packaging指打包方式,jar、war或pom,可选,默认是jar
- classifier定义构件输出的一些附属构件,如主构件是nexus-indexer-2.0.0.jar,项目还使用一些插件生成附属构件,如nexus-indexer-2.0.0-javadoc.jar。不能直接定义classifier因为附属构件不是项目直接默认生成的,而是附加的插件生成的
依赖
<dependencies>
<dependency>
<groupId>..</groupId>
<artifactId>..</artifactId>
<version>..</version>
<type>..</type>
<scope>..</scope>
<optional>..</optional>
<exclusions>
<exclusion>
...
</exclusion>
</exclusions>
</dependency>
</dependencies>
依赖范围
Maven有三种classpath,编译、测试、运行,依赖范围就是用scope元素
来控制classpath,scope的主要种类:
- compile:对三种都有效
- test:只对测试classpath有效,编译代码或运行项目时无法使用此类依赖
- provided:编译和测试有效,运行无效
- runtime:测试和运行有效,编译无效
- system:和provided范围一样,只是构件必须显式指明文件路径
传递性依赖
A依赖B,B依赖C,A对于C是传递性依赖,如果第二直接依赖范围(B-C)是compile,传递性依赖范围和第一直接依赖范围(A-B)一致,第二直接依赖范围是test,则依赖不会传递。如果B-C是可选依赖(optional元素
是true)
排除依赖
用exclusion元素排除依赖
仓库
没有仓库之前,每个项目都要存放依赖,很多都是重复的,浪费磁盘空间且很难统一管理。有了坐标机制,Maven就可以在某个位置统一存储所有Maven项目共享的构件,这个位置就是仓库。任意构件都有唯一坐标,根据坐标就可以定义其在仓库中的唯一存储路径,groupId/artifactId/version/artifactId-version.packaging
。
仓库分类
两类:本地仓库和远程仓库,Maven找某构件时,先看本地仓库,不存在就去远程仓库下载。
- 远程仓库:
- 中央仓库是Maven自带的远程仓库
- 私服是特殊的远程仓库,在局域网内部架设的私有的仓库服务器,用其代理外部的远程仓库,节省带宽和时间,且第三方项目或内部项目还能部署到私服上供其他项目使用,可以用Nexus架设私服
<repositories>
<repository>
<id>..</id>
<name>..</name>
<url>..</url>
<releases>
<enabled>true</enabled> <!--表示开启仓库的发布版本下载支持-->
</releases>
<snapshots>
<enabled>false</enabled> <!--表示关闭仓库的快照版本下载支持-->
<updatePolicy>daily</updatePolicy> <!--表示每天检查一次更新-->
</snapshots>
</repository>
</repositories>
- 本地仓库:
- 一般在用户目录的.m2/repository/目录
本地项目可以构建后安装到本地仓库,供其他项目使用,mvn clean install
也可以部署到远程仓库,mvn clean deploy
,需要配置distributionManagement元素
<distributionManagement>
<repository> <!--表示发布版本构件的仓库-->
<id>..</id>
<name>..</name>
<url>..</url>
</repository>
<snapshotRepository> <!--表示快照版本构件的仓库-->
<id>..</id>
<name>..</name>
<url>..</url>
</snapshotRepository>
</distributionManagement>
快照版本
正在开发的模块版本一般都设为快照,如2.0.0-SNAPSHOT,Maven会为快照自动打上时间戳,有了时间戳,Maven就能找到仓库中该快照版本的最构件。其他项目依赖该快照时,每次构建的时候都会下载最新的构件,即使用的是别人最新的代码。快照的“最新”是基于groupId/artifactId/version/maven-metadata.xml
计算出来的
生命周期 & 插件
Maven的生命周期是为了对所有的构建过程进行抽象和统一。生命周期抽象了构建的各个步骤,而具体的任务是由插件来实现。
- Maven有三套相互独立的生命周期:clean、default和site。default是核心,定义了真正构建时执行的所有步骤,如validate、process-sources、complie、test-compile、test、package、verify、install、deploy等。
- 插件可以有多个功能,每个功能就是一个插件目标,用
插件前缀:插件目标
表示 - 生命周期的阶段和插件的目标相互绑定,用以完成具体的构建任务
- Maven为主要的生命周期阶段绑定了很多插件的目标,用命令行调用生命周期阶段时,对于的插件目标就会执行相应的任务,如:
- clean与maven-clean-plugin:clean绑定
- package与maven-jar-plugin:jar绑定
- install与maven-install-plugin:install绑定
从命令的执行信息可以看出用了哪些插件目标,执行了生命周期的哪些阶段
自定义绑定
用户自己选择将某插件目标绑定到生命周期的某个阶段
<build>
<plugins> <!--表示发布版本构件的仓库-->
<plugin>
<groupId>..</groupId>
<artifactId>..</artifactId>
<version>..</version>
<executions>
<execution>
<id>..</id>
<phase>..</phase> <!--生命周期阶段-->
<goals>..</goals> <!--插件目标-->
</execution>
</executions>
<plugin>
</plugins>
</build>
插件配置
插件目标有可配置的参数,可通过命令行和POM等方式来配置
- 命令行配置
- 如maven-surefire-plugin提供maven.test.skip参数,为true时跳过执行测试
- 运行命令行时使用
mvn install -Dmaven.test.skip=true
或mvn isntall -DskipTests=true
- POM配置,全局一次性配置
- 如通常会配置maven-compiler-plugin告诉它编译Java某版本的源文件,用<configuration>元素
插件仓库
与依赖构件一样,插件构件也基于坐标存储在Maven仓库中
<pluginRepositories>
<pluginRepository>
<id>..</id>
<name>..</name>
<url>..</url>
<releases>
<enabled>true</enabled> <!--表示开启仓库的发布版本下载支持-->
</releases>
<snapshots>
<enabled>false</enabled> <!--表示关闭仓库的快照版本下载支持-->
</snapshots>
</pluginRepository>
</pluginRepositories>
聚合
一次构建多个项目,对聚合模块来说,打包方式的值必须为pom,用<modules>来实现模块的聚合。
聚合模块知道有哪些被聚合的模块,但是被聚合的模块不知道这个聚合模块的存在。
继承
让子模块继承父模块,消除一些重复的配置。
父模块的<dependencyManagement>元素既能让子模块继承父模块的依赖配置,约束子模块的<dependencies>中依赖的使用,比如统一版本信息等,又能保证子模块依赖使用的灵活性,因为该元素不会让子模块引入实际的依赖,除非子模块声明了依赖的使用,这样就不会发生多个子模块使用依赖版本不一致的情况。
同理,<pluginManagement>也能帮助管理插件,但不会给子模块造成实际的插件调用行为,除非子模块的POM中配置了真正的plugin元素(grouppId和artifactId跟父模块的一致)
继承关系的父POM不知道有哪些子模块继承了它,但子模块都必须知道自己的父POM是什么。
反应堆
对多模块项目,反应堆包含了模块之间继承与依赖的关系,从而能够自动计算出合理的模块构建顺序