Maven

头像
NTFA
    阅读 87 分钟

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

        image.png

    • 传递性依赖和依赖范围

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

    image.png

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

      image.png

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

        • 当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目标能完成此任务

    image.png

    image.png

    • 自定义绑定

      • 除了内置绑定以外,用户还能够自己选择将某个插件目标绑定到生命周期的某个阶段上,这种自定义绑定方式能让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:虚拟仓库

    image.png

    
    *   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可使用的元素

    NTFA
    24 声望3 粉丝

    自我约束,刻意练习