Maven
一.Maven简介
1.1 何为maven
Maven可翻译为"知识的积累" or"专家",是一款成功的开源跨平台的项目管理工具,无论小型的开源类库项目,还是大型的企业级应用;无论传统的瀑布式开发,还是流行的敏捷模式,Maven都能大显身手.
1.1.1 构建工具
我们一直在不停的寻找避免重复的方法,设计的重复,编码的重复,文档的重复,当然还有构建的重复.Maven最大化的消除了构建的重复,抽象了构建生命周期,并且为绝大部分的构建任务提供了已实现的插件,我们不需要再定义过程,甚至不需要去实现这些过程中的一些任务,只需要遵循Maven中的约定.
同时Maven帮助我们标准化构建过程,以前十个项目可能有十种构建方式,有了Maven后,所有项目的构建命令都是一直且简单的.因此Maven作为一个构建工具:
- 可以帮我们自动化构建,
- 可以帮我们抽象构建过程
- 提供构建任务是实现
- 跨平台
- 对外提供一直的操作接口
1.1.2 不仅仅是构建工具
Maven不仅是构建工具,还是一个依赖管理工具和项目信息管理工具,提供中央仓库来帮忙我们自动下载构建,通过引入一套经纬机制来系统准确的定位每一个构建(artifact).
1.1.3 Maven
在Maven之前,有过程式的Make和Ant,开发者需要显示的指定每一个目标,以及完成该目标所需要执行的任务.针对每一个项目,开发者都需要重新编写这一过程,而其中就隐含着大量重复.
而Maven是声明式的,项目构建过程和过程各个阶段所需的工作都由插件实现,并且大部分插件都是现成的,开发者只需要声明项目的基本元素,Maven就执行内置的,完整的构建过程.
二.Maven的使用
2.1 pom文件
Maven项目的核心是pom.xml,POM(Project Object Model,项目对象模型)定义了项目的基本信息,用于描述项目如何构建,声明项目依赖等.
<modelVersion>4.0.0</modelVersion>:modelVersion指定了当前Pom模型的版本,固定为4.0.0<groupId>com.lsy</groupId>:groupId定义了项目属于哪个组,这个组往往和项目所在的组织和公司相关
<artifactId>hello-world</artifactId>:artifactId定义了当前Maven项目在组中唯一的ID
<version>1.0-SNAPSHOT</version>:version指定了Hello World项目当前的版本,其中SNAPSHOT意为快照,说明该项目还处于开发中
<name>Maven Hello World Project</name>:name元素声明了一个对于用户更为友好的项目名称.非必须
当运行mvn clean compile命令:clean告诉Maven清理输出目录target/,compile告诉Maven编译项目主代码,从输出中看到Maven首先执行clean:clean任务,删除target/目录(默认情况下,Maven构建的所有输出都在target目录中,接着执行resources:resources任务(未定义项目资源),最后执行compiler:compile任务,将项目主代码编译至target/classes目录
其中clean:clean,resources:resources和compiler:compile对应了Maven插件及插件目标,比如clean:clean是clean插件的clean目标,compiler:compile是compiler插件的compile目标
2.2 测试
首先添加Junit依赖
<dependencies> <dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.7</version>
<scope>test</scope>
</dependency>
<dependencies>
其中scope元素为依赖范围,若依赖范围为test则表示该依赖只对测试有效,如果不声明依赖范围,则默认是compile,表示该依赖对主代码和测试代码均有效
当运行mvn clean test命令,Maven实际执行的不止test任务,而是clean:clean,resources:resources,compiler:compile,resources:testResources以及compiler:testCompile,即在执行测试之前,会先自动执行项目资源处理,主代码编译,测试资源处理,测试代码编译等工作,这是Maven生命周期的一个特性.
由于Maven核心插件之一compiler插件默认支持Java1.3,因此需要配置插件支持Java1.8
<build> <plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.8<source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
2.3 打包和运行
将项目进行编译,测试之后,下一个重要的步骤就是打包(package),当没有指定打包类型时,默认打包类型是jar,当执行mvn clean package命令,Maven会在打包之前进行编译,测试等操作,之后通过jar:jar任务负责打包,实际上就是jar插件的jar目标将项目主代码打包成一个hello-world-1.0-SNAPSHOT.jar的文件
当其他项目需要直接引用这个jar时,接下来需要一个安装的步骤,执行mvn clean install,此命令执行了安装任务install:install,将项目输出的jar安装到了Maven本地仓库中,而构件只有被下载到本地仓库后,才能由Maven项目使用.
2.4 使用Archetype生成项目骨架
mvn archetype:generate,当运行插件时,格式为:groupId:artifactId:version:goal,默认使用最新的稳定版
三. 坐标和依赖
3.1 坐标
- Maven的一大功能还是管理项目依赖,为了能自动化解析任何一个Java构件,Maven就必须将它们唯一标识,这就依赖管理的底层基础---坐标
- 在实际生活中,我们可以将地址看作是一种坐标,省,市,区,街道等一系列信息可以唯一标识城市中的任意居住地址,对应在Maven的世界中拥有非常巨大的构件,也就是平常使用的一些jar,war等文件
Maven定义了这样一规则:世界上任意一个构建都可以使用Maven坐标唯一标识,Maven坐标的元素包括
- groupId:定义当前Maven项目隶属公司的实际项目
- artifactId:该元素定义实际项目中的一个Maven模块,推荐做法使用实际项目名称为artifact的前缀,便于寻找实际构建
- version:该元素定义Maven项目当前所处的版本
- package:该元素定义Maven项目的打包方式,默认为jar包
- classifier:该元素用来帮助定义构建输出一些附属构建,附属构件与主构件对应
<groupId\>org.sonatype.nexus</groupId\> <artifactId\>nexus-indexer</artifactId\> <version\>2.0.0</version\> <packaging\>jar</packaging\> <!--> 1\. 以上5个元素,groupId,artifactId,version是必须定义的,packaging是可选的,而classifier是不能直接定义的 2\. 项目构件的文件名格式:artifactId-version\[-classifier\].packaging
3.2 依赖
3.2.1 依赖的配置
项目要引用Maven中的构建,就需要在pom文件中,通过坐标来使用依赖,在根元素project下的dependencies可以包含一个或多个dependency元素,用以声明一个或多个项目依赖
- groupId,artifactId和version:依赖的基本坐标,对于任何一个依赖来说,基本坐标是最重要的
- type:依赖的类型,对应项目坐标定义的packaging,一般不必声明,默认为jar
- scope:依赖的范围
- optional:标记依赖是否可选
- exclusions:用来排除传递性依赖
3.2.2 依赖的范围
- Maven项目在编译项目主代码时需要使用一套classpath,如编译项目主代码时需要使用spring-core,该文件以依赖的方式被引入到classpath中.其次,Maven在编译和执行测试的时候会使用另外一套classpath,依赖同样会引入到相应的classpath中,最后在运行Maven项目时,又会使用一套classpath
依赖范围就是用来控制依赖与三种classpath(编译classpath,测试classpath,运行classpath)的关系
- compile:编译依赖范围,没有指定时,为默认依赖范围.使用此依赖范围的Maven依赖,对于编译,测试运行三种classpath都有效,典型的例子为:spring-core,在编译,测试,运行阶段都需要使用该依赖
- test:测试依赖范围,使用此依赖范围的Maven依赖,只对于测试的classpath有效,在编译主代码或者运行项目时将无法使用此类依赖,典型的例子就是JUnit,它只有在编译器测试代码及运行测试的时候才需要
- provided:已提供依赖范围,使用此依赖范围的Maven依赖,对于编译和测试classpath有效,但在运行时无效,典型的例子就是servlet-api,编译和测试项目的时候需要该依赖,但在运行项目的时候,由于容器已经提供,就不需要Maven重复引入了
- runtime:运行时依赖范围,使用此依赖范围的Maven依赖,对于测试和运行classpath有效,但在编译主代码时无效,典型的例子是JDBC驱动实现,项目主代码的编译只需要JDK提供的JDBC接口,只有在执行测试或运行项目的时候才需要实现上述接口的具体JDBC驱动
- system:系统依赖范围,该依赖与三种classpath的关系,和provided依赖范围完全一致,但是,使用system范围的依赖时必须通过systemPath元素显示地指定依赖文件的路径,由于此类依赖不是通过Maven仓库解析的,而且往往与本机系统绑定,可能造成构建的不可移植性
<dependency>
<groupId>javax.sql</groupId>
<artifactId>jdbc-stdext</artifactId>
<version>2.0</version>
<scope>system</scope>
<systemPath>${java.home}/lib/rt.jar</systemPath>
</dependency>- import:导入依赖范围,该依赖范围不会对三种classpath产生实际的影响,主要用于导入其他pom文件中的dependencyManagement元素对于依赖版本约束的内容
3.2.3 传递性依赖
在使用Maven依赖时,如Spring Framework,此依赖又会依赖其他的开源库,因此实际中往往会下载一个很大的如spring-framework-2.5.6-with-dependencies.zip包,这样往往就引入了很多不必要的依赖,而Maven的传递性依赖机制就可以很好的解决这一问题
- 当A项目引入一个compile范围的B依赖,而B依赖中有一个compile范围的C依赖,那么C依赖同样会成为A的compile范围依赖
传递性依赖和依赖范围
- 假设A依赖于B,B依赖于C,那么A对于B是第一直接依赖,B对于C是第二直接依赖,A对于C是传递性依赖
- 当第二直接依赖是compile的时候,传递性依赖与第一直接依赖范围一致
- 当第二直接依赖是test的时候,依赖不会得以传递
- 当第二直接依赖是provided的时候,只传递第一直接依赖范围为provided的依赖,且传递性依赖范围为provided
- 当第二直接依赖是runtime的时候,传递性地依赖的范围与第一直接依赖的范围一致,但compile例外,此时传递性依赖的范围为runtime
3.2.4 可选依赖
假如有这样一个依赖关系,A依赖于B,B依赖于X和Y,B对于X和Y的依赖都是可选依赖,根据传递性依赖的定义,如果这三个依赖的范围都是compile,那么X,Y就是A的compile范围传递性依赖,但是由于X,Y都是可选依赖,所以依赖不会得以传递,因此X,Y不会对A有任何影响
为什么会有可选依赖这一特性呢?
- 当B实现了两个特性,特性一依赖于X,特性二依赖于Y,并且这两个特性是互斥的,用户不可能同时使用两个特性,比如B是一个持久层隔离工具包,支持多种数据库,在使用这个工具包的时候,只会依赖一种数据库
<dependencies\> <dependency\> <groupId\>mysql</groupId\> <artifact\>mysql-connector-java</artifact\> <version\>5.6.0</version\> <optional\>true</optional\> </dependency\> <dependency\> <groupId\>postgresql</groupId\> <artifactpostgresql</artifact> <version\>8.4-701.jdbc3</version\> <optional\>true</optional\> </dependency\> </dependencies\> * 以上使用<optional\>元素表示两个依赖为可选依赖,他们只会对当前项目B产生影响,当其他项目依赖于B时,这两个依赖不会被传递,所以当A依赖于B项目时,如果要使用mysql数据库,那么需要显式的声明mysql-connector-java这一依赖 * 关于可选依赖,在理想的情况下,是不应该使用可选依赖的,使用可选依赖的原因是某一个项目中实现了多个特性,而根据单一职责原则,应该针对不同的特性分别创建一个Maven项目,用户根据需要选择使用其中某一个依赖
3.2.5 排除依赖
传递性依赖会给项目隐式的引入很多依赖,这极大的简化了项目依赖的管理,但是有时候这种特性䧥带来问题
- 例如,当前项目有一个第三方依赖,而这个依赖由于某些原因依赖了另一个类库的SNAPSHOT,那么整个SNAPSHOT就会成为当前项目的传递性依赖,而SNAPSHOT的不稳定性会直接影响到当前的项目,这时候就需要排除掉该SNAPSHOT,并且在当前项目中声明该类库的某个正式发布版
<dependencies>
<dependency>
<groupId>com.lsy.myproject</groupId>
<artifactId>myproject-a</artifactId>
<version>1.0.0</version>
<exclusions>
<exclusion>
<groupId>com.lsy.myproject</groupId>
<artifactId>myproject-b</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.lsy.myproject</groupId>
<artifactId>myproject-b</artifactId>
<version>1.1.0</version>
</dependency>
</dependencies>
3.2.6 归类依赖
- 当一些依赖来自同一个项目的不同模块,这些依赖的版本都应该是相同的,将来升级也是一起升级,如Spring Framework,这时可以使用properties元素定义Maven属性
<properties>
<springframework.version>4.2.1</springframework.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<verison>${springframework.version}</verison>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<verison>${springframework.version}</verison>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<verison>${springframework.version}</verison>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<verison>${springframework.version}</verison>
</dependency>
</dependencies>
3.2.7 优化依赖
在软件开发过程中,通常会通过重构等方式不断优化自己代码,同样,对于Maven项目的依赖也需要对其进行优化
- 去除多余的依赖
- 显示声明某些必要的依赖
Maven会自动解析所有项目的直接依赖和间接依赖,并且根据规则判断每个依赖范围,对于一些依赖冲突,也能进行调整,以确保任何一个构建只有唯一的版本在依赖中存在,此称为解析依赖
- mvn dependency:list 查看当前项目的已解析依赖
- mvn dependency:tree 以树结构查看已解析依赖
mvn dependency:analyze 解析依赖
- Used undeclared dependencies:指项目中使用到的,但是没有显示声明的依赖,这种依赖意味着潜在的风险,当前项目直接在使用它们,所以需要显示声明任何项目中直接用到的依赖
- Unused declared dependencies:指项目中未使使用的.但显示声明的依赖
四. 仓库
- 之前已经介绍了Maven的坐标和依赖,坐标和依赖是任何一个构件在Maven世界中的逻辑表示方式,而构建的物理表示方式是文件,Maven通过仓库来同一管理这些文件
-
在Maven世界中,任何一个以依赖,插件或项目构建的输出,都可以成为构件
- 例如:log4j-1.2.15.jar,maven-compiler-plugin-2.0.2.jar或项目打包后的myproject-1.0.0-SNAPSHOT.jar
- 在一台工作站上,可能会有几十个项目,所有项目在/lib目录下都会有自己所需的依赖包,而这些依赖中都有大量的重复,每个项目各自存储自己所需的依赖包不仅造成磁盘空间的浪费,而且也难于同一管理,文件的复制等操作
- 得益于坐标机制,任何Maven项目使用任何一个构建的方式都是相同的,因此,Maven可以在某个位置统一存储所有Maven项目共享的构件,这个统一的位置就是仓库,为了实现重用,项目构建完毕后生成的构建也可以安装或部署到仓库中
4.1 仓库的布局
任何一个构建都有其唯一的坐标,根据这个坐标可以定义其在仓库中的唯一存储路径,这就是Maven的仓库布局方式,如log4j:log4j:1.2.15这一依赖,其对应的仓库路径为log4j/log4j/1.2.15/log4j-1.2.15.jar
- 路径与坐标的关系为:groupId/artifactId/version/artifactId-verision.packaging
4.2 仓库的分类
对于Maven来说,仓库分为两类
- 本地仓库
远程仓库
- 中央仓库:Maven核心自带的仓库服务器,它包含了绝大部分开源的构件
- 私服:另一种特殊的远程仓库,为了节省宽带和时间,应该在局域网内部构建一个私有仓库服务器,用其代理所有外部的远程仓库,内部项目部署到私服上供其它项目使用
- 其他公共库
- 当Maven根据坐标寻找构件时,它首先会查看本地仓库,如果本地仓库存在此构件,则直接使用,如果本地仓库不存在此构件,或者需要查看是否有更新的构件版本,Maven就会去远程仓库查找,发现需要的构件后下载到本地仓库再使用,若本地和远程都没有,则报错
4.3 本地仓库
一般来说,在Maven项目目录下,没有注入lib/这样用来存放依赖文件的目录,当Maven执行编译或测试时,如果需要使用依赖文件,它总是基于坐标使用本地仓库的依赖文件
- 默认情况,在用户目录下路径名为.m2/repository/的仓库目录,当想自定义本地仓库目录地址时,可以编辑~/.m2/setting.xml,设置localRepository元素来指定仓库地址
<settings>
<localRepository>D:/java/repository/</localRepository>
</settings>- 默认情况下,~/.m2/settings.xml文件是不存在的,用户需要从Maven安装目录复制$M2_HOME/conf/settings.xml文件再编辑,建议不要直接修改全局目录的settings.xml,而是在用户目录下进行修改
一个构建只有在本地仓库中,才能由其它Maven项目使用
- 依赖Maven从远程仓库下载到本地仓库中
- 将本地项目的构件安装到Maven本地仓库中 mvn clean install
4.4 远程仓库
安装好Maven后,如果不执行任何Maven命令,本地仓库目录是不存在的,只有输入第一条Maven命令后,Maven才会创建本地仓库,并根据配置和需要,从远程仓库下载至本地仓库
- 这就好比藏书,本地仓库好比书房,远程仓库好比书店,我需要读书时先去书房找,当书房没有时,就去书店买回放到书房,并且一般对每个人来说,书房只有一个,而外面的书店可以有多个
4.4.1 中央仓库
最原始的本地仓库是空的,所以Maven必须知道至少一个可用的远程仓库,才能在执行Maven命令时下载到需要的构建,中央仓库就是这样一个默认的远程仓库,可以通过解压工具打开$M2_HOME/lib/maven-model-builder-3.0.jar中的org/apache/maven/model/pom-4.0.0.xml文件
<repositories>
<repository>
<id>central</id>
<name>Maven Repository Switchboard</name>
<url>http://repo1.maven/org/maven2</url>
<layout>default</layout>
<snapshots>
<enable>false</enable>
</snapshots>
</repository>
</repositories>
- 这段配置是所有Maven项目都会继承的超级Pom文件,这段配置使用id central对中央仓库进行唯一标识,其名称为Maven Repository Switchboard,它使用default仓库布局,也就是在之前介绍的仓库布局,最后snapshots元素表示不从该仓库下载快照版
4.4.2 私服
私服是一种特殊的远程仓库,它是架设在局域网内的仓库服务,私服代理广域网上的远程仓库,供局域网内的Maven用户使用,当Maven需要下载构建的时候,它从私服请求,如果私服上不存在该构件,则从外部的远程仓库下载,缓存在私服上之后,再为Maven的下载提供服务
- 此外,一些无法从外部下载的构件也可以从本地上传到私服上供大家使用
私服的好处
- 节省外网带宽:建立私服同样可以减少组织自己的开支,大量的对于外部仓库的重复请求会消耗很大的带宽,利用私服代理外部仓库之后,对外的重复构件下载便可以消除,即降低外网带宽的压力
- 加速Maven构件:不停的连接请求外部仓库是十分耗时的,但Maven的一些内部机制(如快照更新检查井)要求Maven在执行构建时不停的检查远程仓库数据,因此,当项目配置很多外部仓库时,构建速度就会降低
- 部署第三方构件:当某个构件无法从任何一个外部远程仓库获取,建立私服之后,便可以将这些构件部署到这个内部的仓库中,供内部的Maven项目使用
- 提高稳定性,增强控制:Maven构建高度依赖远程仓库,因此,大哥Internet不稳定的时候,Maven构建也会变得不稳定,甚至无法构建,使用私服后,即是短暂时没有Internet连接,由于私服中有大量缓存,Maven依然可以正常运行,并且私服中有很多额外的权限功能控制
- 降低中央仓库的负荷:每天中央仓库都需要面对大量的下载请求,使用私库可以降低对于中央仓库的负荷
远程仓库的配置
很多情况下,默认的中央仓库无法满足项目的需求,可能项目需要的构件存在于另一个远程仓库中,可以通过Pom/settings文件中来配置该仓库
<!--> POM文件 </!-->
<project>
.....
<repositories>
<repository>
<id>jboss</id>
<name>JBoss Repository</name>
<url>http://repository.jboss.com/m...;/url>
<release>
<enabled>true</enabled>
</release>
<snapshots>
<enabled>false</enabled>
</snapshots>
<layout>default</layout>
</repository>
</repositories>
</project>
<!--> settings文件 </!-->
<profiles>
<profile>
<id>dev</id>
<activation>
<activatedByDefault>true</activatedByDefault>
</activation>
<repositories>
<repository>
<id>jboss</id>
<name>JBoss Repository</name>
<url>http://repository.jboss.com/m...;/url>
<release>
<enabled>true</enabled>
</release>
<snapshots>
<enabled>false</enabled>
</snapshots>
<layout>default</layout>
</repository>
</repositories>
</profile>
</profiles>
- 在repositories元素下,可以使用repository子元素声明一个或多个远程仓库,并且声明一个id,任何一个仓库声明的id必须是唯一id
- Maven自带的中央仓库使用的id为central,如果其他仓库使用该id,则会覆盖中央仓库的配置,该配置中的url指向了仓库的地址
该配置中的release和snapshots元素用来控制Maven对于发布版构件和快照版构件的下载,对于release和snapshots元素来说除了enabled子元素,还有updatePolicy和checksumPolicy元素
updatePolicy:用来配置Maven从远程仓库检查更新的频率,默认为daily
- daily:每天
- never:从不
- always:每次
- interval :X :每隔X分钟一次
checksumPolicy:配置Maven检查文件失败时的策略,默认为warn
- fail:Maven遇到验证和错误就让构建失败
- warn:Maven遇到验证和错误就发出警告信息
- ignore:Maven遇到验证和错误时完全忽略
<snapshots\> <enabled\>true</enabled\> <updatePolicy\>daily</updatePolicy\> <checksumPolicy\>ignore</checksumPolicy\> </snapshots\> * 远程仓库的验证 * 大部分远程仓库无需认证就可以访问,但出于安全考虑,我们需要提供一些认证信息才能访问一些远程仓库 * 配置认证信息和配置仓库信息不同,仓库信息可以直接配置在POM文件中,但是认证信息必须配置在settings.xml文件中 * 其中server元素的id必须与POM文件中需要认证的repository元素的id完全一致 <settings\> ...... <servers\> <server\> <id\>my-project</id\> <username\>user</username\> <password\>password</password\> </server\> </servers\> ....... </settings\> * 部署至远程仓库 * 私服一大作用就是部署第三方构件,包括组织内部生成的构件以及无法从外部仓库直接获取的构件,无论是日常开发中生成的构件,还是正式版本发布的构件,都需要部署到仓库中 * distributionManagement包含repository和snapshotRepository子元素,前者表示发布版构件的仓库,后者表示快照版的仓库 * 往远程仓库部署构件时,往往需要认证,认证配置需在settings.xml中创建一个server元素,其id与仓库的id匹配,无论从远程仓库下载构件,还是部署构件至远程仓库,当需要认证时,配置方式都是一样的 <!--> 在POM文件中配置distributionManagement </!--> <project\> ..... <distributionManagement\> <repository\> <id\>jboss-release</id\> <name\>JBoss Release Repository</name\> <url\>http://192.168.1.99/content/repository/jboss-release</url\> </repository\> <snapshotRepository\> <id\>jboss-snapshots</id\> <name\>JBoss Snapshots Repository</name\> <url\>http://192.168.1.99/content/repository/jboss-snapshots</url\> </snapshotRepository\> </distributionManagement\> ..... </project\>
4.5 快照
- 在Maven的世界中,任何一个项目或者构件都必须有自己的版本,版本的值可能是1.0.0,1.3-alpha-4,2.0,2.1-SNAPSHOT或者2.1-20191214.221414-13,其中1.0.0,1.3-alpha-4和2.0是稳定的发布版本,而2.1-SNAPSHOT和2.1-20091214.221414-13是不稳定的快照版本
- 对于一个稳定的版本,如果仓库中已经包含,那么Maven就不会再去对照远程仓库进行更新,除非每次执行Maven命令前,清除本地仓库中等待稳定版本,而对于一个正在迭代的项目,如果要实时更新版本的内容就需要频繁的修改新的版本名称,这样是对版本号的滥用
- 针对这种情况,使用快照版时,Maven会自动为构件打上时间戳,因此,Maven就能随时找到仓库中该构建最新版本的文件,一旦有新的更新,就会去同步到本地仓库.当项目经过完善的测试后需要发布的时候,再将快照版本更改为发布版本
- 快照版本只应该在组织内部的项目或模块之间依赖使用,因为这时,组织对这些快照版本的依赖具有完全的理解和控制权,项目不应该依赖任何组织外部的快照版本依赖,由于快照版本的不稳定性,随时可能发生变化,这样的依赖会有潜在的危险
4.6 从仓库解析依赖的机制
当本地仓库没有依赖构件的时候,Maven会自动从远程仓库下载,当依赖版本为快照版本时,Maven会自动找到最新的快照,这背后的依赖机制可以概括如下
- 当依赖的范围时system的时候,Maven直接从本地文件系统解析构件
- 根据依赖坐标计算仓库路径后,尝试从本地仓库寻找构件,如果发现相应的构件,则解析成功
- 在本地仓库不存在相应构件的情况下,如果依赖的版本时显式的发布版本构件,如1.2,2.1-beta-1等,则遍历所有的远程仓库,发现后,下载并解析使用
- 如果依赖的版本时RELEASE或LATEST,则基于更新策略读取所有远程仓库的元数据groupId/artifact/maven-metadata.xml,将其与本地仓库的对应元数据合并后,计算出RELEASE或LATEST真实的值,然后基于这个真实的值检查本地仓库和远程仓库
- 如果依赖的版本是SNAPSHOT,则基于更新策略读取所有远程仓库的元数据groupId/artifactId/maven-metadata.xml,将其与本地仓库的对应数据合并后,得到最新的快照版本的值,然后基于该值检查本地仓库,或者从远程仓库下载
- 如果最后解析得到的构件版本是时间戳格式,如1.4.1-20191104.121455-8,则复制其时间戳格式的文件至非时间戳格式,如SNAPSHOT,并使用该非时间戳格式的构件
<!--> 基于groupId和artifactId的maven-metadata.xml </!-->
<?xml version="1.0" encoding="UTF-8"?>
<metadata>
<groupId>org.sonatype.nexus</groupId>
<artifactId>nexus</artifactId>
<versioning>
<latest>1.4.2-SNAPSHOT</latest>
<release>1.3.7</release>
<versions>
<version>1.3.5</version>
<version>1.3.6</version>
<version>1.3.7</version>
<version>1.4.0-SNAPSHOT</version>
<version>1.4.1-SNAPSHOT</version>
<version>1.4.2-SNAPSHOT</version>
</versions>
<lastUpdated>20191214221133</lastUpdated>
</versioning>
</metadata>该XML文件列出了仓库中存在的构件所有可用的版本,同时latest元素指向了这些版本中最新的那个版本1.4.2-SNAPSHOT,而release元素指向了这些版本中最新的发布版本1.3.7,Maven通过合并多个远程仓库及本地仓库的元数据,就能计算出基于所有仓库的latest和release
- 需要注意的是,在依赖声明使用LATEST和RELEASE是不推荐的做法,因为Maven随时可能解析到不同的构件,且Maven不会明确告诉用户这样的变化
4.7 镜像
如果仓库X可以提供仓库Y存储的所有内容,那么就可以认为X是Y的一个镜像,由于地理位置的因素,中央仓库的下载速度会比较慢,这时我们可以配置Maven使用镜像来代替中央仓库,编辑settings.xml
<settings>
......
<mirrors>
<mirror>
<id>maven.net.cn</id>
<name>one of the central mirror in china</name>
<url>http://maven.net.cn/content/g...;/url>
<mirrorOf>central</mirrorOf>
</mirror>
</mirrors>
......
</settings>
<mirrorOf>的值为central,表示该配置为中央仓库的镜像,任何对于中央仓库的请求都会转至该镜像,用户也可以使用同样的方法配置其他仓库的镜像,另外三个元素id,name,url与一般仓库配置无异,表示该镜像仓库的唯一标识,名称以及地址
- 若该镜像要进行验证,即基于该id配置仓库认证
<settings\> ..... <mirrors\> <mirror\> <id\>internal-repository</id\> <name\>Internal Repository Mananger</name\> <url\>http://192.168.1.100/maven2/</url\> <mirrorOf\>\*</mirrorOf\> </mirror\> </mirrors\> ..... </settings\>
以上mirrorOf元素的值为*,表示该配置是所有Maven仓库的镜像,对于任何远程仓库的请求都会被转至该指定的仓库,如果镜像仓库需要验证,则配置一个id为internal-repository的<server>即可
- <mirrorOf>*<mirrorOf>:匹配所有远程仓库
- <mirrorOf>external:*<mirrorOf>:匹配所有远程仓库,使用localhost的除外,使用file://协议的除外,也就是说,匹配所有不在本机上的远程仓库
- <mirrorOf>repo1,repo2<mirrorOf>:匹配仓库repo1和repo2,用逗号分隔多个仓库
- <mirrorOf>*,! repo1<mirrorOf>:匹配所有的远程仓库,除repo1外,使用感叹号将仓库从匹配中排除
- 需要注意的是,由于镜像仓库完全屏蔽了被镜像仓库,当镜像仓库不稳定或停止服务时,Maven仍将无法访问被镜像仓库,因此无法下载构件
五. 生命周期和插件
5.1 Maven的生命周期
在Maven出现之前,项目构建的生命周期就已经存在,但是不同的公司和开发人员虽然同样在做构件工作,其对于不同的项目却不能够重用,只能重新定制开发,而Maven的生命周期就是为了对所有的构件过程进行抽象和统一
- 这个生命周期包含了项目的清理,初始化,编译,测试,打包,集成测试,验证,部署和站点生成等几乎所有的构件步骤,也就是说几乎所有项目的构件,都能映射到这样一个生命周期上
Maven的生命周期是抽象的,这意味着生命周期本身不做任何实际的工作,在Maven的设计中,实际的任务(如编译主代码)都交由插件完成,这种思想和设计模式的模方法类似,在父类中定义算法的整体结构,子类可以通过实现或重写父类的方法来控制实际的行为
public abstract class Template{
public void build(){
initialize();
compile();
test();
packagee();
integrationTest();
deploy();
}protect abstract void initialize();
protect abstract void compile();
protect abstract void test();
protect abstract void packagee();
protect abstract void integrationTest();
protect abstract void deploy();
}
- 在Maven的生命周期中抽象了各个步骤,定义了他们的次序,但是没有提供具体的实现,而通过插件机制为每个构件步骤绑定一个或多个插件行为,而且Maven为大多数构件步骤都绑定了默认的插件,例如,针对编译的maven-compiler-plguin,针对测试的maven-surefire-plugin等
- Maven定义的生命周期和插件机制一方面保证了所有Maven项目有一致的构件标准,另一方面又通过默认的插件简化和稳定实际项目的构件,此外,该机制还提供了足够的扩展,用户可以通过配置现有的插件或自定义插件来自定义构件行为
三套声明周期
Maven拥有三套相互独立的生命周期,他们分别为clean,default和site,每个生命周期包含一些阶段,这些阶段是有序的,并且后面的阶段依赖于前面的阶段,但是三套声明周期本身是互相独立的
clean生命周期:清理项目
- pre-clean:执行一些清理前需要完成的工作
- clean:清理上次构件生成的文件
- post-clean:执行一些清理后需要完成的工作
default声明周期:定义了真正的构件所需执行的所有步骤
- validate
- initialize
- generate-sources
- process-sources:处理项目主资源文件,一般来说针对/src/main/resources目录的内容进行变量替换等工作后,复制到项目输出的主classpath目录中
- compile:编译项目的主源码,一般来说针对/src/main/java目录下的Java文件至目录输出的主classpath目录中
- process-classes
- generate-test-sources
- process-test-sources:处理项目测试资源文件,一般来说针对/src/test/resources目录的内容进行变量替换工作后,复制到项目输出的测试classpath目录
- test-compile:编译项目的测试代码,一般来说针对/src/test/java目录下的java文件至输出的测试classpath目录中
- test:使用单元测试框架运行测试,测试代码不会被打包或部署
- prepare-package
- package:接收编译好的代码,打包成可发布的格式,如jar
- pre-integration-test
- integration-test
- post-integration-test
- verify
- install:将包安装到Maven本地仓库,供本地其他Maven项目使用
- deploy:将最终的包复制到远程仓库,供其他开发人员和Maven项目使用
site声明周期:建立和发布项目站点,Maven能够基于POM所包含的信息,自动生成一个友好的站点供交流和发布项目信息
- pre-site:执行一些在生成项目站点之前需要完成的工作
- site:生成项目站点文档
- post-site:执行一些在生成项目站点后需要完成的工作
- site-deploy:将生成的项目站点发布到服务器上
命令行与生命周期
从命令行执行Maven任务最主要方式就是调用Maven的生命周期,各个生命周期是相互独立的,而生命周期的阶段是有前后依赖关系的
- mvn 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阶段
5.2 插件
5.2.1 插件目标和绑定
Maven的核心仅仅定义了抽象的生命周期,具体的任务是交由插件完成的,插件以独立的构件形式存在
- 对于插件本身而言,为了能够复用代码,它往往能够完成多个任务,为每个功能编写一个插件显然不合理,因为这些任务背后有大量可复用的代码,因此,这些功能聚集到一个插件里面,每个功能就是一个插件目标
Maven的生命周期和插件相互绑定,用以完成实际的构件任务,具体而言,是生命周期的阶段与插件的目标相互绑定,以完成某个具体的构件任务
- 例如:编译这一任务对应了default生命周期的compile这一阶段,而maven-compiler-plugin这一插件的compile目标能完成此任务
自定义绑定
除了内置绑定以外,用户还能够自己选择将某个插件目标绑定到生命周期的某个阶段上,这种自定义绑定方式能让Maven项目在构件过程中执行更多更丰富的任务
- 当需要创建项目的源码jar包,maven-source-plugin可以帮我们完成任务,但是内置的插件绑定关系中并没有涉及这一任务,因此需要自行配置,它的jar-no-fork目标能将项目主代码打包成jar文件,可以将其绑定到default生命周期的verify阶段,在执行完集成测试和安装构件之前创建源码jar包
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<version>2.1.1</version>
<executions>
<execution>
<id>attach-sources</id>
<phase>verify</phase>
<goals>
<goal>jar-no-fork</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>在POM的build元素下的plugins子元素中声明插件的使用,上例用到了是maven-source-plugin,其groupId为org.apache.maven.plugins,用户总是应该声明一个非快照版本,这样可以避免由于插件版本变化造成的构件不稳定性
- 除了基本的插件坐标声明外,还有插件执行配置,executions下每个execution子元素可以用来配置执行一个任务,该例配置了一个id为attach-sources的任务,通过phase配置将其绑定到verify生命周期阶段上,再通过goals配置指定要执行的插件目标,在运行mvn verify时就会执行该任务
有时候.即是不通过phase元素配置生命周期阶段,插件目标也能绑定到生命周期中去,原因是:有很多插件的目标在编写时就已经定义了默认绑定阶段,可以使用maven-help-plugin查看详细信息
mvn help:describe -Dplugin=org.apache.maven.plugins:maven-source-plugin:2.1 -Ddetail
- Bound to phase : package 默认绑定到package生命周期
- 当插件目标被绑定到不同生命周期阶段的时候,其执行顺序会由生命周期阶段的先后顺序决定,如果多个目标被绑定到同一个阶段,他们的顺序就由插件声明的先后顺序决定
5.2.2 插件配置
用户可以通过配置插件目标的参数,进一步调整插件目标所执行的任务,几乎所有Maven插件的目标都可以配置参数,用户可以通过命令行和POM配置等方式来配置这些参数
命令行插件配置
在日常的Maven使用中,我们通常从命令行输入并执行Maven命令,很多插件目标的参数都支持从命令行配置,用户可以在Maven命令中使用-D参数,并伴随一个参数键=参数值的形式,来配置插件目标的参数
mvn install -Dmaven.test.skip = ture:给maven.surefire-plugin提供一个maventest.skip参数,当参数为true时,就会跳过执行测试
- 参数-D是Java自带的,其功能是通过命令行设置一个Java系统属性,Maven简单地重用了该参数,在准备插件的时候检查系统属性来实现插件参数的配置
POM中插件全局配置
并不是所有的插件参数都适合用命令行配置,有些参数的值从项目创建到项目发布都不会改变,或者说很少改变,这种情况下在POM文件中一次性配置比重复在命令行输入更合理
- 用户可以在声明插件的时候,对插件进行一个全局的配置,所有基于该插件的目标任务,都会使用这些配置,如:maven-compiler-plugin来编译1.8版本的源文件,生成与JVM1.8兼容的字节码文件
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifact>maven-compiler-plugin</artifact>
<version>2.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
5.2.3 获取插件信息
当遇到一个构建任务时,不仅需要知道去哪里找到合适的插件,还需要详细的了解插件的配置点,由于Maven的插件非常多,其中大部分没有完善的文档,因此,通过帮助命令来了解插件显得非常重要
使用maven-help-plugin描述插件
除了访问在线的插件文档外,可以借助maven-help-plugin来获取插件的详细信息
mvn help:decribe -Dplugin = org.apache.maven.plugins:maven-compiler-plugin:2.1
- 这里执行的是maven-help-plugin的decribe目标,在参数plugin中输入需要描述插件的groupId,artifactId和version,Maven在命令行的输出maven-compiler-plugin的简要信息,包括插件的坐标,目标前缀和目标等信息
在描述插件时,可以省去版本信息,Maven会自动获取最新版本来进行表述,并可以用目标前缀来替换坐标
- mvn help:describe -Dplugin=compiler
如果仅仅描述某个插件目标的信息,则加上goal参数,更详细的信息,则加上detail参数
- mvn help:describe -Dplugin=compiler -Dgoal=compile -Ddetail
5.2.4 插件解析机制
为了方便用户使用和配置插件,Maven不需要用户提供完整的插件坐标信息,就可以解析得到正确的插件
插件仓库
- 与依赖构件一样,插件构件同样基于坐标存储在Maven仓库中,在需要的时候Maven先从本地仓库寻找,如果不存在,则从远程仓库查找,找到插件之后,再下载到本地仓库使用
- 不同于repositories以及repository子元素,插件的远程仓库使用pluginRepositories和pluginRepository配置
<pluginRepositories>
<pluginRepository>
<id>central</id>
<name>Maven Plugin Resository</name>
<url>http://repo1.maven.org/maven2</url>
<layout>default</layout>
<snapshots>
<enabled>false</enabled>
</snapshots>
<release>
<enabled>true</enabled>
<updatePolicy>never</updatePolicy>
</release>
</pluginRepository>
</pluginRepositories>插件默认的groupId
- 在POM中配置插件时,如果该插件时Maven的官方插件(即groupId为org.apache.maven.plugins),就可以省略groupId,Maven在解析该插件的时候,会自动使用默认groupId不起
<build>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>2.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>解析插件版本
同样为了简化插件的配置和使用,在用户没有提供插件版本的情况下,Maven会自动解析插件版本
- 首先Maven在超级POM中为所有核心插件设定了版本,超级POM是所有Maven项目的父POM,所有项目都继承这个超级POM配置,因此即使用户不加任何配置,Maven在使用核心插件时,它的版本就已经确定了
- 如果用户使用的某个插件没有设定版本,并且这个插件也不属于核心插件,Maven机会去检查所有仓库中可用的版本,通过仓库元数据groupId/artifactId/maven-metadata.xml文件,如maven-compiler-plugin插件为例,它在org/apache/maven/plugins/maven-compiler-plugin/maven-metadata.xml
<?xml version="1.0" encoding="UTF-8"?>
<metadata>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<versioning>
<latest>2.1</latest>
<release>2.1</release>
<versions>
<version>2.0-beta-1</version>
<version>2.1</version>
<version>2.2</version>
<version>2.3</version>
</versions>
<lastUpdated>20190102092331</lastUpdated>
</versioning>
</metadata>- Maven遍历本地仓库和所有远程仓库,将仓库元数据合并后,就能计算出latest和release的值,maven3将版本解析到最新的非快照版,如2.1
5.2.5 解析插件前缀
- Maven命令支持使用插件前缀来简化插件的使用,插件前缀和groupId:artifact是一一对应的,这种匹配关系存储在仓库元数据中,与之前提到的groupId/artifactId/maven-metadata.xml不同,这里仓库元数据为groupId/maven-metadata.xml,一般插件都位于/org/apache/maven/plugins/和org/code-haus/mojo/,可以通过settings.xml配置让Maven检查其他groupId上的插件仓库元数据
<settings>
<pluginGroups>
<pluginGroup>com.my.plugins</pluginGroup>
</pluginGroups>
</settings>- 在仓库的元数据文件中可以看到插件的前缀定义
<metadata>
<plugins>
<plugin>
<name>Maven Clean Plugin</name>
<prefix>clean</prefix>
<artifact>maven-clean-plugin</artifact>
</plugin>
<plugin>
<name>Maven Compiler Plugin</name>
<prefix>compile</prefix>
<artifact>maven-compile-plugin</artifact>
</plugin>
<plugin>
<name>Maven Dependency Plugin</name>
<prefix>dependency</prefix>
<artifact>maven-dependency-plugin</artifact>
</plugin>
</plugins>
</metadata>
六.聚合与继承
- Maven的集合特性能够把项目各个模块聚合在一起构建,而Maven的继承特性则能帮助抽泣各模块相同的依赖和插件等配置,在简化POM的同时,还能促进各个模块配置的一致性
6.1 集合
aggregator
<modelVersion>4.0.0</modelVersion>
<groupId>com.lsy.project</groupId>
<artifact>project-aggregator</artifact>
<version>1.0.0-SNAPSHOT</version>
<packaging>pom</packaging>
<name>Aggregator</name>
<modules>
<module>project-email</module>
<module>project-register</module>
</modules>
- 对于聚合模块,其打包方式packaging的值必须为pom
通过modules元素来实现聚合,用户可以通过在一个打包方式为pom的Maven项目中声明任意数量的module元素来实现模块的聚合,这里每个module的值都是一个当前POM的相对目录
- 如aggregator的POM路径为.../project-aggregator/pom.xml,那么project-email对应 的目录为.../project-aggregator/project-email/,并且目录中包含了pom.xml,src/main/java,src/test/java等内容,离开了project-aggregator也能独立创建=
- 聚合模块和其他模块的目录结构并非一定要父子关系,也可以是平行关系
<modules>
<module>../prject-email</module>
<module>../project-register</module>
</modules>- 当在聚合模块中执行mvn clean install时,Maven首先解析聚合模块的POM,分析要构件的模块,并计算出一个反应堆构件顺序(Reactor Build Order),然后根据这个顺序构件各个模块
聚合模块的构件顺序
- Maven按序读取POM文件,如果POM没有依赖模块,那么就构件模块,否则就先构件其依赖模块
6.2 继承
从以上的聚合模块和其他模块中可以看到,多个被管理模块的POM文件中会有大量重复的相同配置,他们有相同的groupId和version,相同的依赖,相同的插件配置,而通过POM文案的继承可以消除重复
project-parent父模块
<modelVersion>4.0.0</modelVersion>
<groupId>com.lsy.project</groupId>
<artifactId>project-parent</artifactId>
<version>1.0.0-SNAPSHOT</version>
<packaging>pom</packaging>
<name>Parent</name>
- 父模块的POM文件使用与其他模块一直的groupId和version,它的packaging方式为pom,与聚合模块一致
- 父模块主要是为了消除配置的重复,因此它本身不包含除POM文件之外的项目文件,也就不需要src/main/java之类的文件夹了
project-email子模块
<parent>
<groupId>com.lsy.project</groupId>
<artifactId>project-parent</artifactId>
<version>1.0.0-SNAPSHOT</version>
<relativePath>../project-parent/pom.xml</relativePath>
</parent>
<artifactId>project-email</artifactId>
<name>Email</name>
<dependencies>
.....
</dependencies>
使用parent元素声明父模块,parent下子元素groupId,artifactId,version指定父模块的坐标,元素relativePath表示父模块POM的相对路径,表示Email模块和其父模块是在平行的目录下
- 当构件项目时,Maven会首先根据relativePath检查父POM文件,如果找不到再从本地仓库找,relativePath的默认值为../pom.xml,也就是说,Maven默认父POM在上一层目录
- 子模块没有groupId和version,是因为子模块隐式的从父模块继承了这两个元素,从而消除了不必要的配置
可继承的POM元素
- groupId:项目组ID,项目坐标的核心元素
- version:项目版本,项目坐标的核心元素
- description:项目的描述信息
- organization:项目的组织信息
- inceptionYear:项目的创始年份
- url:项目的URL地址
- develops:项目的开发者信息
- contributors:项目贡献者信息
- distributionManagement:项目的部署配置
- issueManagement:项目的缺陷跟踪系统信息
- ciManagement:项目的持续集成系统信息
- scm:项目的版本控制系统信息
- mailingList:项目的邮件列表信息
- properties:自定义属性
- dependencies:项目的依赖配置
- dependencyManagement:项目的依赖管理配置
- repositories:项目的仓库配置
- pluginRepositories:项目的插件仓库配置
- build:包括项目的源码目录配置,输出目录配置,插件配置,插件管理配置等
- reporting:项目的输出目录配置,报告插件配置等
依赖管理
- dependencies元素是可以被继承的,说明依赖是会被继承的,所以我们可以将子模块共有的依赖配置到父模块中,子模块就可以移除这些依赖,简化配置
上述方法是可行的,但是可能将来会有新的模块并不需要父模块中的一些依赖,这就会产生不合理的现象,从而Maven提供了dependencyManagement元素,既能让子模块继承到父模块的依赖配置,又能保证子模块依赖使用的灵活性
- 在dependencyManagement元素下的依赖声明不会引入实际的依赖,不过它能够约束dependencies下的依赖使用
<modelVersion\>4.0.0</modelVersion\> <groupId\>com.lsy.project</groupId\> <artifactId\>project-parent</artifactId\> <version\>1.0.0-SNAPSHOT</version\> <packaging\>pom</packaging\> <name\>Parent</name\> <properties\> <springframework.version\>4.3.1</springframework.version\> </properties\> <dependencyManagement\> <dependencies\> <dependency\> <groupId\>org.springframework</groupId\> <artifactId\>spring-core</artifactId\> <version\>${springframwork.version}</version\> </dependency\> <dependency\> <groupId\>org.springframework</groupId\> <artifactId\>spring-beans</artifactId\> <version\>${springframwork.version}</version\> </dependency\> <dependency\> <groupId\>org.springframework</groupId\> <artifactId\>spring-context</artifactId\> <version\>${springframwork.version}</version\> </dependency\> </dependencies\> </dependencyManagement\> * 这里使用dependencyManagement声明的依赖既不会给parent模块引入依赖,也不会给子模块引入依赖,不过这段配置是会被继承的 * 子模块POM <parent\> <groupId\>com.lsy.project</groupId\> <artifactId\>project-parent</artifactId\> <version\>1.0.0-SNAPSHOT</version\> <relativePath\>../project-parent/pom.xml</relativePath\> </parent\> <artifactId\>project-email</artifactId\> <name\>Email</name\> <dependencies\> <dependency\> <groupId\>org.springframework</groupId\> <artifactId\>spring-core</artifactId\> </dependency\> <dependency\> <groupId\>org.springframework</groupId\> <artifactId\>spring-beans</artifactId\> </dependency\> <dependency\> <groupId\>org.springframework</groupId\> <artifactId\>spring-context</artifactId\> </dependency\> </dependencies\> * 使用这种依赖管理机制,可以在父POM中使用dependencyManagement声明依赖能够统一项目规范中的依赖版本,当版本在父POM中声明之后,子模块使用依赖时就不需要声明了.也不会发生多个子模块使用依赖版本不一致的情况 * scoper元素的import依赖范围只在dependencyManagement元素下才有效果,使用该范围的依赖通常指向一个POM文件,作用是将POM中的dependencyManagement配置导入并合并到当前POM的dependencyManagement元素中 * 所以除了复制配置和继承父模块这两种方式外,还可以通过import范围依赖导入这一配置 <dependencyManagement\> <dependencies\> <dependency\>com.lsy.project</dependency\> <artifactId\>project-parent</artifactId\> <version\>1.0-SNAPSHOT</version\> <type\>pom</type\> <scope\>import</scope\> </dependencies\> </dependencyManagement\>
插件管理
- Maven提供了dependencyManagement元素帮助管理依赖,类似的,Maven也提供了pluginManagement元素管理插件,该元素中配置的依赖同样不会造成实际的插件调用行为,而POM文件中配置了真正的plugin元素,并且groupId和artifact一致时,才会产生实际的插件行为
- 父模块
<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<version>3.1.1</version>
<executions>
<execution>
<id>attach-source</id>
<phase>verify</phase>
<goals>
<goal>jar-no-fork</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</pluginManagement>
</build>子模块
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifact>maven-source-plugin</artifact>
</plugin>
</plugins>
</build>
- 子模块使用了maven-source-plugin插件,同时又继承了父模块的pluginManagement配置
集合与继承的关系
多模块的聚合与继承其实是两个概念,其目的是完全不同的,前者为了方便快速的构件项目,后者主要为了消除重复配置
- 对于聚合模块来说:它知道有哪些模块被聚合,但是那些被聚合的模块并不知道这个聚合模块的存在
- 对于继承关系的父POM来说:它不知道有哪些子模块继承于它,但是那些子模块都必须知道自己的父模块是什么
- 在实际项目中,一个POM往往即是聚合模块,又是父模块
约定优于配置
Maven提倡"约定优于配置",Maven只需要一个简单的POM文件,就可以完成清除,构件等任务
- 源码目录:src/main/java/
- 编译输出目录为:target/classes/
- 打包方式为:jar
- 包输出目录为:target/
- Maven此机制的来源就是超级POM文件,此文件在$MAVEN_HOME/lib/maven-model-builder-x.x.x.jar中的org/apache/maven/model/pom-4.0.0.xml路径下
<repositories>
<repository>
<id>central</id>
<name>Maven Repository Swithboard</name>
<url>http://repo1.maven.org/maven2</url>
<layout>default</layout>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>central</id>
<name>Maven Plugin Repository</name>
<url>http://repo1.maven.org/maven2</url>
<layout>default</layout>
<snapshots>
<enabled>false</enabled>
</snapshots>
<releases>
<updatePolicy>never</updatePolicy>
</releases>
</pluginRepository>
</pluginRepositories>
<build>
<!--> 定义了项目的主输出目录 </!-->
<directory>${project.basedir}/target</directory>
<!--> 主代码输出目录 </!-->
<outputDirectory>${project.build.directory}/classes</outputDirectory>
<!--> 最终构件的名称格式 </!-->
<finalName>${project.artifactId}-${project.version}</finalName>
<!--> 测试代码输出目录 </!-->
<testOutputDirectory>${project.build.directory}/test-
classes</testOutputDirectory>
<!--> 主源码目录 </!-->
<sourceDirectory>${project.basedir}/src/main/java</sourceDirectory>
<!--> 脚本源码目录 </!-->
<scriptSourceDirectory>src/main/scripts</scriptSourceDirectory>
<!--> 测试源码目录 </!-->
<testSourceDirectory>${project.basedir}/src/test/java</testSourceDirectory>
<!--> 主资源目录 </!-->
<resources>
<resource>
<directory>${project.basedir}/src/main/resources</directory>
</resource>
</resources>
<!-- >测试资源目录 </!-->
<testResources>
<testResource>
<directory>${project.basedir}/src/test/resources</directory>
</testResource>
</testResources><!--> 核心插件版本 </!-->
<pluginManagement>
<plugins>
<plugin>
<artifactId>maven-antrun-plugin</artifactId>
<version>1.3</version>
</plugin>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<version>2.3</version>
</plugin>
<plugin>
<artifactId>maven-clean-plugin</artifactId>
<version>2.3</version>
</plugin>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>2.0.3</version>
</plugin>
......
</plugins>
</pluginManagement>
</build>
七. Nexus私服
7.1 Nexus内置的仓库
Nexus仓库有四种类型
- group:仓库组
- hosted:宿主仓库
- proxy:代理仓库
- virtual:虚拟仓库
* Maven可以从宿主仓库下载构件,Maven也可以从代理仓库下载构件,而代理仓库会简介的从远程仓库下载并缓存构件 * 而一般为了方便,我们会从仓库组下载构件,而仓库组没有实际的内容,它只是管理一组实际的仓库,当它接收到请求时,它会转向其包含的宿主仓库或代理仓库获得实际构件的内容
仓库的Policy属性
- Release:发布版
- Snapshot:快照版
7.2 配置Maven从Nexus下载构件
- 在POM文件中配置仓库和插件仓库------只对当前项目有效
<project>
.......
<repositories>
<repository>
<id>nexus</id>
<name>Nexus</name>
<url>http://localhost:8081/nexus/content/groupId/public/</url>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>nexus</id>
<Name>Nexus</Name>
<url>http://localhost:8081/nexus/content/groupId/public/</url>
<release>
<enabled>true</enabled>
</release>
<snapshots>
<enabled>true</enabled>
</snapshots>
</pluginRepository>
</pluginRepositories>
.......
</project>在settings.xml文件中配置---全局有效
- settings.xml文件并不支持直接配置repositories和pluginRepositories,但是可以通过Maven提供的Profile机制,让用户将仓库配置到settings.xml中的Profile中
<settings\> .... <profiles\> <profile\> <id\>nexus</id\> <repositories\> <repository\> <id\>nexus</id\> <name\>Nexus</name\> <url\>http://localhost:8081/nexus/content/groupId/public/</url\> <release\> <enabled\>true</enabled\> </release\> <snapshots\> <enabled\>ture</enabled\> </snapshots\> </repository\> </repositories\> <pluginRepositories\> <pluginRepository\> <id\>nexus</id\> <Name\>Nexus</Name\> <url\>http://localhost:8081/nexus/content/groupId/public/</url\> <release\> <enabled\>true</enabled\> </release\> <snapshots\> <enabled\>true</enabled\> </snapshots\> </pluginRepository\> </pluginRepositories\> </profile\> </profiles\> <!--> 激活指定id的profile </!--> <activeProfiles\> <activeProfile\>nexus</activeProfile\> </activeProfiles\> </settings\>
以上配置已经能让Maven项目从Nexus私服下载构件了,但是Maven仍然会去访问中央仓库,现在我们想要将所有请求都仅仅通过私服,这就需要借助于Maven镜像了
<settings>
.....
<mirrors>
<mirror>
<id>nexus</id>
<mirrorOf>*</mirrorOf>
<url>http://localhost:8081/nexus/content/groupId/public/</url>
</mirror>
</mirrors><profiles>
<profile>
<id>nexus</id>
<repositories>
<repository>
<id>central</id>
<name>central</name>
<url>http://central</url>
<release>
<enabled>true</enabled>
</release>
<snapshots>
<enabled>ture</enabled>
</snapshots>
</repository>
</repositories><pluginRepositories>
<pluginRepository>
<id>central</id>
<Name>Nexus</Name>
<url>http://central</url>
<release>
<enabled>true</enabled>
</release>
<snapshots>
<enabled>true</enabled>
</snapshots>
</pluginRepository>
</pluginRepositories>
</profile>
</profiles><!--> 激活指定id的profile </!-->
<activeProfiles>
<activeProfile>nexus</activeProfile>
</activeProfiles>
</settings>
- 这里仓库和插件仓库配置的id都为central,它们将会覆盖POM中央仓库的配置,并且这里的url已经无关紧要,因为所有的请求都会通过镜像访问私服地址
7.3 部署构件至Nexus
如果只是为了代理外部公共仓库,那么Nexus的代理仓库就已经完全足够了,对于另一类Nexus仓库---宿主仓库来说,他们主要作用是存储组织内部的,或一些无法从公共仓库获得的第三方构件,用户可以通过配置Maven自动部署构件至Nexus的宿主仓库
使用Maven部署构件至Nexus
- 日常开发生成的快照版本构件可以直接部署到Nexus中策略为Snapshot的宿主仓库中,项目正式发布的构件则应该部署到Nexus中策略为Release的宿主仓库中
- POM文件配置
<project>
.....
<distributeManagement>
<repository>
<id>nexus-releases</id>
<name>Nexus Release Repository</name>
<url>http://localhost:8081/nexus/content/repositories/
release/</url>
</repository>
<snapshotRepository>
<id>nexus-snapshots</id>
<name>Nexus Snapshots Repository</name>
<url>http://localhost:8081/nexus/content/repositories/
snapshots</url>
</snapshotRepository>
</distributeManagement>
.....
</project>- Nexus的仓库对于匿名用户只是可读的,为了能够部署构件,还需要在settings.xml中配置认证信息
<settings>
....
<servers>
<server>
<id>nexus-releases</id>
<username>admin</username>
<password>admin123</password>
</server>
<server>
<id>nexus-snapshots</id>
<username>admin</username>
<password>admin123</password>
</server>
</servers>
....
</settings>
八. Profile
- 一个优秀的构件系统必须足够灵活,它应该能够让项目在不同的环境下都能够成功的构件,例如:开发环境,测试环境和产品环境,这些环境的数据库配置不尽相同,那么项目构建时就需要能够识别其所在的环境并正确使用配置
8.1 属性
<properties>
<springframework.version>4.3.1</springframework.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${springframwork.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>${springframwork.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
- 这是最常见的Maven属性使用的方式,通过properties元素使得用户自定义一个或多个Maven属性,然后在POM的其他地方使用${属性名称}的方式来引用该属性,这种做法的最大意义是消除重复
Maven的属性有六类
内置属性
- ${basedir}:表示项目根目录,即包含pom.xml文件的目录
- ${version}:表示项目的版本
POM属性:用户可以使用该类属性引用POM文件中对应元素的值
${project.artifactId}:对应<project><artifactId>元素的值
- ${project.build.sourceDirectory}:项目的主源码目录.默认为/src/main/java/
- ${project.build.testSourceDirectory}:项目的测试代码源码目录.默认为/src/test/java/
- ${project.build.directory}:项目构件输出目录.默认为/target
- ${project.build.outputDirectory}:项目主代码编译输出目录.默认为/target/classes
- ${project.build.testOutputDirectory}:项目测试代码编译输出目录.默认为/target/test-classes
- ${project.build.groupId}:项目的groupId
- ${project.build.artifactId}:项目的artifactId
- ${project.build.build.finalName}:项目打包输出文件的名称,默认为${project.artifactId}
-${project.version}
- 自定义属性:<properties>元素下自定义的Maven属性
- settings属性:与POM属性同理,用户使用settings.来引用,如:${settings.localRepository}指向用户本地仓库的地址
Java系统属性:所有Java系统属性都可以使用Maven属性引用,如${user.home}指向用户目录
- 可以通过mvn help:system查看所有的java系统属性
环境变量属性:所有环境变量都可以用env.来引用,例如:${env.JAVA_HOME}
- 可以通过 mvn help:system查看所有的环境变量
8.2 资源过滤
- 在不同的开发环境中,项目的源码应该使用不同的方式进行构件,最常见的就是数据库的配置了,在开发中,有些项目会在src/main/resources/目录下放置不同环境下的数据库配置
database.jdbc.driverClass = com.mysql.jdbc.Driver
database.jdbc.connectionURL = jdbc:mysql://localhost:3306/dev
database.jdbc.username = dev
database.jdbc.password = dev-passwd
database.jdbc.driverClass = com.mysql.jdbc.Driver
database.jdbc.connectionURL = jdbc:mysql://localhost:3306/test
database.jdbc.username = test
database.jdbc.password = test-passwd- 为了应对环境的变化,我们可以使用Maven属性将这些会发生变化的部分提取出来
database.jdbc.driverClass = ${db.driver}
database.jdbc.connectionURL = ${db.url}
database.jdbc.username = ${db.username}
database.jdbc.password = ${db.password}在settings.xml中通过profile定义不同环境下的配置数据
<profiles>
<profile>
<id>dev</id>
<properties>
<db.driver>com.mysql.jdbc.Driver</db.driver>
<db.url>jdbc:mysql://localhost:3306/dev</db.url>
<db.username>dev</db.username>
<db.password>dev-passwd</db.password>
</properties>
</profile><profile>
<id>test</id>
<properties>
<db.driver>com.mysql.jdbc.Driver</db.driver>
<db.url>jdbc:mysql://localhost:3306/test</db.url>
<db.username>test</db.username>
<db.password>test-passwd</db.password>
</properties>
</profile>
</profiles>
需要注意的是:
- Maven属性默认只会在POM文件中才会被解析,也就是说${db.username}放到POM文件中会变成dev或test,但是在src/main/resources/目录下仍然还是${db.username},因此,需要让Maven解析资源文件中的Maven属性
- 资源文件处理的是maven-reosurces-plugin做事,它默认的行为只是将项目主资源文件复制到主代码编译输出目录
开启资源过滤
- Maven默认的主资源目录和测试目录的定义是在超级POM文件中
<resources\> <resource\> <directory\>${project.basedir}/src/main/resources</directory\> <filtering\>true</filtering\> </resource\> </resources\> <testResources\> <testResource\> <directory\>${project.basedir}/src/test/resources</directory\> <filtering\>true</filtering\> </testResource\> </testResources\>
- 通过mvn clean install -Pdev : 通过mvn 的 -P参数表示在命令行激活一个profile=dev的profile
8.3 Profile
激活Pofile
命令行激活
用户可以使用mvn命令行参数-P 加上profile的id来激活profile,多个id之间逗号分隔
- mvn clean install -Pdev,-Ptest
settings文件显示激活
- 如果用户希望某个profile默认一直处于激活状态,就可以配置settings.xml文件的activeProfile元素,表示其配置的profile对所有项目都处于激活状态
<settings>
.....
<activeProfiles>
<activeProfile>dev</activeProfile>
</activeProfiles>
.....
</settings>系统属性激活
- 用户可以配置当前某系统属性存在时,自动激活profile
<profiles>
<profile>
<activation>
<property>
<name>test</name>
</property>
</activation>
.....
</profile>
</profiles>- 用户可以配置当前某系统属性存在且等于a时,自动激活profile
<profiles>
<profile>
<activation>
<property>
<name>test</name>
<value>a</value>
</property>
</activation>
.....
</profile>
</profiles>用户可以在命令行声明系统属性
- mvn clean install -Dtest=x
操作系统环境变量激活
- Profile还可以根据操作系统环境激活
<profiles>
<profile>
<activation>
<os>
<name>Windows10</name>
</os>
</activation>
.....
</profile>
</profiles>文件存在与否激活
- Maven可以根据项目中某个文件是否存在来决定是否激活profile
<profiles>
<profile>
<activation>
<file>
<missing>a.properties</missing>
<exists>b.properties</exists>
</file>
</activation>
.....
</profile>
</profiles>默认激活
- 用户可以在定义profile的时候指定其默认激活
<profiles>
<profile>
<id>dev</id>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
.....
</profile>
</profiles>以上激活优先级从上之下依次减小
- 可以通过mvn help:active-profiles来查看当前激活的profile
- 可以通过mvn help:all-profiles来查看所有的profile
profile的种类
根据具体的需要,可以在一下位置声明profile
Maven
一.Maven简介
1.1 何为maven
Maven可翻译为"知识的积累" or"专家",是一款成功的开源跨平台的项目管理工具,无论小型的开源类库项目,还是大型的企业级应用;无论传统的瀑布式开发,还是流行的敏捷模式,Maven都能大显身手.
1.1.1 构建工具
我们一直在不停的寻找避免重复的方法,设计的重复,编码的重复,文档的重复,当然还有构建的重复.Maven最大化的消除了构建的重复,抽象了构建生命周期,并且为绝大部分的构建任务提供了已实现的插件,我们不需要再定义过程,甚至不需要去实现这些过程中的一些任务,只需要遵循Maven中的约定.
同时Maven帮助我们标准化构建过程,以前十个项目可能有十种构建方式,有了Maven后,所有项目的构建命令都是一直且简单的.因此Maven作为一个构建工具:
- 可以帮我们自动化构建,
- 可以帮我们抽象构建过程
- 提供构建任务是实现
- 跨平台
- 对外提供一直的操作接口
1.1.2 不仅仅是构建工具
Maven不仅是构建工具,还是一个依赖管理工具和项目信息管理工具,提供中央仓库来帮忙我们自动下载构建,通过引入一套经纬机制来系统准确的定位每一个构建(artifact).
1.1.3 Maven
在Maven之前,有过程式的Make和Ant,开发者需要显示的指定每一个目标,以及完成该目标所需要执行的任务.针对每一个项目,开发者都需要重新编写这一过程,而其中就隐含着大量重复.
而Maven是声明式的,项目构建过程和过程各个阶段所需的工作都由插件实现,并且大部分插件都是现成的,开发者只需要声明项目的基本元素,Maven就执行内置的,完整的构建过程.
二.Maven的使用
2.1 pom文件
Maven项目的核心是pom.xml,POM(Project Object Model,项目对象模型)定义了项目的基本信息,用于描述项目如何构建,声明项目依赖等.
<modelVersion>4.0.0</modelVersion>:modelVersion指定了当前Pom模型的版本,固定为4.0.0<groupId>com.lsy</groupId>:groupId定义了项目属于哪个组,这个组往往和项目所在的组织和公司相关
<artifactId>hello-world</artifactId>:artifactId定义了当前Maven项目在组中唯一的ID
<version>1.0-SNAPSHOT</version>:version指定了Hello World项目当前的版本,其中SNAPSHOT意为快照,说明该项目还处于开发中
<name>Maven Hello World Project</name>:name元素声明了一个对于用户更为友好的项目名称.非必须
当运行mvn clean compile命令:clean告诉Maven清理输出目录target/,compile告诉Maven编译项目主代码,从输出中看到Maven首先执行clean:clean任务,删除target/目录(默认情况下,Maven构建的所有输出都在target目录中,接着执行resources:resources任务(未定义项目资源),最后执行compiler:compile任务,将项目主代码编译至target/classes目录
其中clean:clean,resources:resources和compiler:compile对应了Maven插件及插件目标,比如clean:clean是clean插件的clean目标,compiler:compile是compiler插件的compile目标
2.2 测试
首先添加Junit依赖
<dependencies> <dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.7</version>
<scope>test</scope>
</dependency>
<dependencies>
其中scope元素为依赖范围,若依赖范围为test则表示该依赖只对测试有效,如果不声明依赖范围,则默认是compile,表示该依赖对主代码和测试代码均有效
当运行mvn clean test命令,Maven实际执行的不止test任务,而是clean:clean,resources:resources,compiler:compile,resources:testResources以及compiler:testCompile,即在执行测试之前,会先自动执行项目资源处理,主代码编译,测试资源处理,测试代码编译等工作,这是Maven生命周期的一个特性.
由于Maven核心插件之一compiler插件默认支持Java1.3,因此需要配置插件支持Java1.8
<build> <plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.8<source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
2.3 打包和运行
将项目进行编译,测试之后,下一个重要的步骤就是打包(package),当没有指定打包类型时,默认打包类型是jar,当执行mvn clean package命令,Maven会在打包之前进行编译,测试等操作,之后通过jar:jar任务负责打包,实际上就是jar插件的jar目标将项目主代码打包成一个hello-world-1.0-SNAPSHOT.jar的文件
当其他项目需要直接引用这个jar时,接下来需要一个安装的步骤,执行mvn clean install,此命令执行了安装任务install:install,将项目输出的jar安装到了Maven本地仓库中,而构件只有被下载到本地仓库后,才能由Maven项目使用.
2.4 使用Archetype生成项目骨架
mvn archetype:generate,当运行插件时,格式为:groupId:artifactId:version:goal,默认使用最新的稳定版
三. 坐标和依赖
3.1 坐标
- Maven的一大功能还是管理项目依赖,为了能自动化解析任何一个Java构件,Maven就必须将它们唯一标识,这就依赖管理的底层基础---坐标
- 在实际生活中,我们可以将地址看作是一种坐标,省,市,区,街道等一系列信息可以唯一标识城市中的任意居住地址,对应在Maven的世界中拥有非常巨大的构件,也就是平常使用的一些jar,war等文件
Maven定义了这样一规则:世界上任意一个构建都可以使用Maven坐标唯一标识,Maven坐标的元素包括
- groupId:定义当前Maven项目隶属公司的实际项目
- artifactId:该元素定义实际项目中的一个Maven模块,推荐做法使用实际项目名称为artifact的前缀,便于寻找实际构建
- version:该元素定义Maven项目当前所处的版本
- package:该元素定义Maven项目的打包方式,默认为jar包
- classifier:该元素用来帮助定义构建输出一些附属构建,附属构件与主构件对应
<groupId>org.sonatype.nexus</groupId> <artifactId>nexus-indexer</artifactId> <version>2.0.0</version> <packaging>jar</packaging> <!--> 1. 以上5个元素,groupId,artifactId,version是必须定义的,packaging是可选的,而classifier是不能直接定义的 2. 项目构件的文件名格式:artifactId-version[-classifier].packaging
3.2 依赖
3.2.1 依赖的配置
项目要引用Maven中的构建,就需要在pom文件中,通过坐标来使用依赖,在根元素project下的dependencies可以包含一个或多个dependency元素,用以声明一个或多个项目依赖
- groupId,artifactId和version:依赖的基本坐标,对于任何一个依赖来说,基本坐标是最重要的
- type:依赖的类型,对应项目坐标定义的packaging,一般不必声明,默认为jar
- scope:依赖的范围
- optional:标记依赖是否可选
- exclusions:用来排除传递性依赖
3.2.2 依赖的范围
- Maven项目在编译项目主代码时需要使用一套classpath,如编译项目主代码时需要使用spring-core,该文件以依赖的方式被引入到classpath中.其次,Maven在编译和执行测试的时候会使用另外一套classpath,依赖同样会引入到相应的classpath中,最后在运行Maven项目时,又会使用一套classpath
依赖范围就是用来控制依赖与三种classpath(编译classpath,测试classpath,运行classpath)的关系
- compile:编译依赖范围,没有指定时,为默认依赖范围.使用此依赖范围的Maven依赖,对于编译,测试运行三种classpath都有效,典型的例子为:spring-core,在编译,测试,运行阶段都需要使用该依赖
- test:测试依赖范围,使用此依赖范围的Maven依赖,只对于测试的classpath有效,在编译主代码或者运行项目时将无法使用此类依赖,典型的例子就是JUnit,它只有在编译器测试代码及运行测试的时候才需要
- provided:已提供依赖范围,使用此依赖范围的Maven依赖,对于编译和测试classpath有效,但在运行时无效,典型的例子就是servlet-api,编译和测试项目的时候需要该依赖,但在运行项目的时候,由于容器已经提供,就不需要Maven重复引入了
- runtime:运行时依赖范围,使用此依赖范围的Maven依赖,对于测试和运行classpath有效,但在编译主代码时无效,典型的例子是JDBC驱动实现,项目主代码的编译只需要JDK提供的JDBC接口,只有在执行测试或运行项目的时候才需要实现上述接口的具体JDBC驱动
system:系统依赖范围,该依赖与三种classpath的关系,和provided依赖范围完全一致,但是,使用system范围的依赖时必须通过systemPath元素显示地指定依赖文件的路径,由于此类依赖不是通过Maven仓库解析的,而且往往与本机系统绑定,可能造成构建的不可移植性
<dependency> <groupId>javax.sql</groupId> <artifactId>jdbc-stdext</artifactId> <version>2.0</version> <scope>system</scope> <systemPath>${java.home}/lib/rt.jar</systemPath> </dependency>
- import:导入依赖范围,该依赖范围不会对三种classpath产生实际的影响,主要用于导入其他pom文件中的dependencyManagement元素对于依赖版本约束的内容
3.2.3 传递性依赖
在使用Maven依赖时,如Spring Framework,此依赖又会依赖其他的开源库,因此实际中往往会下载一个很大的如spring-framework-2.5.6-with-dependencies.zip包,这样往往就引入了很多不必要的依赖,而Maven的传递性依赖机制就可以很好的解决这一问题
- 当A项目引入一个compile范围的B依赖,而B依赖中有一个compile范围的C依赖,那么C依赖同样会成为A的compile范围依赖
传递性依赖和依赖范围
- 假设A依赖于B,B依赖于C,那么A对于B是第一直接依赖,B对于C是第二直接依赖,A对于C是传递性依赖
- 当第二直接依赖是compile的时候,传递性依赖与第一直接依赖范围一致
- 当第二直接依赖是test的时候,依赖不会得以传递
- 当第二直接依赖是provided的时候,只传递第一直接依赖范围为provided的依赖,且传递性依赖范围为provided
- 当第二直接依赖是runtime的时候,传递性地依赖的范围与第一直接依赖的范围一致,但compile例外,此时传递性依赖的范围为runtime
3.2.4 可选依赖
假如有这样一个依赖关系,A依赖于B,B依赖于X和Y,B对于X和Y的依赖都是可选依赖,根据传递性依赖的定义,如果这三个依赖的范围都是compile,那么X,Y就是A的compile范围传递性依赖,但是由于X,Y都是可选依赖,所以依赖不会得以传递,因此X,Y不会对A有任何影响
为什么会有可选依赖这一特性呢?
- 当B实现了两个特性,特性一依赖于X,特性二依赖于Y,并且这两个特性是互斥的,用户不可能同时使用两个特性,比如B是一个持久层隔离工具包,支持多种数据库,在使用这个工具包的时候,只会依赖一种数据库
<dependencies> <dependency> <groupId>mysql</groupId> <artifact>mysql-connector-java</artifact> <version>5.6.0</version> <optional>true</optional> </dependency> <dependency> <groupId>postgresql</groupId> <artifactpostgresql</artifact> <version>8.4-701.jdbc3</version> <optional>true</optional> </dependency> </dependencies>
- 以上使用<optional>元素表示两个依赖为可选依赖,他们只会对当前项目B产生影响,当其他项目依赖于B时,这两个依赖不会被传递,所以当A依赖于B项目时,如果要使用mysql数据库,那么需要显式的声明mysql-connector-java这一依赖
- 关于可选依赖,在理想的情况下,是不应该使用可选依赖的,使用可选依赖的原因是某一个项目中实现了多个特性,而根据单一职责原则,应该针对不同的特性分别创建一个Maven项目,用户根据需要选择使用其中某一个依赖
3.2.5 排除依赖
传递性依赖会给项目隐式的引入很多依赖,这极大的简化了项目依赖的管理,但是有时候这种特性䧥带来问题
例如,当前项目有一个第三方依赖,而这个依赖由于某些原因依赖了另一个类库的SNAPSHOT,那么整个SNAPSHOT就会成为当前项目的传递性依赖,而SNAPSHOT的不稳定性会直接影响到当前的项目,这时候就需要排除掉该SNAPSHOT,并且在当前项目中声明该类库的某个正式发布版
<dependencies> <dependency> <groupId>com.lsy.myproject</groupId> <artifactId>myproject-a</artifactId> <version>1.0.0</version> <exclusions> <exclusion> <groupId>com.lsy.myproject</groupId> <artifactId>myproject-b</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>com.lsy.myproject</groupId> <artifactId>myproject-b</artifactId> <version>1.1.0</version> </dependency> </dependencies>
3.2.6 归类依赖
当一些依赖来自同一个项目的不同模块,这些依赖的版本都应该是相同的,将来升级也是一起升级,如Spring Framework,这时可以使用properties元素定义Maven属性
<properties> <springframework.version>4.2.1</springframework.version> </properties> <dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <verison>${springframework.version}</verison> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-beans</artifactId> <verison>${springframework.version}</verison> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <verison>${springframework.version}</verison> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context-support</artifactId> <verison>${springframework.version}</verison> </dependency> </dependencies>
3.2.7 优化依赖
在软件开发过程中,通常会通过重构等方式不断优化自己代码,同样,对于Maven项目的依赖也需要对其进行优化
- 去除多余的依赖
- 显示声明某些必要的依赖
Maven会自动解析所有项目的直接依赖和间接依赖,并且根据规则判断每个依赖范围,对于一些依赖冲突,也能进行调整,以确保任何一个构建只有唯一的版本在依赖中存在,此称为解析依赖
- mvn dependency:list 查看当前项目的已解析依赖
- mvn dependency:tree 以树结构查看已解析依赖
mvn dependency:analyze 解析依赖
- Used undeclared dependencies:指项目中使用到的,但是没有显示声明的依赖,这种依赖意味着潜在的风险,当前项目直接在使用它们,所以需要显示声明任何项目中直接用到的依赖
- Unused declared dependencies:指项目中未使使用的.但显示声明的依赖
四. 仓库
- 之前已经介绍了Maven的坐标和依赖,坐标和依赖是任何一个构件在Maven世界中的逻辑表示方式,而构建的物理表示方式是文件,Maven通过仓库来同一管理这些文件
-
在Maven世界中,任何一个以依赖,插件或项目构建的输出,都可以成为构件
- 例如:log4j-1.2.15.jar,maven-compiler-plugin-2.0.2.jar或项目打包后的myproject-1.0.0-SNAPSHOT.jar
- 在一台工作站上,可能会有几十个项目,所有项目在/lib目录下都会有自己所需的依赖包,而这些依赖中都有大量的重复,每个项目各自存储自己所需的依赖包不仅造成磁盘空间的浪费,而且也难于同一管理,文件的复制等操作
- 得益于坐标机制,任何Maven项目使用任何一个构建的方式都是相同的,因此,Maven可以在某个位置统一存储所有Maven项目共享的构件,这个统一的位置就是仓库,为了实现重用,项目构建完毕后生成的构建也可以安装或部署到仓库中
4.1 仓库的布局
任何一个构建都有其唯一的坐标,根据这个坐标可以定义其在仓库中的唯一存储路径,这就是Maven的仓库布局方式,如log4j:log4j:1.2.15这一依赖,其对应的仓库路径为log4j/log4j/1.2.15/log4j-1.2.15.jar
- 路径与坐标的关系为:groupId/artifactId/version/artifactId-verision.packaging
4.2 仓库的分类
对于Maven来说,仓库分为两类
- 本地仓库
远程仓库
- 中央仓库:Maven核心自带的仓库服务器,它包含了绝大部分开源的构件
- 私服:另一种特殊的远程仓库,为了节省宽带和时间,应该在局域网内部构建一个私有仓库服务器,用其代理所有外部的远程仓库,内部项目部署到私服上供其它项目使用
- 其他公共库
- 当Maven根据坐标寻找构件时,它首先会查看本地仓库,如果本地仓库存在此构件,则直接使用,如果本地仓库不存在此构件,或者需要查看是否有更新的构件版本,Maven就会去远程仓库查找,发现需要的构件后下载到本地仓库再使用,若本地和远程都没有,则报错
4.3 本地仓库
一般来说,在Maven项目目录下,没有注入lib/这样用来存放依赖文件的目录,当Maven执行编译或测试时,如果需要使用依赖文件,它总是基于坐标使用本地仓库的依赖文件
默认情况,在用户目录下路径名为.m2/repository/的仓库目录,当想自定义本地仓库目录地址时,可以编辑~/.m2/setting.xml,设置localRepository元素来指定仓库地址
<settings> <localRepository>D:/java/repository/</localRepository> </settings>
- 默认情况下,~/.m2/settings.xml文件是不存在的,用户需要从Maven安装目录复制$M2_HOME/conf/settings.xml文件再编辑,建议不要直接修改全局目录的settings.xml,而是在用户目录下进行修改
一个构建只有在本地仓库中,才能由其它Maven项目使用
- 依赖Maven从远程仓库下载到本地仓库中
- 将本地项目的构件安装到Maven本地仓库中 mvn clean install
4.4 远程仓库
安装好Maven后,如果不执行任何Maven命令,本地仓库目录是不存在的,只有输入第一条Maven命令后,Maven才会创建本地仓库,并根据配置和需要,从远程仓库下载至本地仓库
- 这就好比藏书,本地仓库好比书房,远程仓库好比书店,我需要读书时先去书房找,当书房没有时,就去书店买回放到书房,并且一般对每个人来说,书房只有一个,而外面的书店可以有多个
4.4.1 中央仓库
最原始的本地仓库是空的,所以Maven必须知道至少一个可用的远程仓库,才能在执行Maven命令时下载到需要的构建,中央仓库就是这样一个默认的远程仓库,可以通过解压工具打开$M2_HOME/lib/maven-model-builder-3.0.jar中的org/apache/maven/model/pom-4.0.0.xml文件
<repositories> <repository> <id>central</id> <name>Maven Repository Switchboard</name> <url>http://repo1.maven/org/maven2</url> <layout>default</layout> <snapshots> <enable>false</enable> </snapshots> </repository> </repositories>
- 这段配置是所有Maven项目都会继承的超级Pom文件,这段配置使用id central对中央仓库进行唯一标识,其名称为Maven Repository Switchboard,它使用default仓库布局,也就是在之前介绍的仓库布局,最后snapshots元素表示不从该仓库下载快照版
4.4.2 私服
私服是一种特殊的远程仓库,它是架设在局域网内的仓库服务,私服代理广域网上的远程仓库,供局域网内的Maven用户使用,当Maven需要下载构建的时候,它从私服请求,如果私服上不存在该构件,则从外部的远程仓库下载,缓存在私服上之后,再为Maven的下载提供服务
- 此外,一些无法从外部下载的构件也可以从本地上传到私服上供大家使用
私服的好处
- 节省外网带宽:建立私服同样可以减少组织自己的开支,大量的对于外部仓库的重复请求会消耗很大的带宽,利用私服代理外部仓库之后,对外的重复构件下载便可以消除,即降低外网带宽的压力
- 加速Maven构件:不停的连接请求外部仓库是十分耗时的,但Maven的一些内部机制(如快照更新检查井)要求Maven在执行构建时不停的检查远程仓库数据,因此,当项目配置很多外部仓库时,构建速度就会降低
- 部署第三方构件:当某个构件无法从任何一个外部远程仓库获取,建立私服之后,便可以将这些构件部署到这个内部的仓库中,供内部的Maven项目使用
- 提高稳定性,增强控制:Maven构建高度依赖远程仓库,因此,大哥Internet不稳定的时候,Maven构建也会变得不稳定,甚至无法构建,使用私服后,即是短暂时没有Internet连接,由于私服中有大量缓存,Maven依然可以正常运行,并且私服中有很多额外的权限功能控制
- 降低中央仓库的负荷:每天中央仓库都需要面对大量的下载请求,使用私库可以降低对于中央仓库的负荷
远程仓库的配置
很多情况下,默认的中央仓库无法满足项目的需求,可能项目需要的构件存在于另一个远程仓库中,可以通过Pom/settings文件中来配置该仓库
<!--> POM文件 </!--> <project> ..... <repositories> <repository> <id>jboss</id> <name>JBoss Repository</name> <url>http://repository.jboss.com/maven2/</url> <release> <enabled>true</enabled> </release> <snapshots> <enabled>false</enabled> </snapshots> <layout>default</layout> </repository> </repositories> </project> <!--> settings文件 </!--> <profiles> <profile> <id>dev</id> <activation> <activatedByDefault>true</activatedByDefault> </activation> <repositories> <repository> <id>jboss</id> <name>JBoss Repository</name> <url>http://repository.jboss.com/maven2/</url> <release> <enabled>true</enabled> </release> <snapshots> <enabled>false</enabled> </snapshots> <layout>default</layout> </repository> </repositories> </profile> </profiles>
- 在repositories元素下,可以使用repository子元素声明一个或多个远程仓库,并且声明一个id,任何一个仓库声明的id必须是唯一id
- Maven自带的中央仓库使用的id为central,如果其他仓库使用该id,则会覆盖中央仓库的配置,该配置中的url指向了仓库的地址
该配置中的release和snapshots元素用来控制Maven对于发布版构件和快照版构件的下载,对于release和snapshots元素来说除了enabled子元素,还有updatePolicy和checksumPolicy元素
updatePolicy:用来配置Maven从远程仓库检查更新的频率,默认为daily
- daily:每天
- never:从不
- always:每次
- interval :X :每隔X分钟一次
checksumPolicy:配置Maven检查文件失败时的策略,默认为warn
- fail:Maven遇到验证和错误就让构建失败
- warn:Maven遇到验证和错误就发出警告信息
- ignore:Maven遇到验证和错误时完全忽略
<snapshots> <enabled>true</enabled> <updatePolicy>daily</updatePolicy> <checksumPolicy>ignore</checksumPolicy> </snapshots>
远程仓库的验证
- 大部分远程仓库无需认证就可以访问,但出于安全考虑,我们需要提供一些认证信息才能访问一些远程仓库
- 配置认证信息和配置仓库信息不同,仓库信息可以直接配置在POM文件中,但是认证信息必须配置在settings.xml文件中
其中server元素的id必须与POM文件中需要认证的repository元素的id完全一致
<settings> ...... <servers> <server> <id>my-project</id> <username>user</username> <password>password</password> </server> </servers> ....... </settings>
部署至远程仓库
私服一大作用就是部署第三方构件,包括组织内部生成的构件以及无法从外部仓库直接获取的构件,无论是日常开发中生成的构件,还是正式版本发布的构件,都需要部署到仓库中
- distributionManagement包含repository和snapshotRepository子元素,前者表示发布版构件的仓库,后者表示快照版的仓库
往远程仓库部署构件时,往往需要认证,认证配置需在settings.xml中创建一个server元素,其id与仓库的id匹配,无论从远程仓库下载构件,还是部署构件至远程仓库,当需要认证时,配置方式都是一样的
<!--> 在POM文件中配置distributionManagement </!--> <project> ..... <distributionManagement> <repository> <id>jboss-release</id> <name>JBoss Release Repository</name> <url>http://192.168.1.99/content/repository/jboss-release</url> </repository> <snapshotRepository> <id>jboss-snapshots</id> <name>JBoss Snapshots Repository</name> <url>http://192.168.1.99/content/repository/jboss-snapshots</url> </snapshotRepository> </distributionManagement> ..... </project>
4.5 快照
- 在Maven的世界中,任何一个项目或者构件都必须有自己的版本,版本的值可能是1.0.0,1.3-alpha-4,2.0,2.1-SNAPSHOT或者2.1-20191214.221414-13,其中1.0.0,1.3-alpha-4和2.0是稳定的发布版本,而2.1-SNAPSHOT和2.1-20091214.221414-13是不稳定的快照版本
- 对于一个稳定的版本,如果仓库中已经包含,那么Maven就不会再去对照远程仓库进行更新,除非每次执行Maven命令前,清除本地仓库中等待稳定版本,而对于一个正在迭代的项目,如果要实时更新版本的内容就需要频繁的修改新的版本名称,这样是对版本号的滥用
- 针对这种情况,使用快照版时,Maven会自动为构件打上时间戳,因此,Maven就能随时找到仓库中该构建最新版本的文件,一旦有新的更新,就会去同步到本地仓库.当项目经过完善的测试后需要发布的时候,再将快照版本更改为发布版本
- 快照版本只应该在组织内部的项目或模块之间依赖使用,因为这时,组织对这些快照版本的依赖具有完全的理解和控制权,项目不应该依赖任何组织外部的快照版本依赖,由于快照版本的不稳定性,随时可能发生变化,这样的依赖会有潜在的危险
4.6 从仓库解析依赖的机制
当本地仓库没有依赖构件的时候,Maven会自动从远程仓库下载,当依赖版本为快照版本时,Maven会自动找到最新的快照,这背后的依赖机制可以概括如下
- 当依赖的范围时system的时候,Maven直接从本地文件系统解析构件
- 根据依赖坐标计算仓库路径后,尝试从本地仓库寻找构件,如果发现相应的构件,则解析成功
- 在本地仓库不存在相应构件的情况下,如果依赖的版本时显式的发布版本构件,如1.2,2.1-beta-1等,则遍历所有的远程仓库,发现后,下载并解析使用
- 如果依赖的版本时RELEASE或LATEST,则基于更新策略读取所有远程仓库的元数据groupId/artifact/maven-metadata.xml,将其与本地仓库的对应元数据合并后,计算出RELEASE或LATEST真实的值,然后基于这个真实的值检查本地仓库和远程仓库
- 如果依赖的版本是SNAPSHOT,则基于更新策略读取所有远程仓库的元数据groupId/artifactId/maven-metadata.xml,将其与本地仓库的对应数据合并后,得到最新的快照版本的值,然后基于该值检查本地仓库,或者从远程仓库下载
如果最后解析得到的构件版本是时间戳格式,如1.4.1-20191104.121455-8,则复制其时间戳格式的文件至非时间戳格式,如SNAPSHOT,并使用该非时间戳格式的构件
<!--> 基于groupId和artifactId的maven-metadata.xml </!--> <?xml version="1.0" encoding="UTF-8"?> <metadata> <groupId>org.sonatype.nexus</groupId> <artifactId>nexus</artifactId> <versioning> <latest>1.4.2-SNAPSHOT</latest> <release>1.3.7</release> <versions> <version>1.3.5</version> <version>1.3.6</version> <version>1.3.7</version> <version>1.4.0-SNAPSHOT</version> <version>1.4.1-SNAPSHOT</version> <version>1.4.2-SNAPSHOT</version> </versions> <lastUpdated>20191214221133</lastUpdated> </versioning> </metadata>
该XML文件列出了仓库中存在的构件所有可用的版本,同时latest元素指向了这些版本中最新的那个版本1.4.2-SNAPSHOT,而release元素指向了这些版本中最新的发布版本1.3.7,Maven通过合并多个远程仓库及本地仓库的元数据,就能计算出基于所有仓库的latest和release
- 需要注意的是,在依赖声明使用LATEST和RELEASE是不推荐的做法,因为Maven随时可能解析到不同的构件,且Maven不会明确告诉用户这样的变化
4.7 镜像
如果仓库X可以提供仓库Y存储的所有内容,那么就可以认为X是Y的一个镜像,由于地理位置的因素,中央仓库的下载速度会比较慢,这时我们可以配置Maven使用镜像来代替中央仓库,编辑settings.xml
<settings> ...... <mirrors> <mirror> <id>maven.net.cn</id> <name>one of the central mirror in china</name> <url>http://maven.net.cn/content/groups/public/</url> <mirrorOf>central</mirrorOf> </mirror> </mirrors> ...... </settings>
<mirrorOf>的值为central,表示该配置为中央仓库的镜像,任何对于中央仓库的请求都会转至该镜像,用户也可以使用同样的方法配置其他仓库的镜像,另外三个元素id,name,url与一般仓库配置无异,表示该镜像仓库的唯一标识,名称以及地址
- 若该镜像要进行验证,即基于该id配置仓库认证
<settings> ..... <mirrors> <mirror> <id>internal-repository</id> <name>Internal Repository Mananger</name> <url>http://192.168.1.100/maven2/</url> <mirrorOf>*</mirrorOf> </mirror> </mirrors> ..... </settings>
以上mirrorOf元素的值为*,表示该配置是所有Maven仓库的镜像,对于任何远程仓库的请求都会被转至该指定的仓库,如果镜像仓库需要验证,则配置一个id为internal-repository的<server>即可
- <mirrorOf>*<mirrorOf>:匹配所有远程仓库
- <mirrorOf>external:*<mirrorOf>:匹配所有远程仓库,使用localhost的除外,使用file://协议的除外,也就是说,匹配所有不在本机上的远程仓库
- <mirrorOf>repo1,repo2<mirrorOf>:匹配仓库repo1和repo2,用逗号分隔多个仓库
- <mirrorOf>*,! repo1<mirrorOf>:匹配所有的远程仓库,除repo1外,使用感叹号将仓库从匹配中排除
- 需要注意的是,由于镜像仓库完全屏蔽了被镜像仓库,当镜像仓库不稳定或停止服务时,Maven仍将无法访问被镜像仓库,因此无法下载构件
五. 生命周期和插件
5.1 Maven的生命周期
在Maven出现之前,项目构建的生命周期就已经存在,但是不同的公司和开发人员虽然同样在做构件工作,其对于不同的项目却不能够重用,只能重新定制开发,而Maven的生命周期就是为了对所有的构件过程进行抽象和统一
- 这个生命周期包含了项目的清理,初始化,编译,测试,打包,集成测试,验证,部署和站点生成等几乎所有的构件步骤,也就是说几乎所有项目的构件,都能映射到这样一个生命周期上
Maven的生命周期是抽象的,这意味着生命周期本身不做任何实际的工作,在Maven的设计中,实际的任务(如编译主代码)都交由插件完成,这种思想和设计模式的模方法类似,在父类中定义算法的整体结构,子类可以通过实现或重写父类的方法来控制实际的行为
public abstract class Template{ public void build(){ initialize(); compile(); test(); packagee(); integrationTest(); deploy(); } protect abstract void initialize(); protect abstract void compile(); protect abstract void test(); protect abstract void packagee(); protect abstract void integrationTest(); protect abstract void deploy(); }
- 在Maven的生命周期中抽象了各个步骤,定义了他们的次序,但是没有提供具体的实现,而通过插件机制为每个构件步骤绑定一个或多个插件行为,而且Maven为大多数构件步骤都绑定了默认的插件,例如,针对编译的maven-compiler-plguin,针对测试的maven-surefire-plugin等
- Maven定义的生命周期和插件机制一方面保证了所有Maven项目有一致的构件标准,另一方面又通过默认的插件简化和稳定实际项目的构件,此外,该机制还提供了足够的扩展,用户可以通过配置现有的插件或自定义插件来自定义构件行为
三套声明周期
Maven拥有三套相互独立的生命周期,他们分别为clean,default和site,每个生命周期包含一些阶段,这些阶段是有序的,并且后面的阶段依赖于前面的阶段,但是三套声明周期本身是互相独立的
clean生命周期:清理项目
- pre-clean:执行一些清理前需要完成的工作
- clean:清理上次构件生成的文件
- post-clean:执行一些清理后需要完成的工作
default声明周期:定义了真正的构件所需执行的所有步骤
- validate
- initialize
- generate-sources
- process-sources:处理项目主资源文件,一般来说针对/src/main/resources目录的内容进行变量替换等工作后,复制到项目输出的主classpath目录中
- compile:编译项目的主源码,一般来说针对/src/main/java目录下的Java文件至目录输出的主classpath目录中
- process-classes
- generate-test-sources
- process-test-sources:处理项目测试资源文件,一般来说针对/src/test/resources目录的内容进行变量替换工作后,复制到项目输出的测试classpath目录
- test-compile:编译项目的测试代码,一般来说针对/src/test/java目录下的java文件至输出的测试classpath目录中
- test:使用单元测试框架运行测试,测试代码不会被打包或部署
- prepare-package
- package:接收编译好的代码,打包成可发布的格式,如jar
- pre-integration-test
- integration-test
- post-integration-test
- verify
- install:将包安装到Maven本地仓库,供本地其他Maven项目使用
- deploy:将最终的包复制到远程仓库,供其他开发人员和Maven项目使用
site声明周期:建立和发布项目站点,Maven能够基于POM所包含的信息,自动生成一个友好的站点供交流和发布项目信息
- pre-site:执行一些在生成项目站点之前需要完成的工作
- site:生成项目站点文档
- post-site:执行一些在生成项目站点后需要完成的工作
- site-deploy:将生成的项目站点发布到服务器上
命令行与生命周期
从命令行执行Maven任务最主要方式就是调用Maven的生命周期,各个生命周期是相互独立的,而生命周期的阶段是有前后依赖关系的
- mvn 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阶段
5.2 插件
5.2.1 插件目标和绑定
Maven的核心仅仅定义了抽象的生命周期,具体的任务是交由插件完成的,插件以独立的构件形式存在
- 对于插件本身而言,为了能够复用代码,它往往能够完成多个任务,为每个功能编写一个插件显然不合理,因为这些任务背后有大量可复用的代码,因此,这些功能聚集到一个插件里面,每个功能就是一个插件目标
Maven的生命周期和插件相互绑定,用以完成实际的构件任务,具体而言,是生命周期的阶段与插件的目标相互绑定,以完成某个具体的构件任务
- 例如:编译这一任务对应了default生命周期的compile这一阶段,而maven-compiler-plugin这一插件的compile目标能完成此任务
自定义绑定
除了内置绑定以外,用户还能够自己选择将某个插件目标绑定到生命周期的某个阶段上,这种自定义绑定方式能让Maven项目在构件过程中执行更多更丰富的任务
当需要创建项目的源码jar包,maven-source-plugin可以帮我们完成任务,但是内置的插件绑定关系中并没有涉及这一任务,因此需要自行配置,它的jar-no-fork目标能将项目主代码打包成jar文件,可以将其绑定到default生命周期的verify阶段,在执行完集成测试和安装构件之前创建源码jar包
<build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-source-plugin</artifactId> <version>2.1.1</version> <executions> <execution> <id>attach-sources</id> <phase>verify</phase> <goals> <goal>jar-no-fork</goal> </goals> </execution> </executions> </plugin> </plugins> </build>
在POM的build元素下的plugins子元素中声明插件的使用,上例用到了是maven-source-plugin,其groupId为org.apache.maven.plugins,用户总是应该声明一个非快照版本,这样可以避免由于插件版本变化造成的构件不稳定性
- 除了基本的插件坐标声明外,还有插件执行配置,executions下每个execution子元素可以用来配置执行一个任务,该例配置了一个id为attach-sources的任务,通过phase配置将其绑定到verify生命周期阶段上,再通过goals配置指定要执行的插件目标,在运行mvn verify时就会执行该任务
有时候.即是不通过phase元素配置生命周期阶段,插件目标也能绑定到生命周期中去,原因是:有很多插件的目标在编写时就已经定义了默认绑定阶段,可以使用maven-help-plugin查看详细信息
mvn help:describe -Dplugin=org.apache.maven.plugins:maven-source-plugin:2.1 -Ddetail
- Bound to phase : package 默认绑定到package生命周期
- 当插件目标被绑定到不同生命周期阶段的时候,其执行顺序会由生命周期阶段的先后顺序决定,如果多个目标被绑定到同一个阶段,他们的顺序就由插件声明的先后顺序决定
5.2.2 插件配置
用户可以通过配置插件目标的参数,进一步调整插件目标所执行的任务,几乎所有Maven插件的目标都可以配置参数,用户可以通过命令行和POM配置等方式来配置这些参数
命令行插件配置
在日常的Maven使用中,我们通常从命令行输入并执行Maven命令,很多插件目标的参数都支持从命令行配置,用户可以在Maven命令中使用-D参数,并伴随一个参数键=参数值的形式,来配置插件目标的参数
mvn install -Dmaven.test.skip = ture:给maven.surefire-plugin提供一个maventest.skip参数,当参数为true时,就会跳过执行测试
- 参数-D是Java自带的,其功能是通过命令行设置一个Java系统属性,Maven简单地重用了该参数,在准备插件的时候检查系统属性来实现插件参数的配置
POM中插件全局配置
并不是所有的插件参数都适合用命令行配置,有些参数的值从项目创建到项目发布都不会改变,或者说很少改变,这种情况下在POM文件中一次性配置比重复在命令行输入更合理
用户可以在声明插件的时候,对插件进行一个全局的配置,所有基于该插件的目标任务,都会使用这些配置,如:maven-compiler-plugin来编译1.8版本的源文件,生成与JVM1.8兼容的字节码文件
<build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifact>maven-compiler-plugin</artifact> <version>2.1</version> <configuration> <source>1.8</source> <target>1.8</target> </configuration> </plugin> </plugins> </build>
5.2.3 获取插件信息
当遇到一个构建任务时,不仅需要知道去哪里找到合适的插件,还需要详细的了解插件的配置点,由于Maven的插件非常多,其中大部分没有完善的文档,因此,通过帮助命令来了解插件显得非常重要
使用maven-help-plugin描述插件
除了访问在线的插件文档外,可以借助maven-help-plugin来获取插件的详细信息
mvn help:decribe -Dplugin = org.apache.maven.plugins:maven-compiler-plugin:2.1
- 这里执行的是maven-help-plugin的decribe目标,在参数plugin中输入需要描述插件的groupId,artifactId和version,Maven在命令行的输出maven-compiler-plugin的简要信息,包括插件的坐标,目标前缀和目标等信息
在描述插件时,可以省去版本信息,Maven会自动获取最新版本来进行表述,并可以用目标前缀来替换坐标
- mvn help:describe -Dplugin=compiler
如果仅仅描述某个插件目标的信息,则加上goal参数,更详细的信息,则加上detail参数
- mvn help:describe -Dplugin=compiler -Dgoal=compile -Ddetail
5.2.4 插件解析机制
为了方便用户使用和配置插件,Maven不需要用户提供完整的插件坐标信息,就可以解析得到正确的插件
插件仓库
- 与依赖构件一样,插件构件同样基于坐标存储在Maven仓库中,在需要的时候Maven先从本地仓库寻找,如果不存在,则从远程仓库查找,找到插件之后,再下载到本地仓库使用
不同于repositories以及repository子元素,插件的远程仓库使用pluginRepositories和pluginRepository配置
<pluginRepositories> <pluginRepository> <id>central</id> <name>Maven Plugin Resository</name> <url>http://repo1.maven.org/maven2</url> <layout>default</layout> <snapshots> <enabled>false</enabled> </snapshots> <release> <enabled>true</enabled> <updatePolicy>never</updatePolicy> </release> </pluginRepository> </pluginRepositories>
插件默认的groupId
在POM中配置插件时,如果该插件时Maven的官方插件(即groupId为org.apache.maven.plugins),就可以省略groupId,Maven在解析该插件的时候,会自动使用默认groupId不起
<build> <plugins> <plugin> <artifactId>maven-compiler-plugin</artifactId> <version>2.1</version> <configuration> <source>1.8</source> <target>1.8</target> </configuration> </plugin> </plugins> </build>
解析插件版本
同样为了简化插件的配置和使用,在用户没有提供插件版本的情况下,Maven会自动解析插件版本
- 首先Maven在超级POM中为所有核心插件设定了版本,超级POM是所有Maven项目的父POM,所有项目都继承这个超级POM配置,因此即使用户不加任何配置,Maven在使用核心插件时,它的版本就已经确定了
如果用户使用的某个插件没有设定版本,并且这个插件也不属于核心插件,Maven机会去检查所有仓库中可用的版本,通过仓库元数据groupId/artifactId/maven-metadata.xml文件,如maven-compiler-plugin插件为例,它在org/apache/maven/plugins/maven-compiler-plugin/maven-metadata.xml
<?xml version="1.0" encoding="UTF-8"?> <metadata> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <versioning> <latest>2.1</latest> <release>2.1</release> <versions> <version>2.0-beta-1</version> <version>2.1</version> <version>2.2</version> <version>2.3</version> </versions> <lastUpdated>20190102092331</lastUpdated> </versioning> </metadata>
- Maven遍历本地仓库和所有远程仓库,将仓库元数据合并后,就能计算出latest和release的值,maven3将版本解析到最新的非快照版,如2.1
5.2.5 解析插件前缀
Maven命令支持使用插件前缀来简化插件的使用,插件前缀和groupId:artifact是一一对应的,这种匹配关系存储在仓库元数据中,与之前提到的groupId/artifactId/maven-metadata.xml不同,这里仓库元数据为groupId/maven-metadata.xml,一般插件都位于/org/apache/maven/plugins/和org/code-haus/mojo/,可以通过settings.xml配置让Maven检查其他groupId上的插件仓库元数据
<settings> <pluginGroups> <pluginGroup>com.my.plugins</pluginGroup> </pluginGroups> </settings>
在仓库的元数据文件中可以看到插件的前缀定义
<metadata> <plugins> <plugin> <name>Maven Clean Plugin</name> <prefix>clean</prefix> <artifact>maven-clean-plugin</artifact> </plugin> <plugin> <name>Maven Compiler Plugin</name> <prefix>compile</prefix> <artifact>maven-compile-plugin</artifact> </plugin> <plugin> <name>Maven Dependency Plugin</name> <prefix>dependency</prefix> <artifact>maven-dependency-plugin</artifact> </plugin> </plugins> </metadata>
六.聚合与继承
- Maven的集合特性能够把项目各个模块聚合在一起构建,而Maven的继承特性则能帮助抽泣各模块相同的依赖和插件等配置,在简化POM的同时,还能促进各个模块配置的一致性
6.1 集合
aggregator
<modelVersion>4.0.0</modelVersion> <groupId>com.lsy.project</groupId> <artifact>project-aggregator</artifact> <version>1.0.0-SNAPSHOT</version> <packaging>pom</packaging> <name>Aggregator</name> <modules> <module>project-email</module> <module>project-register</module> </modules>
- 对于聚合模块,其打包方式packaging的值必须为pom
通过modules元素来实现聚合,用户可以通过在一个打包方式为pom的Maven项目中声明任意数量的module元素来实现模块的聚合,这里每个module的值都是一个当前POM的相对目录
- 如aggregator的POM路径为.../project-aggregator/pom.xml,那么project-email对应 的目录为.../project-aggregator/project-email/,并且目录中包含了pom.xml,src/main/java,src/test/java等内容,离开了project-aggregator也能独立创建=
聚合模块和其他模块的目录结构并非一定要父子关系,也可以是平行关系
<modules> <module>../prject-email</module> <module>../project-register</module> </modules>
- 当在聚合模块中执行mvn clean install时,Maven首先解析聚合模块的POM,分析要构件的模块,并计算出一个反应堆构件顺序(Reactor Build Order),然后根据这个顺序构件各个模块
聚合模块的构件顺序
- Maven按序读取POM文件,如果POM没有依赖模块,那么就构件模块,否则就先构件其依赖模块
6.2 继承
从以上的聚合模块和其他模块中可以看到,多个被管理模块的POM文件中会有大量重复的相同配置,他们有相同的groupId和version,相同的依赖,相同的插件配置,而通过POM文案的继承可以消除重复
project-parent父模块
<modelVersion>4.0.0</modelVersion> <groupId>com.lsy.project</groupId> <artifactId>project-parent</artifactId> <version>1.0.0-SNAPSHOT</version> <packaging>pom</packaging> <name>Parent</name>
- 父模块的POM文件使用与其他模块一直的groupId和version,它的packaging方式为pom,与聚合模块一致
- 父模块主要是为了消除配置的重复,因此它本身不包含除POM文件之外的项目文件,也就不需要src/main/java之类的文件夹了
project-email子模块
<parent> <groupId>com.lsy.project</groupId> <artifactId>project-parent</artifactId> <version>1.0.0-SNAPSHOT</version> <relativePath>../project-parent/pom.xml</relativePath> </parent> <artifactId>project-email</artifactId> <name>Email</name> <dependencies> ..... </dependencies>
使用parent元素声明父模块,parent下子元素groupId,artifactId,version指定父模块的坐标,元素relativePath表示父模块POM的相对路径,表示Email模块和其父模块是在平行的目录下
- 当构件项目时,Maven会首先根据relativePath检查父POM文件,如果找不到再从本地仓库找,relativePath的默认值为../pom.xml,也就是说,Maven默认父POM在上一层目录
- 子模块没有groupId和version,是因为子模块隐式的从父模块继承了这两个元素,从而消除了不必要的配置
可继承的POM元素
- groupId:项目组ID,项目坐标的核心元素
- version:项目版本,项目坐标的核心元素
- description:项目的描述信息
- organization:项目的组织信息
- inceptionYear:项目的创始年份
- url:项目的URL地址
- develops:项目的开发者信息
- contributors:项目贡献者信息
- distributionManagement:项目的部署配置
- issueManagement:项目的缺陷跟踪系统信息
- ciManagement:项目的持续集成系统信息
- scm:项目的版本控制系统信息
- mailingList:项目的邮件列表信息
- properties:自定义属性
- dependencies:项目的依赖配置
- dependencyManagement:项目的依赖管理配置
- repositories:项目的仓库配置
- pluginRepositories:项目的插件仓库配置
- build:包括项目的源码目录配置,输出目录配置,插件配置,插件管理配置等
- reporting:项目的输出目录配置,报告插件配置等
依赖管理
- dependencies元素是可以被继承的,说明依赖是会被继承的,所以我们可以将子模块共有的依赖配置到父模块中,子模块就可以移除这些依赖,简化配置
上述方法是可行的,但是可能将来会有新的模块并不需要父模块中的一些依赖,这就会产生不合理的现象,从而Maven提供了dependencyManagement元素,既能让子模块继承到父模块的依赖配置,又能保证子模块依赖使用的灵活性
- 在dependencyManagement元素下的依赖声明不会引入实际的依赖,不过它能够约束dependencies下的依赖使用
<modelVersion>4.0.0</modelVersion> <groupId>com.lsy.project</groupId> <artifactId>project-parent</artifactId> <version>1.0.0-SNAPSHOT</version> <packaging>pom</packaging> <name>Parent</name> <properties> <springframework.version>4.3.1</springframework.version> </properties> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version>${springframwork.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-beans</artifactId> <version>${springframwork.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>${springframwork.version}</version> </dependency> </dependencies> </dependencyManagement>
* 这里使用dependencyManagement声明的依赖既不会给parent模块引入依赖,也不会给子模块引入依赖,不过这段配置是会被继承的 * 子模块POM ```xml <parent> <groupId>com.lsy.project</groupId> <artifactId>project-parent</artifactId> <version>1.0.0-SNAPSHOT</version> <relativePath>../project-parent/pom.xml</relativePath> </parent> <artifactId>project-email</artifactId> <name>Email</name> <dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-beans</artifactId> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> </dependency> </dependencies> ``` * 使用这种依赖管理机制,可以在父POM中使用dependencyManagement声明依赖能够统一项目规范中的依赖版本,当版本在父POM中声明之后,子模块使用依赖时就不需要声明了.也不会发生多个子模块使用依赖版本不一致的情况 * scoper元素的import依赖范围只在dependencyManagement元素下才有效果,使用该范围的依赖通常指向一个POM文件,作用是将POM中的dependencyManagement配置导入并合并到当前POM的dependencyManagement元素中 * 所以除了复制配置和继承父模块这两种方式外,还可以通过import范围依赖导入这一配置 ```xml <dependencyManagement> <dependencies> <dependency>com.lsy.project</dependency> <artifactId>project-parent</artifactId> <version>1.0-SNAPSHOT</version> <type>pom</type> <scope>import</scope> </dependencies> </dependencyManagement> ```
插件管理
- Maven提供了dependencyManagement元素帮助管理依赖,类似的,Maven也提供了pluginManagement元素管理插件,该元素中配置的依赖同样不会造成实际的插件调用行为,而POM文件中配置了真正的plugin元素,并且groupId和artifact一致时,才会产生实际的插件行为
父模块
<build> <pluginManagement> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-source-plugin</artifactId> <version>3.1.1</version> <executions> <execution> <id>attach-source</id> <phase>verify</phase> <goals> <goal>jar-no-fork</goal> </goals> </execution> </executions> </plugin> </plugins> </pluginManagement> </build>
子模块
<build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifact>maven-source-plugin</artifact> </plugin> </plugins> </build>
- 子模块使用了maven-source-plugin插件,同时又继承了父模块的pluginManagement配置
集合与继承的关系
多模块的聚合与继承其实是两个概念,其目的是完全不同的,前者为了方便快速的构件项目,后者主要为了消除重复配置
- 对于聚合模块来说:它知道有哪些模块被聚合,但是那些被聚合的模块并不知道这个聚合模块的存在
- 对于继承关系的父POM来说:它不知道有哪些子模块继承于它,但是那些子模块都必须知道自己的父模块是什么
- 在实际项目中,一个POM往往即是聚合模块,又是父模块
约定优于配置
Maven提倡"约定优于配置",Maven只需要一个简单的POM文件,就可以完成清除,构件等任务
- 源码目录:src/main/java/
- 编译输出目录为:target/classes/
- 打包方式为:jar
- 包输出目录为:target/
Maven此机制的来源就是超级POM文件,此文件在$MAVEN_HOME/lib/maven-model-builder-x.x.x.jar中的org/apache/maven/model/pom-4.0.0.xml路径下
<repositories> <repository> <id>central</id> <name>Maven Repository Swithboard</name> <url>http://repo1.maven.org/maven2</url> <layout>default</layout> <snapshots> <enabled>false</enabled> </snapshots> </repository> </repositories> <pluginRepositories> <pluginRepository> <id>central</id> <name>Maven Plugin Repository</name> <url>http://repo1.maven.org/maven2</url> <layout>default</layout> <snapshots> <enabled>false</enabled> </snapshots> <releases> <updatePolicy>never</updatePolicy> </releases> </pluginRepository> </pluginRepositories> <build> <!--> 定义了项目的主输出目录 </!--> <directory>${project.basedir}/target</directory> <!--> 主代码输出目录 </!--> <outputDirectory>${project.build.directory}/classes</outputDirectory> <!--> 最终构件的名称格式 </!--> <finalName>${project.artifactId}-${project.version}</finalName> <!--> 测试代码输出目录 </!--> <testOutputDirectory>${project.build.directory}/test- classes</testOutputDirectory> <!--> 主源码目录 </!--> <sourceDirectory>${project.basedir}/src/main/java</sourceDirectory> <!--> 脚本源码目录 </!--> <scriptSourceDirectory>src/main/scripts</scriptSourceDirectory> <!--> 测试源码目录 </!--> <testSourceDirectory>${project.basedir}/src/test/java</testSourceDirectory> <!--> 主资源目录 </!--> <resources> <resource> <directory>${project.basedir}/src/main/resources</directory> </resource> </resources> <!-- >测试资源目录 </!--> <testResources> <testResource> <directory>${project.basedir}/src/test/resources</directory> </testResource> </testResources> <!--> 核心插件版本 </!--> <pluginManagement> <plugins> <plugin> <artifactId>maven-antrun-plugin</artifactId> <version>1.3</version> </plugin> <plugin> <artifactId>maven-assembly-plugin</artifactId> <version>2.3</version> </plugin> <plugin> <artifactId>maven-clean-plugin</artifactId> <version>2.3</version> </plugin> <plugin> <artifactId>maven-compiler-plugin</artifactId> <version>2.0.3</version> </plugin> ...... </plugins> </pluginManagement> </build>
七. Nexus私服
7.1 Nexus内置的仓库
Nexus仓库有四种类型
- group:仓库组
- hosted:宿主仓库
- proxy:代理仓库
- virtual:虚拟仓库
- Maven可以从宿主仓库下载构件,Maven也可以从代理仓库下载构件,而代理仓库会简介的从远程仓库下载并缓存构件
- 而一般为了方便,我们会从仓库组下载构件,而仓库组没有实际的内容,它只是管理一组实际的仓库,当它接收到请求时,它会转向其包含的宿主仓库或代理仓库获得实际构件的内容
仓库的Policy属性
- Release:发布版
- Snapshot:快照版
7.2 配置Maven从Nexus下载构件
在POM文件中配置仓库和插件仓库------只对当前项目有效
<project> ....... <repositories> <repository> <id>nexus</id> <name>Nexus</name> <url>http://localhost:8081/nexus/content/groupId/public/</url> <snapshots> <enabled>true</enabled> </snapshots> </repository> </repositories> <pluginRepositories> <pluginRepository> <id>nexus</id> <Name>Nexus</Name> <url>http://localhost:8081/nexus/content/groupId/public/</url> <release> <enabled>true</enabled> </release> <snapshots> <enabled>true</enabled> </snapshots> </pluginRepository> </pluginRepositories> ....... </project>
在settings.xml文件中配置---全局有效
- settings.xml文件并不支持直接配置repositories和pluginRepositories,但是可以通过Maven提供的Profile机制,让用户将仓库配置到settings.xml中的Profile中
<settings> .... <profiles> <profile> <id>nexus</id> <repositories> <repository> <id>nexus</id> <name>Nexus</name> <url>http://localhost:8081/nexus/content/groupId/public/</url> <release> <enabled>true</enabled> </release> <snapshots> <enabled>ture</enabled> </snapshots> </repository> </repositories> <pluginRepositories> <pluginRepository> <id>nexus</id> <Name>Nexus</Name> <url>http://localhost:8081/nexus/content/groupId/public/</url> <release> <enabled>true</enabled> </release> <snapshots> <enabled>true</enabled> </snapshots> </pluginRepository> </pluginRepositories> </profile> </profiles> <!--> 激活指定id的profile </!--> <activeProfiles> <activeProfile>nexus</activeProfile> </activeProfiles> </settings>
以上配置已经能让Maven项目从Nexus私服下载构件了,但是Maven仍然会去访问中央仓库,现在我们想要将所有请求都仅仅通过私服,这就需要借助于Maven镜像了
<settings> ..... <mirrors> <mirror> <id>nexus</id> <mirrorOf>*</mirrorOf> <url>http://localhost:8081/nexus/content/groupId/public/</url> </mirror> </mirrors> <profiles> <profile> <id>nexus</id> <repositories> <repository> <id>central</id> <name>central</name> <url>http://central</url> <release> <enabled>true</enabled> </release> <snapshots> <enabled>ture</enabled> </snapshots> </repository> </repositories> <pluginRepositories> <pluginRepository> <id>central</id> <Name>Nexus</Name> <url>http://central</url> <release> <enabled>true</enabled> </release> <snapshots> <enabled>true</enabled> </snapshots> </pluginRepository> </pluginRepositories> </profile> </profiles> <!--> 激活指定id的profile </!--> <activeProfiles> <activeProfile>nexus</activeProfile> </activeProfiles> </settings>
- 这里仓库和插件仓库配置的id都为central,它们将会覆盖POM中央仓库的配置,并且这里的url已经无关紧要,因为所有的请求都会通过镜像访问私服地址
7.3 部署构件至Nexus
如果只是为了代理外部公共仓库,那么Nexus的代理仓库就已经完全足够了,对于另一类Nexus仓库---宿主仓库来说,他们主要作用是存储组织内部的,或一些无法从公共仓库获得的第三方构件,用户可以通过配置Maven自动部署构件至Nexus的宿主仓库
使用Maven部署构件至Nexus
- 日常开发生成的快照版本构件可以直接部署到Nexus中策略为Snapshot的宿主仓库中,项目正式发布的构件则应该部署到Nexus中策略为Release的宿主仓库中
POM文件配置
<project> ..... <distributeManagement> <repository> <id>nexus-releases</id> <name>Nexus Release Repository</name> <url>http://localhost:8081/nexus/content/repositories/ release/</url> </repository> <snapshotRepository> <id>nexus-snapshots</id> <name>Nexus Snapshots Repository</name> <url>http://localhost:8081/nexus/content/repositories/ snapshots</url> </snapshotRepository> </distributeManagement> ..... </project>
Nexus的仓库对于匿名用户只是可读的,为了能够部署构件,还需要在settings.xml中配置认证信息
<settings> .... <servers> <server> <id>nexus-releases</id> <username>admin</username> <password>admin123</password> </server> <server> <id>nexus-snapshots</id> <username>admin</username> <password>admin123</password> </server> </servers> .... </settings>
八. Profile
- 一个优秀的构件系统必须足够灵活,它应该能够让项目在不同的环境下都能够成功的构件,例如:开发环境,测试环境和产品环境,这些环境的数据库配置不尽相同,那么项目构建时就需要能够识别其所在的环境并正确使用配置
8.1 属性
<properties> <springframework.version>4.3.1</springframework.version> </properties> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version>${springframwork.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-beans</artifactId> <version>${springframwork.version}</version> </dependency> </dependencies> </dependencyManagement>
- 这是最常见的Maven属性使用的方式,通过properties元素使得用户自定义一个或多个Maven属性,然后在POM的其他地方使用${属性名称}的方式来引用该属性,这种做法的最大意义是消除重复
Maven的属性有六类
内置属性
- ${basedir}:表示项目根目录,即包含pom.xml文件的目录
- ${version}:表示项目的版本
POM属性:用户可以使用该类属性引用POM文件中对应元素的值
${project.artifactId}:对应<project><artifactId>元素的值
- ${project.build.sourceDirectory}:项目的主源码目录.默认为/src/main/java/
- ${project.build.testSourceDirectory}:项目的测试代码源码目录.默认为/src/test/java/
- ${project.build.directory}:项目构件输出目录.默认为/target
- ${project.build.outputDirectory}:项目主代码编译输出目录.默认为/target/classes
- ${project.build.testOutputDirectory}:项目测试代码编译输出目录.默认为/target/test-classes
- ${project.build.groupId}:项目的groupId
- ${project.build.artifactId}:项目的artifactId
- ${project.build.build.finalName}:项目打包输出文件的名称,默认为\${project.artifactId}
-${project.version}
- 自定义属性:<properties>元素下自定义的Maven属性
- settings属性:与POM属性同理,用户使用settings.来引用,如:${settings.localRepository}指向用户本地仓库的地址
Java系统属性:所有Java系统属性都可以使用Maven属性引用,如${user.home}指向用户目录
- 可以通过mvn help:system查看所有的java系统属性
环境变量属性:所有环境变量都可以用env.来引用,例如:${env.JAVA_HOME}
- 可以通过 mvn help:system查看所有的环境变量
8.2 资源过滤
在不同的开发环境中,项目的源码应该使用不同的方式进行构件,最常见的就是数据库的配置了,在开发中,有些项目会在src/main/resources/目录下放置不同环境下的数据库配置
database.jdbc.driverClass = com.mysql.jdbc.Driver database.jdbc.connectionURL = jdbc:mysql://localhost:3306/dev database.jdbc.username = dev database.jdbc.password = dev-passwd database.jdbc.driverClass = com.mysql.jdbc.Driver database.jdbc.connectionURL = jdbc:mysql://localhost:3306/test database.jdbc.username = test database.jdbc.password = test-passwd
为了应对环境的变化,我们可以使用Maven属性将这些会发生变化的部分提取出来
database.jdbc.driverClass = ${db.driver} database.jdbc.connectionURL = ${db.url} database.jdbc.username = ${db.username} database.jdbc.password = ${db.password}
在settings.xml中通过profile定义不同环境下的配置数据
<profiles> <profile> <id>dev</id> <properties> <db.driver>com.mysql.jdbc.Driver</db.driver> <db.url>jdbc:mysql://localhost:3306/dev</db.url> <db.username>dev</db.username> <db.password>dev-passwd</db.password> </properties> </profile> <profile> <id>test</id> <properties> <db.driver>com.mysql.jdbc.Driver</db.driver> <db.url>jdbc:mysql://localhost:3306/test</db.url> <db.username>test</db.username> <db.password>test-passwd</db.password> </properties> </profile> </profiles>
需要注意的是:
- Maven属性默认只会在POM文件中才会被解析,也就是说${db.username}放到POM文件中会变成dev或test,但是在src/main/resources/目录下仍然还是${db.username},因此,需要让Maven解析资源文件中的Maven属性
- 资源文件处理的是maven-reosurces-plugin做事,它默认的行为只是将项目主资源文件复制到主代码编译输出目录
开启资源过滤
- Maven默认的主资源目录和测试目录的定义是在超级POM文件中
<resources> <resource> <directory>${project.basedir}/src/main/resources</directory> <filtering>true</filtering> </resource> </resources> <testResources> <testResource> <directory>${project.basedir}/src/test/resources</directory> <filtering>true</filtering> </testResource> </testResources>
- 通过mvn clean install -Pdev : 通过mvn 的 -P参数表示在命令行激活一个profile=dev的profile
8.3 Profile
激活Pofile
命令行激活
用户可以使用mvn命令行参数-P 加上profile的id来激活profile,多个id之间逗号分隔
- mvn clean install -Pdev,-Ptest
settings文件显示激活
如果用户希望某个profile默认一直处于激活状态,就可以配置settings.xml文件的activeProfile元素,表示其配置的profile对所有项目都处于激活状态
<settings> ..... <activeProfiles> <activeProfile>dev</activeProfile> </activeProfiles> ..... </settings>
系统属性激活
用户可以配置当前某系统属性存在时,自动激活profile
<profiles> <profile> <activation> <property> <name>test</name> </property> </activation> ..... </profile> </profiles>
用户可以配置当前某系统属性存在且等于a时,自动激活profile
<profiles> <profile> <activation> <property> <name>test</name> <value>a</value> </property> </activation> ..... </profile> </profiles>
用户可以在命令行声明系统属性
- mvn clean install -Dtest=x
操作系统环境变量激活
Profile还可以根据操作系统环境激活
<profiles> <profile> <activation> <os> <name>Windows10</name> </os> </activation> ..... </profile> </profiles>
文件存在与否激活
Maven可以根据项目中某个文件是否存在来决定是否激活profile
<profiles> <profile> <activation> <file> <missing>a.properties</missing> <exists>b.properties</exists> </file> </activation> ..... </profile> </profiles>
默认激活
用户可以在定义profile的时候指定其默认激活
<profiles> <profile> <id>dev</id> <activation> <activeByDefault>true</activeByDefault> </activation> ..... </profile> </profiles>
以上激活优先级从上之下依次减小
- 可以通过mvn help:active-profiles来查看当前激活的profile
- 可以通过mvn help:all-profiles来查看所有的profile
profile的种类
根据具体的需要,可以在一下位置声明profile
- pom.xml:很显然,pom.xml中声明的profile只对当前项目有效
- 用户settings.xml:用户目录下.m2/settings.xml中的profile只对本机该用户所有的Maven项目有效
- 全局settings.xml:Maven安装目录下conf/settings.xml中profile对本机所有Maven项目都有效
POM中profile可使用的元素
<project> <repository></repository> <pluginRepository></pluginRepository> <distributionManagement></distributionManagement> <dependencies></dependencies> <dependencyManagement></dependencyManagement> <modules></modules> <properties></properties> <reporting></reporting> <build> <plugins></plugins> <defaultGoal></defaultGoal> <resources></resources> <testResources></testResources> <finalName></finalName> </build> </project>
* pom.xml:很显然,pom.xml中声明的profile只对当前项目有效 * 用户settings.xml:用户目录下.m2/settings.xml中的profile只对本机该用户所有的Maven项目有效 * 全局settings.xml:Maven安装目录下conf/settings.xml中profile对本机所有Maven项目都有效
- POM中profile可使用的元素
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。