Maven 是跨平台的项目管理工具,主要服务于基于Java平台的项目构建、依赖管理和项目信息管理。Maven 的主要思想是约定优于配置。经过将约定项目的目录结构,抽象项目的生命周期的方式,将程序员从繁琐的项目构建中解放出来。java
注:本文用的是 maven-3.5.0 版本。程序员
平常工做除了编写源代码,天天有至关一部分时间花在了项目构建中,他们包括项目的编译、运行单元测试、生成文档、打包和部署等烦琐且不起眼的工做。spring
项目自动化构建。Maven 提供一套规范以及一系列脚本,从清理、编译、测试到生成报告,再到打包和部署实现自动化构建。还提供了插件扩展的方式,进一步简化构建的过程。Maven 还能对项目进行编译、测试、打包,而且将项目生成的构建部署到仓库中。apache
Maven 是跨平台的。对外提供了一致的操做接口,不管在 windows 平台、Linux 平台仍是 Mac 上都能用相同的命令进行操做。同时,Maven 项目目录结构、测试用例命名方式等内容都有既定的规则,只要遵循了这些成熟的规则,用户在项目间切换的时候就免去了额外的学习成本。windows
Maven 是依赖管理工具。Java 项目须要依赖许多的 jar 包,随着依赖的增多,版本不一致、版本冲突、依赖臃肿等问题都会接踵而来。Maven 经过仓库
统一存储这些 jar 包,并经过 pom 文件
来管理这些依赖。api
Maven 是项目配置工具。Maven能帮助咱们管理本来分散在项目中各个角落的项目信息,包括项目描述、开发者列表、版本控制系统地址、许可证、缺陷管理系统地址等。缓存
以上是 Maven 的概念模型,前面说过 Maven 能管理众多的 jar 包,而且梳理他们之间的依赖关系。Maven 经过 pom 文件
和仓库
进行实现。tomcat
若是没有 maven 咱们要使用一个 jar 包要从项目的官网寻找下载 jar 到本地,而后再将 jar 包导入到项目中。这样存在几个问题:服务器
...框架
最好的解决方式就是将这些 jar 包统一管理,每次只要去一个地方找就能够了。
Maven 就帮咱们作了这样一件事情,他提供一个免费的中央仓库http://repo1.maven.org/maven2
,该中央仓库包含了世界上大部分流行的开源项目。
咱们能够从中央仓库下载须要的 jar 包,从中央仓库
下载的 jar 包会统一保存在 maven 的本地仓库
中。本地仓库
在本机的.m2
文件夹中。
本地仓库更多相关信息能够去搜索 maven 的安装教程。
远程仓库除了中央仓库还有私服和其余公共仓库。
私服是一种特殊的远程仓库,它是架设在局域网内的仓库服务,私服代理广域网上的远程仓库,供局域网内的Maven用户使用。
上图是搭建私服的示意图。私服会将其余公共仓库的 jar 缓存到搭建的服务器上,局域网内的用户还能够将构建的项目直接放到私服上供其余开发人员使用。
架设私服是 Maven 推荐的作法,私服减小中央服务器的压力。若是没有私服,每次请求都须要向中央服务器请求,有了私服以后经过私服的服务器向中央仓库请求,局域网内的用户只须要向私服请求便可。
好比 Maven 的中央仓库部署在国外,国内访问外网速度不够,咱们能够在国内架设 Maven 的公共仓库。
若是仓库 X 能够提供仓库 Y 存储的全部内容,那么就能够认为 X 是 Y 的一个镜像,显然在国内咱们须要一个中央仓库的镜像。http://maven.net.cn/content/groups/public/
是中央仓库,http://repo1.maven.org/maven2/
在中国的镜像。
Maven 默认是从中央仓库下载文件的,想要让其从其余地方下载文件就要进行配置,这里就须要操做 maven 的 setting.xml
文件了。
在安装好 maven 的基础上,进入 maven 的安装目录,能够看到以下的目录结构:
setting.xml
文件就在conf
目录中。这里的setting.xml
是 maven 全局的配置文件,不建议修改。修改以后会影响 maven 的升级等操做。经常使用的作法是拷贝一份setting.xml
到 maven 本地仓库的同一目录下,而本地仓库配置在用户目录的.m2
文件夹中,此时的setting.xml
就是用户级别的配置文件。
强烈建议遵循以上规范,避免没必要要的麻烦。
接下来就来看setting.xml
的一些配置了。
首先localRepository
定义本地仓库位置,默认在用户目录下的.m2/repository
中。
Default: ${user.home}/.m2/repository <localRepository> /path/to/local/repo </localRepository>
前面讲过有中央仓库和其余远程仓库,配置远程仓库就在repositories
中配置。
<repositories> <repository> <id>jboss</id> <name>JBoss Repository</name> <url>http://repository.jboss.com/maven2/</url> <releases> <enabled>true</enabled> <updatePolicy>daily</updatePolicy> </releases> <snapshots> <enabled>false</enabled> <checksumPolicy>warn</checksumPolicy> </snapshots> <layout>default</layout> </repository> </repositories>
在repositories元素下,可使用repository子 元素声明一个或者多个远程仓库。
<settings> …… <mirrors> <mirror> <id>maven.net.cn</id> <name>one of the central mirrors in China </name> <url> http://maven.net.cn/content/groups/public/ </url> <mirrorOf>central</mirrorOf> </mirror> </mirrors> …… </settings>
<mirrors> <mirror> <id>maven.oschina.net</id> <name>maven mirror in China</name> <url>http://maven.oschina.net/content/groups/public/</url> <mirrorOf>central</mirrorOf> </mirror> </mirrors>
如下网站提供 Maven 仓库搜索功能。
通常我就用最后一个搜索。
如今有了仓库统一保管这些 jar 包,剩下的问题就是怎么取了。
不知道你有没有取快递的经验。咱们能够这些 jar 包想象成是快递,仓库中保管着这些快递。咱们去认领快递须要依靠快递单来肯定,一张快递单上会有单号、咱们的姓名、手机号等信息。依靠这些信息就不会领错快递了。
这里的快递单就像 Maven 中的 pom 文件
,单子上的信息就像是 pom 文件中的坐标系
。
Maven 项目规定的项目结构是这样的:
每一个 maven 项目都有 pom.xml
文件。Maven坐标为各类构件引入了秩序,任何一个构件都必须明肯定义本身的坐标。
一组 Maven坐标是经过一些元素定义的,它们是 groupId、artifactId、version、packaging、 classifier:
经过坐标系咱们来保证项目在 Maven 仓库中的惟一性,每次取也不会取错了。
咱们本身项目须要用别人的 jar 包,好比 spring。这就是咱们的项目依赖于 spring,所以咱们经过 pom 来配置这样的依赖关系,这样就能让项目有清晰的结构。
依赖的关系用用<dependecy>
标签来表示依赖:
上图说明该项目依赖了 hibernate 等
如今来考虑一种状况,咱们在项目开发的过程当中用到了 junit 进行测试,也就是说咱们的项目依赖于 junit。在项目构建的过程当中咱们会把 junit 也打包在项目中。可是在生产环境中彻底没有必要用到 junit,咱们并不想将它发布到生产环境中。
咱们能够每次在发布项目以前把他删除了对么?那若是依赖 servlet-api,咱们只有在编译和测试项目的时候须要该依赖,但在运行项目的时候,因为容器已经提供,也不须要 Maven 重复地引入一遍。
因此最好是在编译、测试、运行的过程当中须要用到什么 jar 包,就让 Maven 去打包什么。
maven 为此提供了scope
标签表示依赖范围
,表示该 jar 包在何时须要被使用。
依赖范围除了控制classpath,还会对依赖传递产生影响。若是A依赖B,B依赖C,则A对于B是第一直接依赖。B对于C是第二直接依赖。A对于C是传递性依赖。结论是:第一直接依赖的范围和第二直接依赖的范围决定了传递性依赖的范围。
第一列是第一直接依赖,第一行是第二直接依赖,中间表示传递性依赖范围。
此外 maven 还提供了option
和exclusions
来进一步管理依赖,分别称为可选依赖
和排除依赖
。
在依赖中添加 <optional> true/false <optional> 表示是否向下传递。
如上图所示,B 依赖于 X,Y 而 A 依赖于 B,若是 B 不但愿将依赖传递给 A 则能够配置 B 中的 X,Y 依赖的optional
为 true 来阻止依赖的传递。
再来看一种状况,A 依赖于 B,且 B 将他的依赖 C 传递给了 A。可是 A 依赖了 C 的另外一个版本。这个时候 A 能够主动排除 B 给的 C 依赖,转而使用本身须要的版本,这就用到了exclusions
标签。
用exclusions元素声明排除依赖,exclusions能够包 含一个或者多个exclusion子元素,所以能够排除一个或者多个传递性依赖。
因此我用主动和被动的方式来区分他们。
接上面的问题,若是 A 和 B 依赖 C 的不一样版本,并且既没有配置可选依赖
也没有配置排除依赖
。两个版本都被解析显然是不对的,由于那会形成依赖重复,所以必须选择一个。
路径最近者优先。若是直接与间接依赖中包含有同一个坐标不一样版本的资源依赖,以直接依赖的版本为准。
第一声明者优先。在依赖路径长度相等的前 提下,在POM中依赖声明的顺序决定了谁会被解析使用,顺序最靠前的那个依赖优胜。
上面例子中,A -> C(1.10) 和 A -> B -> C(?),C(1.10)的路径短因此用它。
Maven的生命周期就是为了对全部的构建过程进行抽象和统一。这个生命周期包含了项目的 清理、初始化、编译、测试、打包、集成测试、 验证、部署和站点生成等几乎全部构建步骤。
初学者每每会觉得Maven的生命周期是一个总体,其实否则。Maven拥有三套相互独立的生命周期,它们分别为clean、default和site。clean生命周期的目的是清理项目,default生命周期的目的是构建项目,而site生命周期的目的是创建项目站点。
clean生命周期的目的是清理项目,它包含三个阶段:
mvn clean 中的clean就是上面的clean,在一个生命周期中,运行某个阶段的时候,它以前的全部阶段都会被运行,也就是说,mvn clean 等同于 mvn pre-clean clean ,若是咱们运行 mvn post-clean ,那么 pre-clean,clean 都会被运行。这是Maven很重要的一个规则,能够大大简化命令行的输入。
default生命周期定义了真正构建时所须要执 行的全部步骤,它是全部生命周期中最核心的部分,其包含的阶段以下:
运行任何一个阶段的时候,它前面的全部阶段都会被运行,这也就是为何咱们运行mvn install 的时候,代码会被编译,测试,打包。此外,Maven的插件机制是彻底依赖Maven的生命周期的,所以理解生命周期相当重要。
site生命周期的目的是创建和发布项目站点,Maven可以基于POM所包含的信息,自动生成一个友好的站点,方便团队交流和发布项目信息。
site 生成项目的站点文档
这里常常用到的是site阶段和site-deploy阶段,用以生成和发布Maven站点,这但是Maven至关强大的功能,Manager比较喜欢,文档及统计数据自动生成,很好看。
mvn clean:该命令调用clean生命周期的clean阶段。实际执行的阶段为clean生命周期的 pre-clean和clean阶段。
mvn test:该命令调用default生命周期的test阶段。实际执行的阶段为default生命周期的 validate、initialize等,直到test的全部阶段。这也解释了为何在执行测试的时候,项目的代码可以自动得以编译。
mvn clean install:该命令调用clean生命周期 的clean阶段和default生命周期的install阶段。
mvn clean deploy site-deploy:该命令调用 clean生命周期的clean阶段、default生命周期的 deploy阶段,以及site生命周期的site-deploy阶段。
软件设计人员每每会采用各类方式对软件划分模块,以获得更清晰的设计及更高的重用性。当把Maven应用到实际项目中的时候,也须要将项目分红不一样的模块。
简单的说就是有 A,B 两个模块,如今想要将他们统一管理。Maven 的聚合特性可以把项目的各个模块聚合在一块儿构建,而Maven的继承特性则能帮助抽取各模块相同的依赖和插件等配置,在简化POM的同时,还能促进各个模块配置的一致性。
两个子模块但愿同时构建。这时,一个简单的需求就会天然而然地显现出来:咱们会想要一次构建两个项目,而不是到两个模块的目录下分别执行mvn命令。Maven聚合(或者称为多模块)这一特性就是为该需求服务的。
上图所示api
是一个模块,cmd
是一个模块他们都有各自的 pom 文件,其实每个包都是一个子模块,而最底下的 pom 文件则是统一管理这些子模块。
他们的配置很简单,咱们最好遵循规范。
api 的 pom.xml
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <parent> <groupId>com.shuiyujie.fu</groupId> <artifactId>sop</artifactId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>api</artifactId> ...
cmd 的 pom.xml
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <parent> <groupId>com.shuiyujie.fu</groupId> <artifactId>sop</artifactId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>cmd</artifactId>
聚合 pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.shuiyujie.fu</groupId> <artifactId>pom</artifactId> <packaging>sop</packaging> <version>1.0-SNAPSHOT</version> <name>sop</name> <url>http://maven.apache.org</url> ... <modules> <module>base</module> <module>core</module> <module>server</module> <module>persist</module> <module>api</module> <module>impl</module> <module>cmd</module> </modules> ...
观察上面的三个代码清单能够聚合 pom 文件中定义了 <modules>
标签,标签中包含的就是各个子模块,而且用子模块的artifactId
来标记他们。
注意:聚合 pom 文件的打包方式,即 packaging
必须为 pom。
这样只须要构建聚合 pom 文件便可同时构建在其管理下的多个子模块。
消除重复。在面向对象世界中,程序员可使用类继承在必定程度上消除重复,在Maven的世界 中,也有相似的机制能让咱们抽取出重复的配 置,这就是POM的继承。
任然看上面的三个 pom.xml 代码清单,子模块都有一个parent
标签,这就代表他们继承了一个 pom 文件,而parent
标签下的其余标签就是一个坐标系
,经过一个坐标系就能定位一个惟一的项目。
好比上面的子模块继承自聚合 pom 文件
,因此此时聚合 pom 文件
也是父类 pom 文件
。
在继承的过程当中咱们考虑一种情形,咱们但愿在父类中统一控制 spring 的版本,而后子类继承自父类就可使用统一版本的 spring 依赖了。可是有些子模块不须要依赖 spring,并不须要从父类继承 spring 的依赖。
咱们可使用dependencyManagement
标签。
父类 pom.xml
<dependencyManagement> <dependencies> <!-- 模块间依赖 start --> <dependency> <groupId>${project.groupId}</groupId> <artifactId>core</artifactId> <version>${project.version}</version> </dependency> <dependency> <groupId>${project.groupId}</groupId> <artifactId>persist</artifactId> <version>${project.version}</version> </dependency> <dependency> <groupId>${project.groupId}</groupId> <artifactId>api</artifactId> <version>${project.version}</version> </dependency> <dependency> <groupId>${project.groupId}</groupId> <artifactId>impl</artifactId> <version>${project.version}</version> </dependency> <dependency> <groupId>${project.groupId}</groupId> <artifactId>server</artifactId> <version>${project.version}</version> </dependency> <dependency> <groupId>${project.groupId}</groupId> <artifactId>cmd</artifactId> <version>${project.version}</version> </dependency> <!-- 模块间依赖 end --> </dependencies> </dependencyManagement>
父类dependencyManagement
中声明了各个子模块,子模块之间有的会须要相互引用,有的却并不须要。因此在父类中统一配置各个子模块的groupId
,artifactId
,version
等基本信息。
在dependencyManagement
中声明的依赖不会在当前pom中引入依赖,也不会再继承他的pom中引入依赖,他的做用只是声明了可能要引入依赖的一些通用信息。
若是要使用一个子模块要使用其余子模块就能够另外声明,可是不须要指定版本等通用信息,这样就能够减小依赖冲突的发生,代码以下:
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <parent> <groupId>com.shuiyujie.fu</groupId> <artifactId>sop</artifactId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>api</artifactId> <dependencies> <dependency> <groupId>${project.parent.groupId}</groupId> <artifactId>fu-persist</artifactId> </dependency> </dependencies> </project>
Maven 的核心思想是约定优于配置。
首先,Maven 约定了项目的结构,咱们不须要配置 Maven 编译、打包等操做时文件的位置。统一的项目结构下降了学习的成本,让我能将精力集中到了项目自己。
其次,Maven 抽象了项目构建的过程,将其分红一个个生命周期进行管理。经过命令和插件的形式进一步简化操做,又让咱们从繁琐的操做解放出来。
本文大部份内容来自于《Maven 实战》一书,想要了解一手信息强烈建议阅读。网上的其余文章基本上都是摘抄《Maven 实战》的部份内容。
因此还想说一遍:发现一本好书就像发现了一座宝藏。