Maven

代码精灵
• 阅读 1761

Maven

一.Maven简介

1.1 何为maven

Maven可翻译为"知识的积累" or"专家",是一款成功的开源跨平台的项目管理工具,无论小型的开源类库项目,还是大型的企业级应用;无论传统的瀑布式开发,还是流行的敏捷模式,Maven都能大显身手.

1.1.1 构建工具

​ 我们一直在不停的寻找避免重复的方法,设计的重复,编码的重复,文档的重复,当然还有构建的重复.Maven最大化的消除了构建的重复,抽象了构建生命周期,并且为绝大部分的构建任务提供了已实现的插件,我们不需要再定义过程,甚至不需要去实现这些过程中的一些任务,只需要遵循Maven中的约定.

​ 同时Maven帮助我们标准化构建过程,以前十个项目可能有十种构建方式,有了Maven后,所有项目的构建命令都是一直且简单的.因此Maven作为一个构建工具:

  1. 可以帮我们自动化构建,
  2. 可以帮我们抽象构建过程
  3. 提供构建任务是实现
  4. 跨平台
  5. 对外提供一直的操作接口

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范围依赖

      Maven

  • 传递性依赖和依赖范围

    • 假设A依赖于B,B依赖于C,那么A对于B是第一直接依赖,B对于C是第二直接依赖,A对于C是传递性依赖
    • 当第二直接依赖是compile的时候,传递性依赖与第一直接依赖范围一致
    • 当第二直接依赖是test的时候,依赖不会得以传递
    • 当第二直接依赖是provided的时候,只传递第一直接依赖范围为provided的依赖,且传递性依赖范围为provided
    • 当第二直接依赖是runtime的时候,传递性地依赖的范围与第一直接依赖的范围一致,但compile例外,此时传递性依赖的范围为runtime

Maven

3.2.4 可选依赖
  • 假如有这样一个依赖关系,A依赖于B,B依赖于X和Y,B对于X和Y的依赖都是可选依赖,根据传递性依赖的定义,如果这三个依赖的范围都是compile,那么X,Y就是A的compile范围传递性依赖,但是由于X,Y都是可选依赖,所以依赖不会得以传递,因此X,Y不会对A有任何影响

    Maven

    • 为什么会有可选依赖这一特性呢?

      • 当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项目使用

    1. 依赖Maven从远程仓库下载到本地仓库中
    2. 将本地项目的构件安装到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&lt;/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会自动找到最新的快照,这背后的依赖机制可以概括如下

    1. 当依赖的范围时system的时候,Maven直接从本地文件系统解析构件
    2. 根据依赖坐标计算仓库路径后,尝试从本地仓库寻找构件,如果发现相应的构件,则解析成功
    3. 在本地仓库不存在相应构件的情况下,如果依赖的版本时显式的发布版本构件,如1.2,2.1-beta-1等,则遍历所有的远程仓库,发现后,下载并解析使用
    4. 如果依赖的版本时RELEASE或LATEST,则基于更新策略读取所有远程仓库的元数据groupId/artifact/maven-metadata.xml,将其与本地仓库的对应元数据合并后,计算出RELEASE或LATEST真实的值,然后基于这个真实的值检查本地仓库和远程仓库
    5. 如果依赖的版本是SNAPSHOT,则基于更新策略读取所有远程仓库的元数据groupId/artifactId/maven-metadata.xml,将其与本地仓库的对应数据合并后,得到最新的快照版本的值,然后基于该值检查本地仓库,或者从远程仓库下载
    6. 如果最后解析得到的构件版本是时间戳格式,如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,每个生命周期包含一些阶段,这些阶段是有序的,并且后面的阶段依赖于前面的阶段,但是三套声明周期本身是互相独立的

      1. clean生命周期:清理项目

        • pre-clean:执行一些清理前需要完成的工作
        • clean:清理上次构件生成的文件
        • post-clean:执行一些清理后需要完成的工作
      2. 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项目使用
      3. 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

Maven

  • 自定义绑定

    • 除了内置绑定以外,用户还能够自己选择将某个插件目标绑定到生命周期的某个阶段上,这种自定义绑定方式能让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的插件非常多,其中大部分没有完善的文档,因此,通过帮助命令来了解插件显得非常重要

    1. 使用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&lt;/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&lt;/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&lt;/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仓库有四种类型

    1. group:仓库组
    2. hosted:宿主仓库
    3. proxy:代理仓库
    4. virtual:虚拟仓库

Maven


*   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&lt;/url>
    <release>
    <enabled>true</enabled>
    </release>
    <snapshots>
    <enabled>ture</enabled>
    </snapshots>
    </repository>
    </repositories>

    <pluginRepositories>
    <pluginRepository>
    <id>central</id>
    <Name>Nexus</Name>
    <url>http://central&lt;/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的属性有六类

    1. 内置属性

      1. ${basedir}:表示项目根目录,即包含pom.xml文件的目录
      2. ${version}:表示项目的版本
    2. 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}

    3. 自定义属性:<properties>元素下自定义的Maven属性
    4. settings属性:与POM属性同理,用户使用settings.来引用,如:${settings.localRepository}指向用户本地仓库的地址
    5. Java系统属性:所有Java系统属性都可以使用Maven属性引用,如${user.home}指向用户目录

      • 可以通过mvn help:system查看所有的java系统属性
    6. 环境变量属性:所有环境变量都可以用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

    1. 命令行激活

      • 用户可以使用mvn命令行参数-P 加上profile的id来激活profile,多个id之间逗号分隔

        • mvn clean install -Pdev,-Ptest
    2. settings文件显示激活

      • 如果用户希望某个profile默认一直处于激活状态,就可以配置settings.xml文件的activeProfile元素,表示其配置的profile对所有项目都处于激活状态

        <settings>
        .....
        <activeProfiles>
        <activeProfile>dev</activeProfile>
        </activeProfiles>
        .....
        </settings>

    3. 系统属性激活

      • 用户可以配置当前某系统属性存在时,自动激活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
    4. 操作系统环境变量激活

      • Profile还可以根据操作系统环境激活

        <profiles>
        <profile>
        <activation>
        <os>
        <name>Windows10</name>
        </os>
        </activation>
        .....
        </profile>
        </profiles>

    5. 文件存在与否激活

      • Maven可以根据项目中某个文件是否存在来决定是否激活profile

        <profiles>
        <profile>
        <activation>
        <file>
        <missing>a.properties</missing>
        <exists>b.properties</exists>
        </file>
        </activation>
        .....
        </profile>
        </profiles>

    6. 默认激活

      • 用户可以在定义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. 可以帮我们自动化构建,
  2. 可以帮我们抽象构建过程
  3. 提供构建任务是实现
  4. 跨平台
  5. 对外提供一直的操作接口

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范围依赖

      1590662084736

  • 传递性依赖和依赖范围

    • 假设A依赖于B,B依赖于C,那么A对于B是第一直接依赖,B对于C是第二直接依赖,A对于C是传递性依赖
    • 当第二直接依赖是compile的时候,传递性依赖与第一直接依赖范围一致
    • 当第二直接依赖是test的时候,依赖不会得以传递
    • 当第二直接依赖是provided的时候,只传递第一直接依赖范围为provided的依赖,且传递性依赖范围为provided
    • 当第二直接依赖是runtime的时候,传递性地依赖的范围与第一直接依赖的范围一致,但compile例外,此时传递性依赖的范围为runtime

1590662084736

3.2.4 可选依赖
  • 假如有这样一个依赖关系,A依赖于B,B依赖于X和Y,B对于X和Y的依赖都是可选依赖,根据传递性依赖的定义,如果这三个依赖的范围都是compile,那么X,Y就是A的compile范围传递性依赖,但是由于X,Y都是可选依赖,所以依赖不会得以传递,因此X,Y不会对A有任何影响

    1590662084736

    • 为什么会有可选依赖这一特性呢?

      • 当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项目使用

    1. 依赖Maven从远程仓库下载到本地仓库中
    2. 将本地项目的构件安装到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会自动找到最新的快照,这背后的依赖机制可以概括如下

    1. 当依赖的范围时system的时候,Maven直接从本地文件系统解析构件
    2. 根据依赖坐标计算仓库路径后,尝试从本地仓库寻找构件,如果发现相应的构件,则解析成功
    3. 在本地仓库不存在相应构件的情况下,如果依赖的版本时显式的发布版本构件,如1.2,2.1-beta-1等,则遍历所有的远程仓库,发现后,下载并解析使用
    4. 如果依赖的版本时RELEASE或LATEST,则基于更新策略读取所有远程仓库的元数据groupId/artifact/maven-metadata.xml,将其与本地仓库的对应元数据合并后,计算出RELEASE或LATEST真实的值,然后基于这个真实的值检查本地仓库和远程仓库
    5. 如果依赖的版本是SNAPSHOT,则基于更新策略读取所有远程仓库的元数据groupId/artifactId/maven-metadata.xml,将其与本地仓库的对应数据合并后,得到最新的快照版本的值,然后基于该值检查本地仓库,或者从远程仓库下载
    6. 如果最后解析得到的构件版本是时间戳格式,如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,每个生命周期包含一些阶段,这些阶段是有序的,并且后面的阶段依赖于前面的阶段,但是三套声明周期本身是互相独立的

      1. clean生命周期:清理项目

        • pre-clean:执行一些清理前需要完成的工作
        • clean:清理上次构件生成的文件
        • post-clean:执行一些清理后需要完成的工作
      2. 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项目使用
      3. 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目标能完成此任务

1590662084736

1590662084736

  • 自定义绑定

    • 除了内置绑定以外,用户还能够自己选择将某个插件目标绑定到生命周期的某个阶段上,这种自定义绑定方式能让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的插件非常多,其中大部分没有完善的文档,因此,通过帮助命令来了解插件显得非常重要

    1. 使用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仓库有四种类型

    1. group:仓库组
    2. hosted:宿主仓库
    3. proxy:代理仓库
    4. virtual:虚拟仓库

1590846416265

    • 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的属性有六类

      1. 内置属性

        1. ${basedir}:表示项目根目录,即包含pom.xml文件的目录
        2. ${version}:表示项目的版本
      2. 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}

            -&dollar;{project.version}

      3. 自定义属性:<properties>元素下自定义的Maven属性
      4. settings属性:与POM属性同理,用户使用settings.来引用,如:&dollar;{settings.localRepository}指向用户本地仓库的地址
      5. Java系统属性:所有Java系统属性都可以使用Maven属性引用,如&dollar;{user.home}指向用户目录

        • 可以通过mvn help:system查看所有的java系统属性
      6. 环境变量属性:所有环境变量都可以用env.来引用,例如:&dollar;{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文件中才会被解析,也就是说&dollar;{db.username}放到POM文件中会变成dev或test,但是在src/main/resources/目录下仍然还是&dollar;{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

      1. 命令行激活

        • 用户可以使用mvn命令行参数-P 加上profile的id来激活profile,多个id之间逗号分隔

          • mvn clean install -Pdev,-Ptest
      2. settings文件显示激活

        • 如果用户希望某个profile默认一直处于激活状态,就可以配置settings.xml文件的activeProfile元素,表示其配置的profile对所有项目都处于激活状态

          <settings>
              .....
              <activeProfiles>
                  <activeProfile>dev</activeProfile>
              </activeProfiles>
              .....
          </settings>
      3. 系统属性激活

        • 用户可以配置当前某系统属性存在时,自动激活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
      4. 操作系统环境变量激活

        • Profile还可以根据操作系统环境激活

          <profiles>
              <profile>
                  <activation>
                      <os>
                          <name>Windows10</name>
                      </os>
                  </activation>
                  .....
              </profile>
          </profiles>
      5. 文件存在与否激活

        • Maven可以根据项目中某个文件是否存在来决定是否激活profile

          <profiles>
              <profile>
                  <activation>
                      <file>
                          <missing>a.properties</missing>
                          <exists>b.properties</exists>
                      </file>
                  </activation>
                  .....
              </profile>
          </profiles>
      6. 默认激活

        • 用户可以在定义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可使用的元素
    点赞
    收藏
    评论区
    推荐文章
    推荐学java 推荐学java
    3年前
    推荐学java——Maven初识
    Maven介绍Maven是Apache下开源的Java项目管理工具,对软件项目提供构建与依赖管理。其为Java项目提供了统一的管理方式,已成为业界标准。Maven下载安装官网下载地址:https://maven.apache.org/推荐下载压缩包版本,然后解压到本地不带中文路径的目录下,我这里解压在了D盘的根目录下:D:\ProgramF
    kenx kenx
    3年前
    Maven 基础标签之版本管理和冲突解决
    前言我们在做java项目的时候由于jar包太多,我们就需要使用maven做项目管理,管理项目的jar包依赖,包括打包上线maven基础Maven是一个项目管理工具,主要用于项目构建,依赖管理,项目信息管理每个maven项目根目录都会有一个pom.xml文件,负责项目构建,依赖管理在这个文件里面,你只需要添加相应的jar包坐标配置,maven就会自动
    Stella981 Stella981
    3年前
    Maven 国内镜像(Maven下载慢的解决方法)
      Maven是当前流行的项目管理工具,但官方的库在国外经常连不上,连上也下载速度很慢。国内oschina的maven服务器很早之前就关了。今天发现阿里云的一个中央仓库,亲测可用。1<mirror2<idalimaven</id3<mirrorOfcentral</mirrorOf4
    Stella981 Stella981
    3年前
    CentOS 7 安装 Gradle
           Java生态体系中有三大构建工具:Ant、Maven和Gradle。其中,Ant是由Apache软件基金会维护;Maven这个单词来自于意第绪语(犹太语),意为知识的积累,最初在JakataTurbine项目中用来简化构建过程;Gradle是一个基于ApacheAnt和ApacheMaven概念的项目自动化构建开源工具,它使用一种基于G
    Stella981 Stella981
    3年前
    Maven Wrapper简介
    MavenWrapper简介简介开发java项目少不了要用到maven或者gradle,对比gradle而言,可能maven要更加常用一些。要使用maven那就必要要安装maven,如果有些用户不想安装maven怎么办?或者说用户不想全局安装maven,那么可以使用项目级别的MavenWrapper来实现这个功能。如果大家使用Intel
    Wesley13 Wesley13
    3年前
    Maven学习总结(七)——eclipse中使用Maven创建Web项目
    Maven学习总结(七)——eclipse中使用Maven创建Web项目一、创建Web项目1.1 选择建立Maven Project  选择File  New Project,如下图所示:在New窗口中选择
    Stella981 Stella981
    3年前
    Jenkins+Gradle+Docker打docker镜像包上传至s3
    gradle打包跟maven打包的环境搭建有相似之处,可参考maven打包https://www.cnblogs.com/chenchentester/p/6408815.html进入Jenkins,系统管理》插件管理,下载所需插件GitPlugin项目目录!(https://oscimg.oschina.net/oscnet/e0f
    Wesley13 Wesley13
    3年前
    01.Flink笔记
    Flink开发环境部署配置Flink是一个以Java及Scala作为开发语言的开源大数据项目,代码开源在github上,并使用maven来编译和构建项目。所需工具:Java、maven、Git。本次操作是在windows环境下。一、工具安装Java配置(略)maven配置1.
    Stella981 Stella981
    3年前
    Maven使用 国内镜像配置
    Maven使用国内镜像配置  Maven  setting.xml中配置<repositories<repository<idnexus</id<namelocalprivatenexus</name
    Stella981 Stella981
    3年前
    Maven总结
    何为maven?1.Maven主要是基于Java平台的项目构建,依赖管理和项目信息2.Maven是优秀的构建工具,跨平台,消除构建的重复,抽象了一个完整的构建生命周期模型,标准化构建过程3.管理分布的项目信息,版本控制系统,轻松获取项目文档,测试报告,静态分析报告,版本日志报告等4.极限编程(XP)
    代码精灵
    代码精灵
    Lv1
    愿山野浓雾都有路灯,风雨漂泊都有归舟。
    文章
    8
    粉丝
    0
    获赞
    0