Maven知识点汇总

Maven简介

  1. 跨平台的项目管理工具(无论是在Windows上,还是在Linux或者Mac上,都可以使用同样的命令);
  2. 主要服务于基于Java平台的项目构建、依赖管理和项目信息管理;

构建

  1. 编译、运行单元测试、生成文档、打包和部署等;
  2. Maven是一个强大的构建工具,能够帮我们自动化构建过程,从清理、编译、测试到生成报告,再到打包和部署;
  3. Maven能帮助我们标准化构建过程;

依赖管理

  1. Maven通过一个坐标系统准确地定位每一个构件(artifact),也就是通过一组坐标Maven能够找到任何一个Java类库(如jar文件);
  2. Maven给这个类库世界引入了经纬,让它们变得有秩序,于是我们可以借助它来有序地管理依赖,轻松地解决那些繁杂的依赖问题;

项目信息管理

  1. Maven还能帮助我们管理原本分散在项目中各个角落的项目信息,包括项目描述、开发者列表、版本控制系统地址、许可证、缺陷管理系统地址等;
  2. 除了直接的项目信息,通过Maven自动生成的站点,以及一些已有的插件,我们还能够轻松获得项目文档、测试报告、静态分析报告、源码版本日志报告等非常具有价值的项目信息;

中央仓库

  1. Maven为全世界的Java开发者提供了一个免费的中央仓库,在其中几乎可以找到任何的流行开源类库;
  2. 通过一些Maven的衍生工具(如Nexus),我们还能对其进行快速地搜索;
  3. 只要定位了坐标,Maven就能够帮我们自动下载,省去了手工劳动;

约定优于配置

Maven对于项目目录结构、测试用例命名方式等内容都有既定的规则,只要遵循了这些成熟的规则,用户在项目间切换的时候就免去了额外的学习成本。

安装Maven

检查JDK安装

打开Windows的命令行,运行如下的命令来检查Java安装:

echo %JAVA_HOME%
java -version

上述命令首先检查环境变量JAVA_HOME是否指向了正确的JDK目录,接着尝试运行java命令。如果Windows无法执行java命令,或者无法找到JAVA_HOME环境变量,就需要检查Java是否安装了,或者环境变量是否设置正确。

安装Maven

  1. 下载Maven安装文件;
  2. 解压安装文件到指定目录,如D:\bin\apache-maven-3.0;
  3. 设置环境变量,将Maven安装配置到操作系统环境中:打开系统属性面板(在桌面上右击“我的电脑”→“属性”),单击高级系统设置,再单击环境变量,在系统变量中新建一个变量,变量名为M2_HOME,变量值为Maven的安装目录D:\bin\apache-maven-3.0。单击“确定”按钮,接着在系统变量中找到一个名为Path的变量,在变量值的末尾加上%M2_HOME%\bin;。注意:多个值之间需要有分号隔开,然后单击“确定”按钮。至此,环境变量设置完成;

值得注意的是Path环境变量。当我们在cmd中输入命令时,Windows首先会在当前目录中寻找可执行文件或脚本,如果没有找到,Windows会接着遍历环境变量Path中定义的路径。由于将%M2_HOME%\bin添加到了Path中,而这里%M2_HOME%实际上是引用了前面定义的另一个变量,其值是Maven的安装目录。因此,Windows会在执行命令时搜索目录D:\bin\apache-maven-3.0\bin,而mvn执行脚本的位置就是这里。

  1. 检查Maven的安装情况:打开一个新的cmd窗口,运行以下命令:
echo %M2_HOME%
mvn -v

echo %M2_HOME%用来检查环境变量M2_HOME是否指向了正确的Maven安装目录;mvn -v执行了第一条Maven命令,以检查Windows是否能够找到正确的mvn执行脚本。

  1. 升级Maven:下载新的Maven安装文件,解压至本地目录,然后更新M2_HOME环境变量。

M2_HOME

M2_HOME环境变量指向Maven的安装目录,该目录的结构和内容:

  • bin
  • boot
  • conf
  • lib
  • LICENSE.txt
  • NOTICE.txt
  • README.txt
  1. bin:包含了mvn运行的脚本,这些脚本用来配置Java命令,准备好classpath和相关的Java系统属性,然后执行Java命令。其中mvn是基于UNIX平台的shell脚本,mvn.bat是基于Windows平台的bat脚本。在命令行输入任何一条mvn命令时,实际上就是在调用这些脚本。
  2. boot:该目录只包含一个文件,以maven 3.0为例,该文件为plexus-classworlds-2.2.3.jar。plexus-classworlds是一个类加载器框架,相对于默认的java类加载器,它提供了更丰富的语法以方便配置,Maven使用该框架加载自己的类库。
  3. conf:包含了一个非常重要的文件settings.xml。直接修改该文件,就能在机器上全局地定制Maven的行为。一般情况下,我们更偏向于复制该文件至~/.m2/目录下(~表示用户目录),然后修改该文件,在用户范围定制Maven的行为;
  4. lib:该目录包含了所有Maven运行时需要的Java类库,Maven本身是分模块开发的,因此用户能看到诸如maven-core-3.0.jar、maven-model-3.0.jar之类的文件。此外,这里还包含一些Maven用到的第三方依赖,如common-cli-1.2.jar、google-collection-1.0.jar等;
  5. 其他:LICENSE.txt记录了Maven使用的软件许可证Apache License Version 2.0;NOTICE.txt记录了Maven包含的第三方软件;而README.txt则包含了Maven的简要介绍,包括安装需求及如何安装的简要指令等。

~/.m2

输入mvn help:system,在执行该命令的过程中,会生成~/.m2文件夹(~ 代表操作系统的当前用户目录),也就是本地仓库,并且会从Maven官网下载必要的依赖包到本地仓库。

  1. ~表示用户目录;
  2. 该文件夹下放置了Maven本地仓库.m2/repository,所有的Maven构件都被存储到该仓库中,以方便重用;
  3. Maven根据一套规则来确定任何一个构件在仓库中的位置;
  4. 默认情况下,~/.m2目录下除了repository仓库之外就没有其他目录和文件了,不过大多数Maven用户需要复制M2_HOME/conf/settings.xml文件到~/.m2/settings.xml。这是一条最佳实践;

Maven安装最佳实践

1 设置MAVEN_OPTS环境变量

  1. 运行mvn命令实际上是执行了Java命令,既然是运行Java,那么运行Java命令可用的参数当然也应该在运行mvn命令时可用;
  2. 通常需要设置MAVEN_OPTS的值为-Xms128m-Xmx512m,因为Java默认的最大可用内存往往不能够满足Maven运行的需要,比如在项目较大时,使用Maven生成项目站点需要占用大量的内存,如果没有该配置,则很容易得到java.lang.OutOfMemeoryError。因此,一开始就配置该变量是推荐的做法;
  3. 在Windows下创建一个名为MAVEN_OPTS的环境变量,设置其值为-Xms128m-Xmx512m即可。尽量不要直接修改mvn.bat或者mvn这两个Maven执行脚本文件。因为如果修改了脚本文件,升级Maven时就不得不再次修改,一来麻烦,二来容易忘记。同理,应该尽可能地不去修改任何Maven安装目录下的文件;

2 配置用户范围settings.xml

  1. Maven用户可以选择配置$M2_HOME/conf/settings.xml或者~/.m2/settings.xml。前者是全局范围的,整台机器上的所有用户都会直接受到该配置的影响,而后者是用户范围的,只有当前用户才会受到该配置的影响;
  2. 推荐使用用户范围的settings.xml,主要是为了避免无意识地影响到系统中的其他用户。如果有切实的需求,需要统一系统中所有用户的settings.xml配置,当然应该使用全局范围的settings.xml;
  3. 除了影响范围这一因素,配置用户范围settings.xml文件还便于Maven升级。直接修改conf目录下的settings.xml会导致Maven升级不便,每次升级到新版本的Maven,都需要复制settings.xml文件。如果使用~/.m2目录下的settings.xml,就不会影响到Maven安装文件,升级时就不需要触动settings.xml文件;

3 不要使用IDE内嵌的Maven

当集成Maven时,IDE往往会安装一个内嵌的Maven,这个内嵌的Maven通常会比较新,但不一定很稳定,而且往往也会和在命令行使用的Maven不是同一个版本。

编写POM

  1. Maven项目的核心是pom.xml;
  2. POM(Project Object Model,项目对象模型)定义了项目的基本信息,用于描述项目如何构建,声明项目依赖,等等;
  3. groupId、artifactId和version这三个元素定义了一个项目基本的坐标,在Maven的世界,任何的jar、pom或者war都是以基于这些基本的坐标进行区分的;
  4. groupId定义了项目属于哪个组,这个组往往和项目所在的组织或公司存在关联。如果公司是mycom,有一个项目为myapp,那么groupId就应该是com.mycom.myapp;
  5. artifactId定义了当前Maven项目在组中唯一的ID,你可能会为不同的子项目(模块)分配artifactId,如myapp-util、myapp-domain、myapp-web等;
  6. version指定了项目当前的版本。SNAPSHOT意为快照,说明该项目还处于开发中,是不稳定的版本。随着项目的发展,version会不断更新,如升级为1.0、1.1-SNAPSHOT、1.1、2.0等;
  7. name元素声明了一个对于用户更为友好的项目名称,虽然这不是必须的,但还是推荐为每个POM声明name,以方便信息交流;
  8. 没有任何实际的Java代码,就能够定义一个Maven项目的POM,这体现了Maven的一大优点,它能让项目对象模型最大程度地与实际代码相独立,即解耦。这在很大程度上避免了Java代码和POM代码的相互影响。比如当项目需要升级版本时,只需要修改POM,而不需要更改Java代码;而在POM稳定之后,日常的Java代码开发工作基本不涉及POM的修改。

编写主代码

  1. 项目主代码和测试代码不同,项目的主代码会被打包到最终的构件中(如jar),而测试代码只在运行测试时用到,不会被打包;主代码目录是src/main/java,测试代码目录是src/test/java;
  2. 主代码Java类的包名与之前在POM中定义的groupId和artifactId相吻合;
  3. 使用Maven进行编译:在项目根目录下运行命令mvn clean compile。clean告诉Maven清理输出目录target/,compile告诉Maven编译项目主代码至target/classes目录;

编写测试代码

  1. 测试代码目录是src/test/java;
  2. 在Java世界中,JUnit是事实上的单元测试标准。要使用JUnit,首先需要为Hello World项目添加一个JUnit依赖;
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.7/version>
<scope>test</scope>
</dependency>
  1. scope为依赖范围,若依赖范围为test则表示该依赖只对测试有效。换句话说,测试代码中的import JUnit代码是没有问题的,但是如果在主代码中用import JUnit代码,就会造成编译错误。如果不声明依赖范围,那么默认值就是compile,表示该依赖对主代码和测试代码都有效。
  2. 需要执行的测试方法都应该以@Test进行标注;
  3. 运行mvn clean test,但Maven实际执行的可不止这两个任务,还有clean:clean、resources:resources、compiler:compile、resources:testResources以及compiler:testCompile;

配置maven-compiler-plugin

由于历史原因,Maven的核心插件之一——compiler插件默认maven2支持编译Java 1.3、maven3支持Java1.5,因此需要配置该插件使其支持更高版本的Java。

打包和运行

mvn clean package
  1. Maven会在打包之前执行编译、测试等操作;
  2. 输出台看到的jar:jar任务负责打包,实际上就是jar插件的jar目标将项目主代码打包成一个名为artifact-version.jar的文件。该文件位于target/输出目录中;
  3. 至此,我们得到了项目的输出,如果有需要的话,就可以复制这个jar文件到其他项目的classpath中从而使用它的功能;

如何才能让其他的Maven项目直接引用这个jar呢?还需要一个安装的步骤,执行命令:

mvn clean install
  1. 从控制台输出可以看到:在打包之后,又执行了安装任务install:install;
  2. 该任务将项目输出的jar安装到了Maven本地仓库中,可以打开相应的文件夹看到项目的pom和jar;
  3. 这样其它项目就可以直接引用这个项目的jar包了;

compile-test-package-install

Maven最主要的几个命令:mvn clean compile、mvn clean test、mvn clean package、mvn clean install。执行test之前会先执行compile,执行package之前会先执行test,执行install之前会执行package。

Maven坐标

  1. 任何一个构件都可以使用Maven坐标唯一标识,Maven坐标的元素包括groupId、artifactId、version、packaging、classifier。只要我们提供正确的坐标元素,Maven就能找到对应的构件;
  2. “Maven是从哪里下载构件的呢?”Maven内置了一个中央仓库的地址(http://repo1.maven.org/maven2),该中央仓库包含了世界上大部分流行的开源项目构件,Maven会在需要的时候去那里下载;
  3. 在我们开发自己项目的时候,也需要为其定义适当的坐标,这是Maven强制要求的。在这个基础上,其他Maven项目才能引用该项目生成的构件;
    4.
  4. groupId:1 定义当前Maven项目隶属的实际项目;2 Maven项目和实际项目不一定是一对一的关系。比如SpringFramework这一实际项目,其对应的Maven项目会有很多,如spring-core、spring-context等。这是由于Maven中模块的概念,因此,一个实际项目往往会被划分成很多模块;3 groupId不应该对应项目隶属的组织或公司,因为一个组织下会有很多实际项目,如果groupId只定义到组织级别,而后面我们会看到,artifactId只能定义Maven项目(模块),那么实际项目这个层将难以定义;4 groupId的表示方式与Java包名的表示方式类似,通常与域名反向一一对应。上例中,groupId为org.sonatype.nexus,org.sonatype表示Sonatype公司建立的一个非盈利性组织,nexus表示Nexus这一实际项目,该groupId与域名nexus.sonatype.org对应。
  5. artifactId:1 定义实际项目中的一个Maven项目(模块);2 推荐的做法是使用实际项目名称作为artifactId的前缀。比如上例中的artifactId是nexus-indexer,使用了实际项目名nexus作为前缀,这样做的好处是方便寻找实际构件。在默认情况下,Maven生成的构件,其文件名会以artifactId作为开头,如nexus-indexer-2.0.0.jar,使用实际项目名称作为前缀之后,就能方便从一个lib文件夹中找到某个项目的一组构件。考虑有5个项目,每个项目都有一个core模块,如果没有前缀,我们会看到很多core-1.2.jar这样的文件,加上实际项目名前缀之后,便能很容易区分foo-core-1.2.jar、bar-core-1.2.jar……
  6. version:定义Maven项目当前所处的版本,如上例中nexus-indexer的版本是2.0.0。
  7. packaging:1 定义Maven项目的打包方式。2 打包方式通常与所生成构件的文件扩展名对应,如上例中packaging为jar,最终的文件名为nexus-indexer-2.0.0.jar,而使用war打包方式的Maven项目,最终生成的构件会有一个.war文件;2 打包方式会影响到构建的生命周期,比如jar打包和war打包会使用不同的命令;3 当不定义packaging的时候,Maven会使用默认值jar。
  8. classifier:1 用来帮助定义构建输出的一些附属构件;2 附属构件与主构件对应,如上例中的主构件是nexus-indexer-2.0.0.jar,该项目可能还会通过使用一些插件生成如nexus-indexer-2.0.0-javadoc.jar、nexus-indexer-2.0.0-sources.jar这样一些附属构件,其包含了Java文档和源代码。这时候,javadoc和sources就是这两个附属构件的classifier。这样,附属构件也就拥有了自己唯一的坐标。注意,不能直接定义项目的classifier,因为附属构件不是项目直接默认生成的,而是由附加的插件帮助生成。
  9. groupId、artifactId、version是必须定义的,packaging是可选的(默认为jar),而classifier是不能直接定义的。
  10. 项目构件的文件名是与坐标相对应的,一般的规则为artifactId-version[-classifier].packaging,[-classifier]表示可选。另外,packaging并非一定与构件扩展名对应,比如packaging为maven-plugin的构件扩展名为jar。

依赖配置

  1. groupId、artifactId和version:依赖的基本坐标;
  2. type:依赖的类型,对应于项目坐标定义的packaging。大部分情况下,该元素不必声明,其默认值为jar;
  3. scope:依赖范围;
  4. optional:标记依赖是否可选;
  5. exclusions:用来排除传递性依赖;
  6. 大部分依赖声明只包含基本坐标,然而在一些特殊情况下,其他元素至关重要;

依赖范围

  • 依赖范围就是用来控制依赖与三种classpath(编译classpath、测试classpath、运行classpath)的关系。
  • compile:编译依赖范围。如果没有指定,就会默认使用该依赖范围。使用此依赖范围的Maven依赖,对于编译、测试、运行三种classpath都有效。典型的例子是spring-core,在编译、测试和运行的时候都需要使用该依赖。
  • test:测试依赖范围。使用此依赖范围的Maven依赖,只对于测试classpath有效,在编译主代码或者运行项目的使用时将无法使用此类依赖。典型的例子是JUnit,它只有在编译测试代码及运行测试的时候才需要。
  • provided:已提供依赖范围。使用此依赖范围的Maven依赖,对于编译和测试classpath有效,但在运行时无效。典型的例子是servlet-api,编译和测试项目的时候需要该依赖,但在运行项目的时候,由于容器已经提供,就不需要Maven重复地引入一遍。
  • runtime:运行时依赖范围。使用此依赖范围的Maven依赖,对于测试和运行class-path有效,但在编译主代码时无效。典型的例子是JDBC驱动实现,项目主代码的编译只需要JDK提供的JDBC接口,只有在执行测试或者运行项目的时候才需要实现上述接口的具体JDBC驱动。
  • system:系统依赖范围。该依赖与三种classpath的关系,和provided依赖范围完全一致。但是,使用system范围的依赖时必须通过systemPath元素显式地指定依赖文件的路径。由于此类依赖不是通过Maven仓库解析的,而且往往与本机系统绑定,可能造成构建的不可移植,因此应该谨慎使用。
    在这里插入图片描述

传递性依赖

  • 如果一个依赖又依赖于其他类库,一种做法是项目下载本依赖的jar包以及它依赖的其他jar包,这样就引入了很多不必要的依赖,另一种做法是只下载本依赖的jar包,到实际使用时再根据出错信息加入需要的其他依赖。显然,这些做法不够理想。
  • Maven的传递性依赖机制可以很好地解决这一问题。例如account-mail有一个compile范围的spring-core依赖,spring-core有一个compile范围的commons-logging依赖,那么commons-logging就会成为account-email的compile范围依赖,commons-logging是account-email的一个传递性依赖。
    在这里插入图片描述
  • 有了传递性依赖机制,在使用Spring Framework的时候就不用去考虑它依赖了什么,也不用担心引入多余的依赖。Maven会解析各个直接依赖的POM,将那些必要的间接依赖,以传递性依赖的形式引入到当前的项目中。

传递性依赖和依赖范围

假设A依赖于B,B依赖于C,我们说A对于B是第一直接依赖,B对于C是第二直接依赖,A对于C是传递性依赖。第一直接依赖的范围和第二直接依赖的范围决定了传递性依赖的范围。

如表所示,最左边一列表示第一直接依赖范围,最上面一行表示第二直接依赖范围,中间的交叉单元格则表示传递性依赖范围。

在这里插入图片描述
当第二直接依赖的范围是compile的时候,传递性依赖的范围与第一直接依赖的范围一致;当第二直接依赖的范围是test的时候,依赖不会得以传递;当第二直接依赖的范围是provided的时候,只传递第一直接依赖范围也为provided的依赖,且传递性依赖的范围同样为provided;当第二直接依赖的范围是runtime的时候,传递性依赖的范围与第一直接依赖的范围一致,但compile例外,此时传递性依赖的范围为runtime。

依赖调解

  1. 假如项目A有这样的依赖关系:A->B->C->X(1.0)、A->D->X(2.0),X是A的传递性依赖,但是两条依赖路径上有两个版本的X,那么哪个X会被Maven解析使用呢?两个版本都被解析显然是不对的,因为那会造成依赖重复,因此必须选择一个。Maven依赖调解的第一原则是:路径最近者优先。该例中X(1.0)的路径长度为3,而X(2.0)的路径长度为2,因此X(2.0)会被解析使用;
  2. 比如这样的依赖关系:A->B->Y(1.0)、A->C->Y(2.0),Y(1.0)和Y(2.0)的依赖路径长度是一样的,都为2。那么到底谁会被解析使用呢?在Maven 2.0.8及之前的版本中,这是不确定的,但是从Maven 2.0.9开始,为了尽可能避免构建的不确定性,Maven定义了依赖调解的第二原则:第一声明者优先。在依赖路径长度相等的前提下,在POM中依赖声明的顺序决定了谁会被解析使用,顺序最靠前的那个依赖优胜。该例中,如果B的依赖声明在C之前,那么Y(1.0)就会被解析使用;

可选依赖

项目A依赖于项目B,项目B依赖于项目X和Y,B对于X和Y的依赖都是可选依赖:A->B、B->X(可选)、B->Y(可选)。根据传递性依赖的定义,如果所有这三个依赖的范围都是compile,那么X、Y就是A的compile范围传递性依赖。然而,由于这里X、Y是可选依赖,依赖将不会得以传递。换句话说,X、Y将不会对A有任何影响,如图所示:
在这里插入图片描述
为什么要使用可选依赖这一特性呢?可能项目B实现了两个特性,其中的特性一依赖于X,特性二依赖于Y,而且这两个特性是互斥的,用户不可能同时使用两个特性。比如B是一个持久层隔离工具包,它支持多种数据库,包括MySQL、PostgreSQL等,在构建这个工具包的时候,需要这两种数据库的驱动程序,但在使用这个工具包的时候,只会依赖一种数据库。

使用<optional>元素表示mysql-connector-java和postgresql这两个依赖为可选依赖,它们只会对当前项目B产生影响,当其他项目依赖于B的时候,这两个依赖不会被传递。因此,当项目A依赖于项目B的时候,如果其实际使用基于MySQL数据库,那么在项目A中就需要显式地声明mysql-connector-java这一依赖。

在理想的情况下不应该使用可选依赖。使用可选依赖的原因是某一个项目实现了多个特性,在面向对象设计中,有个单一职责性原则,意指一个类应该只有一项职责,而不是糅合太多的功能。这个原则在规划Maven项目的时候也同样适用。在上面的例子中,更好的做法是为MySQL和PostgreSQL分别创建一个Maven项目,基于同样的groupId分配不同的artifactId,如com.juvenxu.mvnbook:project-b-mysql和com.juvenxu.mvnbook:project-b-postgresql,在各自的POM中声明对应的JDBC驱动依赖,而且不使用可选依赖,用户则根据需要选择使用project-b-mysql或者project-b-postgresql。由于传递性依赖的作用,就不用再声明JDBC驱动依赖。

最佳实践

排除依赖

例如,当前项目有一个第三方依赖,而这个第三方依赖由于某些原因依赖了另外一个类库的SNAPSHOT版本,那么这个SNAPSHOT就会成为当前项目的传递性依赖,而SNAPSHOT的不稳定性会直接影响到当前的项目。这时就需要排除掉该SNAPSHOT,并且在当前项目中声明该类库的某个正式发布的版本。还有一些情况,你可能也想要替换某个传递性依赖,比如Sun JTA API,Hibernate依赖于这个JAR,但是由于版权的因素,该类库不在中央仓库中,而Apache Geronimo项目有一个对应的实现。这时你就可以排除SunJAT API,再声明Geronimo的JTA API实现。

<project>
<modelVersion>4.0.0/modelVersion>
<groupId>com.juvenxu.mvnbook</groupId>
<artifactId>project-a</artifactId>
<version>1.0.0/version>
<dependencies>
<dependency>
<groupId>com.juvenxu.mvnbook</groupId>
<artifactId>project-b</artifactId>
<version>1.0.0/version>
<exclusions>
<exclusion>
<groupId>com.juvenxu.mvnbook</groupId>
<artifactId>project-c</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.juvenxu.mvnbook</groupId>
<artifactId>project-c</artifactId>
<version>1.1.0/version>
</dependency>
</dependencies>
</project>

上述代码中,项目A依赖于项目B,但是由于一些原因,不想引入传递性依赖C,而是自己显式地声明对于项目C 1.1.0版本的依赖。代码中使用exclusions元素声明排除依赖,exclusions可以包含一个或者多个exclusion子元素,因此可以排除一个或者多个传递性依赖。需要注意的是,声明exclusion的时候只需要groupId和artifactId,而不需要version元素,这是因为只需要groupId和artifactId就能唯一定位依赖图中的某个依赖。
在这里插入图片描述

归类依赖

有些依赖的版本都是相同的,而且可以预见,如果将来需要升级,这些依赖的版本会一起升级。

使用properties元素定义Maven属性,例如并定义一个version子元素,其值为2.5.6。有了这个属性定义之后,Maven运行的时候会将POM中的所有的${version}替换成实际值2.5.6。

优化依赖

1 查看当前项目的已解析依赖

mvn dependency:list

2 查看当前项目的已解析依赖树

mvn dependency:tree

进一步查看已解析依赖的信息。当这些依赖经Maven解析后,就会构成一个依赖树,通过这棵依赖树就能很清楚地看到某个依赖是通过哪条传递路径引入的。

+-”符号表示该包后面还有其它依赖包,
“\-”表示该包后面不再依赖其它jar包

3 分析当前项目的已解析依赖

mvn dependency:analyze
  • 首先是Used undeclared dependencies,意指项目中使用到的,但是没有显式声明的依赖。这种依赖意味着潜在的风险,当前项目直接在使用它们,例如有很多相关的Java import声明,而这种依赖是通过直接依赖传递进来的,当升级直接依赖的时候,相关传递性依赖的版本也可能发生变化,这种变化不易察觉,但是有可能导致当前项目出错。
  • 其次是Unused declared dependencies,意指项目中未使用的,但显式声明的依赖,这里有spring-core和spring-beans。需要注意的是,对于这样一类依赖,我们不应该简单地直接删除其声明,而是应该仔细分析。由于dependency:analyze只会分析编译主代码和测试代码需要用到的依赖,一些执行测试和运行时需要的依赖它就发现不了。对于这样一类依赖,我们不应该简单地直接删除其声明,而是应该仔细分析。有时候确实能通过该信息找到一些没用的依赖,但一定要小心测试。

仓库

  1. 坐标和依赖是任何一个构件在Maven世界中的逻辑表示方式,而构件的物理表示方式是文件,Maven通过仓库来统一管理这些文件。
  2. 任何Maven项目使用任何一个构件的方式都是完全相同的,Maven可以在某个位置统一存储所有Maven项目共享的构件,这个统一的位置就是仓库。
  3. 实际的Maven项目将不再各自存储其依赖文件,它们只需要声明这些依赖的坐标,在需要的时候(例如,编译项目的时候需要将依赖加入到classpath中),Maven会自动根据坐标找到仓库中的构件,并使用它们;
  4. 为了实现重用,项目构建完毕后生成的构件也可以安装或者部署到仓库中,供其他项目使用。
  5. 任何一个构件都有其唯一的坐标,根据这个坐标可以定义其在仓库中的唯一存储路径,这便是Maven的仓库布局方式。该路径与坐标的大致对应关系为groupId/artifactId/version/artifactId-version.packaging;
  6. 当Maven根据坐标寻找构件的时候,它首先会查看本地仓库,如果本地仓库存在此构件,则直接使用;如果本地仓库不存在此构件,或者需要查看是否有更新的构件版本,Maven就会去远程仓库查找,发现需要的构件之后,下载到本地仓库再使用。如果本地仓库和远程仓库都没有需要的构件,Maven就会报错;
  7. 中央仓库是Maven核心自带的远程仓库,它包含了绝大部分开源的构件。在默认配置下,当本地仓库没有Maven需要的构件的时候,它就会尝试从中央仓库下载;
  8. 私服是另一种特殊的远程仓库,为了节省带宽和时间,应该在局域网内架设一个私有的仓库服务器,用其代理所有外部的远程仓库。内部的项目还能部署到私服上供其他项目使用;
  9. 除了中央仓库和私服,还有很多其他公开的远程仓库,常见的有Java.net Maven库(http://download.java.net/maven/2/)和JBoss Maven库(http://repository.jboss.com/maven2/)等;
    在这里插入图片描述

自定义本地仓库地址

  1. 编辑文件~/.m2/settings.xml,设置localRepository元素的值为想要的仓库地址。例如:
<settings>
<localRepository>D:\maven\repository</localRepository>
</settings>
  1. 打开IDEA-Settings-Maven
    在这里插入图片描述

将项目安装到本地仓库

在项目中执行mvn clean install命令。

私服

私服是一种特殊的远程仓库,它是架设在局域网内的仓库服务,私服代理广域网上的远程仓库,供局域网内的Maven用户使用。当Maven需要下载构件的时候,它从私服请求,如果私服上不存在该构件,则从外部的远程仓库下载,缓存在私服上之后,再为Maven的下载请求提供服务。此外,一些无法从外部仓库下载到的构件也能从本地上传到私服上供大家使用,如图所示。

在这里插入图片描述
即使在一台直接连入Internet的个人机器上使用Maven,也应该在本地建立私服:

  1. 节省自己的外网带宽;
  2. 加速Maven构建;
  3. 部署第三方构件。当某个构件无法从任何一个外部远程仓库获得,建立私服之后,便可以将这些构件部署到这个内部的仓库中,供内部的Maven项目使用;
  4. 提高稳定性,增强控制。Maven构建高度依赖于远程仓库,因此,当Internet不稳定的时候,Maven构建也会变得不稳定,甚至无法构建。使用私服后,即使暂时没有Internet连接,由于私服中已经缓存了大量构件,Maven也仍然可以正常运行。此外,一些私服软件(如Nexus)还提供了很多额外的功能,如权限管理、RELEASE/SNAPSHOT区分等,管理员可以对仓库进行一些更高级的控制;
  5. 降低中央仓库的负荷;

远程仓库的配置

  1. 在repositories元素下,可以使用repository子元素声明一个或者多个远程仓库;
  2. 任何一个仓库声明的id必须是唯一的,Maven自带的中央仓库使用的id为central,如果其他的仓库声明也使用该id,就会覆盖中央仓库的配置;
  3. url值指向仓库的地址;
  4. releases的enabled值决定是否支持仓库发布版本下载,snapshots的enabled值决定是否支持仓库快照版本的下载;
  5. layout元素值default表示仓库的布局是Maven 2及Maven 3的默认布局,而不是Maven 1的布局;

远程仓库的认证

大部分远程仓库无须认证就可以访问,但有时候出于安全方面的考虑,我们需要提供认证信息才能访问一些远程仓库。例如,组织内部有一个Maven仓库服务器,该服务器为每个项目都提供独立的Maven仓库,为了防止非法的仓库访问,管理员为每个仓库提供了一组用户名及密码。这时,为了能让Maven访问仓库内容,就需要配置认证信息。

配置认证信息和配置仓库信息不同,仓库信息可以直接配置在POM文件中,但是认证信息必须配置在settings.xml文件中。这是因为POM往往是被提交到代码仓库中供所有成员访问的,而settings.xml一般只放在本机。因此,在settings.xml中配置认证信息更为安全。

<settings>
……
<servers>
<server>
<id>my-proj</id>
<username>repo-user</username>
<password>repo-pwd</password>
</server>
</servers>
……
</settings>

部署至远程仓库

私服的一大作用是部署第三方构件,包括组织内部生成的构件以及一些无法从外部仓库直接获取的构件。无论是日常开发中生成的构件,还是正式版本发布的构件,都需要部署到仓库中,供其他团队成员使用。

  1. 编辑项目的pom.xml文件,配置distributionManagement元素;
  2. distributionManagement包含repository和snapshotRepository子元素,前者表示发布版本构件的仓库,后者表示快照版本的仓库。这两个元素下都需要配置id、name和url,id为该远程仓库的唯一标识,name是为了方便人阅读,关键的url表示该仓库的地址;
  3. 往远程仓库部署构件的时候,往往需要认证;
  4. 配置正确后,在命令行运行mvn clean deploy,Maven就会将项目构建输出的构件部署到配置对应的远程仓库;

快照版本

在Maven的世界中,任何一个项目或者构件都必须有自己的版本。版本的值可能是1.0.0、1.3-alpha-4、2.0、2.1-SNAPSHOT或者2.1-20091214.221414-13。其中,1.0.0、1.3-alpha-4和2.0是稳定的发布版本,而2.1-SNAPSHOT和2.1-20091214.221414-13是不稳定的快照版本。

为什么要用快照版?

  1. 小张在开发模块A的2.1版本,该版本还未正式发布,与模块A一同开发的还有模块B,它由小张的同事小刘开发,B的功能依赖于A。在开发的过程中,小张需要经常将自己最新的构建输出,交给小刘,供她开发和集成调试;
  2. 方案一:让小刘自己签出模块A的源码进行构建。这种方法能够确保小刘得到模块A的最新构件,不过她不得不去构建模块A。多了一些版本控制和Maven操作还不算,当构建A失败的时候,她会是一头雾水,最后不得不找小张解决。显然,这种方式是低效的;
  3. 方案二:重复部署模块A的2.1版本供小刘下载。虽然小张能够保证仓库中的构件是最新的,但对于Maven来说,同样的版本和同样的坐标就意味着同样的构件。因此,如果小刘在本机的本地仓库包含了模块A的2.1版本构件,Maven就不会再对照远程仓库进行更新。除非她每次执行Maven命令之前,清除本地仓库,但这种要求手工干预的做法显然也是不可取的;
  4. 方案三:不停更新版本2.1.1、2.1.2、2.1.3……。首先,小张和小刘两人都需要频繁地更改POM,如果有更多的模块依赖于模块A,就会涉及更多的POM更改;其次,大量的版本其实仅仅包含了微小的差异,有时候是对版本号的滥用;
  5. Maven的快照版本机制就是为了解决上述问题。小张只需要将模块A的版本设定为2.1-SNAPSHOT,然后发布到私服中,在发布的过程中,Maven会自动为构件打上时间戳。比如2.1-20091214.221414-13就表示2009年12月14日22点14分14秒的第13次快照。有了该时间戳,Maven就能随时找到仓库中该构件2.1-SNAPSHOT版本最新的文件。这时,小刘配置对于模块A的2.1-SNAPSHOT版本的依赖,当她构建模块B的时候,Maven会自动从仓库中检查模块A的2.1-SNAPSHOT的最新构件,当发现有更新时便进行下载。默认情况下,Maven每天检查一次更新(由仓库配置的updatePolicy控制),用户也可以使用命令行-U参数强制让Maven检查更新,如mvn clean install-U;
  6. 当项目经过完善的测试后需要发布的时候,就应该将快照版本更改为发布版本。例如,将2.1-SNAPSHOT更改为2.1,表示该版本已经稳定,且只对应了唯一的构件;
  7. 快照版本只应该在组织内部的项目或模块间依赖使用,因为这时,组织对于这些快照版本的依赖具有完全的理解及控制权。项目不应该依赖于任何组织外部的快照版本依赖,由于快照版本的不稳定性,这样的依赖会造成潜在的危险;

镜像

  1. 如果仓库X可以提供仓库Y存储的所有内容,那么就可以认为X是Y的一个镜像;
  2. <mirrorOf>的值为central,表示该配置为中央仓库的镜像,任何对于中央仓库的请求都会转至该镜像,用户也可以使用同样的方法配置其他仓库的镜像;
  3. 由于私服可以代理任何外部的公共仓库(包括中央仓库),因此,对于组织内部的Maven用户来说,使用一个私服地址就等于使用了所有需要的外部仓库,这可以将配置集中到私服,从而简化Maven本身的配置。在这种情况下,任何需要的构件都可以从私服获得,私服就是所有仓库的镜像;
  4. <mirrorOf>*</mirrorOf>:匹配所有远程仓库
  5. <mirrorOf>external:*</mirrorOf>:匹配所有远程仓库,使用localhost的除外,使用file://协议的除外。也就是说,匹配所有不在本机上的远程仓库;
  6. <mirrorOf>repo1,repo2</mirrorOf>:匹配仓库repo1和repo2,使用逗号分隔多个远程仓库;
  7. <mirrorOf>*,!repo1</mirrorOf>:匹配所有远程仓库,repo1除外,使用感叹号将仓库从匹配中排除。

生命周期和插件

  1. 在有关Maven的日常使用中,命令行的输入往往就对应了生命周期,如mvn package就表示执行默认生命周期阶段package;

  2. Maven的生命周期是抽象的,其实际行为都由插件来完成,如package阶段的任务可能就会由maven-jar-plugin完成;
    在这里插入图片描述

  3. Maven的生命周期就是为了对所有的构建过程进行抽象和统一。Maven从大量项目和构建工具中学习和反思,然后总结了一套高度完善的、易扩展的生命周期。这个生命周期包含了项目的清理、初始化、编译、测试、打包、集成测试、验证、部署和站点生成等几乎所有构建步骤。也就是说,几乎所有项目的构建,都能映射到这样一个生命周期上;

  4. Maven定义的生命周期和插件机制一方面保证了所有Maven项目有一致的构建标准,另一方面又通过默认插件简化和稳定了实际项目的构建。此外,该机制还提供了足够的扩展空间,用户可以通过配置现有插件或者自行编写插件来自定义构建行为;

  5. Maven拥有三套相互独立的生命周期,它们分别为clean、default和site。clean生命周期的目的是清理项目,default生命周期的目的是构建项目,而site生命周期的目的是建立项目站点;

  6. 每个生命周期包含一些阶段(phase),这些阶段是有顺序的,并且后面的阶段依赖于前面的阶段,用户和Maven最直接的交互方式就是调用这些生命周期阶段。以clean生命周期为例,它包含的阶段有pre-clean、clean和post-clean。当用户调用pre-clean的时候,只有pre-clean阶段得以执行;当用户调用clean的时候,pre-clean和clean阶段会得以顺序执行;当用户调用post-clean的时候,pre-clean、clean和post-clean会得以顺序执行;

  7. 较之于生命周期阶段的前后依赖关系,三套生命周期本身是相互独立的,用户可以仅仅调用clean生命周期的某个阶段,或者仅仅调用default生命周期的某个阶段,而不会对其他生命周期产生任何影响;

clean生命周期

clean生命周期的目的是清理项目,它包含三个阶段:

  1. pre-clean执行一些清理前需要完成的工作;
  2. clean清理上一次构建生成的文件;
  3. post-clean执行一些清理后需要完成的工作;

default生命周期

default生命周期定义了真正构建时所需要执行的所有步骤,它是所有生命周期中最核心的部分,其包含的阶段如下:

  1. validate:用于验证项目的有效性和其项目所需要的内容是否具备;
  2. initialize:初始化操作,比如创建一些构建所需要的目录等;
  3. generate-sources:生成源代码,这些源代码在compile阶段需要使用到;
  4. process-sources:对源代码进行一些操作,比如过滤一些源代码;
  5. generate-resources:生成资源文件(这些文件将被包含在最后的输入文件中);
  6. process-resources:复制主资源文件至主输出目录;
  7. compile:编译主代码至主输出目录;
  8. process-classes:对编译生成的文件进行处理;
  9. generate-test-sources:生成测试用的源代码;
  10. process-test-sources:对生成的测试源代码进行处理 ;
  11. generate-test-resources:生成测试用的资源文件;
  12. process-test-resources:复制测试资源文件至测试输出目录;
  13. test-compile:编译测试代码至测试输出目录;
  14. process-test-classes:对测试源代码编译后的文件进行处理;
  15. test:执行测试用例;
  16. prepare-package:打包前置操作;
  17. package:接受编译好的代码,打包成可发布的格式,如JAR;
  18. pre-integration-test:集成测试前置操作;
  19. integration-test:集成测试;
  20. post-integration-test:集成测试后置操作;
  21. install:将打好的包安装到本地仓库;
  22. deploy:将打好的包部署到远程仓库;

site生命周期

site生命周期的目的是建立和发布项目站点,Maven能够基于POM所包含的信息,自动生成一个友好的站点,方便团队交流和发布项目信息。该生命周期包含如下阶段:

  1. pre-site执行一些在生成项目站点之前需要完成的工作;
  2. site生成项目站点文档;
  3. post-site执行一些在生成项目站点之后需要完成的工作;
  4. site-deploy将生成的项目站点发布到服务器上;

命令行与生命周期

从命令行执行Maven任务的最主要方式就是调用Maven的生命周期阶段。需要注意的是,各个生命周期是相互独立的,而一个生命周期的阶段是有前后依赖关系的。例如:

  • mvn clean:该命令调用clean生命周期的clean阶段。实际执行的阶段为clean生命周期的pre-clean和clean阶段;
  • mvn test:该命令调用default生命周期的test阶段。实际执行的阶段为default生命周期的validate、initialize等,直到test的所有阶段;
  • mvn clean install:该命令调用clean生命周期的clean阶段和default生命周期的install阶段。实际执行的阶段为clean生命周期的pre-clean、clean阶段,以及default生命周期的从validate至install的所有阶段。该命令结合了两个生命周期,在执行真正的项目构建之前清理项目是一个很好的实践;
  • mvn clean deploy site-deploy:该命令调用clean生命周期的clean阶段、default生命周期的deploy阶段,以及site生命周期的site-deploy阶段。实际执行的阶段为clean生命周期的pre-clean、clean阶段,default生命周期的所有阶段,以及site生命周期的所有阶段;

插件目标

  • 对于插件本身,为了能够复用代码,它往往能够完成多个任务。例如maven-dependency-plugin,它能够基于项目依赖做很多事情。它能够分析项目依赖,帮助找出潜在的无用依赖;它能够列出项目的依赖树,帮助分析依赖来源;它能够列出项目所有已解析的依赖,等等;
  • 这些功能聚集在一个插件里,每个功能就是一个插件目标;
  • maven-dependency-plugin有十多个目标,每个目标对应了一个功能,上述提到的几个功能分别对应的插件目标为dependency:analyze、dependency:tree和dependency:list;

插件绑定

Maven的生命周期与插件相互绑定,用以完成实际的构建任务。具体而言,是生命周期的阶段与插件的目标相互绑定,以完成某个具体的构建任务。

例如项目编译这一任务,它对应了default生命周期的compile这一阶段,而maven-compiler-plugin这一插件的compile目标能够完成该任务。因此,将它们绑定,就能实现项目编译的目的,如图所示:
在这里插入图片描述

插件仓库

与依赖构件一样,插件构件同样基于坐标存储在Maven仓库中。在需要的时候,Maven会从本地仓库寻找插件,如果不存在,则从远程插件仓库查找。找到插件之后,再下载到本地仓库使用。

插件的远程仓库使用pluginRepositories和pluginRepository配置。

聚合与继承

聚合

  1. 打包方式packaging的值为pom;

  2. 通过在一个打包方式为pom的Maven项目中声明任意数量的module元素来实现模块的聚合;

  3. 为了方便用户构建项目,通常将聚合模块放在项目目录的最顶层,其他模块则作为聚合模块的子目录存在,这样当用户得到源码的时候,第一眼发现的就是聚合模块的POM,不用从多个模块中去寻找聚合模块来构建整个项目;
    在这里插入图片描述
    在这里插入图片描述
    这里每个module的值都是一个当前POM的相对目录。

  4. 聚合模块与其他模块的目录结构并非一定要是父子关系。下图展示了另一种平行的目录结构。
    在这里插入图片描述

如果使用平行目录结构,聚合模块的POM也需要做相应的修改,以指向正确的模块目录:

在这里插入图片描述

  1. 运行mvn clean install命令:Maven会首先解析聚合模块的POM、分析要构建的模块、并计算出一个反应堆构建顺序(Reactor Build Order),然后根据这个顺序依次构建各个模块。反应堆是所有模块组成的一个构建结构;

继承

  1. 在面向对象世界中,程序员可以使用类继承在一定程度上消除重复,在Maven的世界中,也有类似的机制能让我们抽取出重复的配置,这就是POM的继承;
  2. 作为父模块的POM,其打包类型必须为pom;
  3. Maven提供的dependencyManagement元素既能让子模块继承到父模块的依赖配置,又能保证子模块依赖使用的灵活性。在dependencyManagement元素下的依赖声明不会引入实际的依赖,不过它能够约束dependencies下的依赖使用;
  4. 使用dependencyManagement声明的依赖既不会给account-parent引入依赖,也不会给它的子模块引入依赖,不过这段配置是会被继承的;
  5. 当项目中的多个模块有同样的插件配置时,应当将配置移到父POM的pluginManagement元素中。即使各个模块对于同一插件的具体配置不尽相同,也应当使用父POM的pluginManagement元素统一声明插件的版本。甚至可以要求将所有用到的插件的版本在父POM的pluginManagement元素中声明,子模块使用插件时不配置版本信息,这么做可以统一项目的插件版本,避免潜在的插件不一致或者不稳定问题,也更易于维护;

聚合与继承的关系

  1. 聚合主要是为了方便快速构建项目,继承主要是为了消除重复配置;
  2. 对于聚合模块来说,它知道有哪些被聚合的模块,但那些被聚合的模块不知道这个聚合模块的存在。对于继承关系的父POM来说,它不知道有哪些子模块继承于它,但那些子模块都必须知道自己的父POM是什么;
  3. 聚合POM与继承关系中的父POM的packaging都必须是pom,同时,聚合模块与继承关系中的父模块除了POM之外都没有实际的内容;
    在这里插入图片描述
  4. 可能一个POM既是聚合POM,又是父POM:既包含modules元素,用来聚合子模块;还包含properties、dependencyManagement和pluginManagement元素供子模块继承;

反应堆

  1. 在一个多模块的Maven项目中,反应堆(Reactor)是指所有模块组成的一个构建结构。对于单模块的项目,反应堆就是该模块本身,但对于多模块项目来说,反应堆就包含了各模块之间继承与依赖的关系,从而能够自动计算出合理的模块构建顺序;
  2. Maven按序读取POM,如果该POM没有依赖模块,那么就构建该模块,否则就先构建其依赖模块,如果该依赖还依赖于其他模块,则进一步先构建依赖的依赖。
  3. 裁剪反应堆:一般来说,用户会选择构建整个项目或者选择构建单个模块,但有些时候,用户会想要仅仅构建完整反应堆中的某些个模块。换句话说,用户需要实时地裁剪反应堆:

-am(also-make):同时构建所列模块的依赖模块;
-amd(also-make-dependents):同时构建依赖于所列模块的模块;
-pl(projects):构建指定的模块,模块间用逗号分隔;
-rf(resume-from):从指定的模块回复反应堆

使用Nexus创建私服

通过建立自己的私服,可以降低中央仓库负荷、节省外网带宽、加速Maven构建、自己部署构件等,从而高效地使用Maven。

Nexus的仓库与仓库组

Nexus内置的仓库

  1. Maven Central:该仓库代理Maven中央仓库,其策略为Release,因此只会下载和缓存中央仓库中的发布版本构件;
  2. Releases:这是一个策略为Release的宿主类型仓库,用来部署组织内部的发布版本构件;
  3. Snapshots:这是一个策略为Snapshot的宿主类型仓库,用来部署组织内部的快照版本构件;
  4. 3rd party:这是一个策略为Release的宿主类型仓库,用来部署无法从公共仓库获得的第三方发布版本构件;
  5. Apache Snapshots:这是一个策略为Snapshot的代理仓库,用来代理Apache Maven仓库的快照版本构件;
  6. Codehaus Snapshots:这是一个策略为Snapshot的代理仓库,用来代理Codehaus Maven仓库的快照版本构件;
  7. Google Code:这是一个策略为Release的代理仓库,用来代理Google Code Maven仓库的发布版本构件;
  8. java.net-Maven 2:这是一个策略为Release的代理仓库,用来代理java.net Maven仓库的发布版本构件;
  9. Public Repositories:该仓库组将上述所有策略为Release的仓库聚合并通过一致的地址提供服务;
  10. Public Snapshot Repositories:该仓库组将上述所有策略为Snapshot的仓库聚合并通过一致的地址提供服务;

假设某公司建立了Maven项目X,公司内部建立了Nexus私服,为所有Maven项目提供服务:

  1. 项目X依赖于很多流行的开源类库如JUnit等,这些构件都能从Maven中央仓库获得,因此Maven Central代理仓库会被用来代理中央仓库的内容,并在私服上缓存下来;
  2. X还依赖于某个Google Code的项目,其构件在中央仓库中不存在,只存在于Google Code的仓库中,因此上述列表中的Google Code代理仓库会被用来代理并缓存这样的构件;
  3. X还依赖于Oracle的JDBC驱动,由于版权的因素,该类库无法从公共仓库获得,因此公司管理员将其部署到3rd party宿主仓库中,供X使用;
  4. X的快照版本构件成功后,会被部署到Snapshots宿主仓库中,供其他项目使用;
  5. 当X发布正式版本的时候,其构件会被部署到Release宿主仓库中;
  6. 由于X用到了上述列表中的很多仓库,为每个仓库声明Maven配置又比较麻烦,因此可以直接使用仓库组Public Repositories和Public Snapshot Repositories,当X需要JUnit的时候,它直接从Public Repositories下载,Public Repositories会选择Maven Central提供实际的内容;

在这里插入图片描述

  1. 如图:Maven可以直接从宿主仓库下载构件;Maven也可以从代理仓库下载构件,而代理仓库会间接地从远程仓库下载并缓存构件;最后,为了方便,Maven可以从仓库组下载构件,而仓库组没有实际内容(图中用虚线表示),它会转向其包含的宿主仓库或者代理仓库获得实际构件的内容;

创建Nexus宿主仓库

  1. 选择Hosted Repository;
  2. 填入仓库ID和名称,Repository Type表示该仓库的类型,Provider用来确定该仓库的格式(默认为Maven2Repository),然后是Repository Policy(发布版构件仓库还是快照版构件仓库);
  3. Default Local Storage Location为该仓库的默认存储目录,如sonatype-work/nexus/storage/repository-id/,Override Local Storage Location用来配置自定义的仓库目录位置;
  4. Deployment Policy用来配置仓库的部署策略,选项有只读(禁止部署)、关闭重新部署(同一构件只能部署一次)以及允许重新部署;
  5. Allow File Browsing表示是否允许浏览仓库内容,一般选True;
  6. Browse Storage选项卡以树形结构浏览仓库存储文件的内容;
  7. Include in Search表示是否对该仓库进行索引并提供搜索;
  8. Publish URL用来控制是否通过URL提供服务,如果选False,当访问该仓库的地址时,会得到HTTP 404 Not Found错误;
  9. 配置中最后的Not Found Cache TTL表示当一个文件没有找到后,缓存这一不存在信息的时间。以默认值1440分钟为例,如果某文件不存在,那么在之后的1440分钟内,如果Nexus再次得到该文件的请求,它将直接返回不存在信息,而不会查找文件系统。这么做是为了避免重复的文件查找操作以提升性能;

创建Nexus代理仓库

  1. 选择Proxy Repository;
  2. Remote Storage Location,用户必须输入有效的值;
  3. Download Remote Indexes表示是否下载远程仓库的索引,有些远程仓库拥有索引,下载其索引后,即使没有缓存远程仓库的构件,用户还是能够在本地搜索和浏览那些构件的基本信息;
  4. Checksum Policy配置校验和出错时的策略,用户可以选择忽略、记录警告信息或者拒绝下载;
  5. 当远程仓库需要认证的时候,这里的Authentication配置就能派上用处;
  6. Expiration Settings较宿主仓库多了Artifact Max Age和Metadata Max Age。其中,前者表示构件缓存的最长时间,后者表示仓库元数据文件缓存的最长时间。对于发布版仓库来说,Artifact Max Age默认值为-1,表示构件缓存后就一直保存着,不再重新下载。对于快照版仓库来说,Artifact Max Age默认值为1440分钟,表示每隔一天重新缓存代理的构件;
  7. 配置中最后两项为HTTP Request Settings和Override HTTP Proxy Settings,其中前者用来配置Nexus访问远程仓库时HTTP请求的参数,后者用来配置HTTP代理;

创建Nexus仓库组

  1. 选择Repository Group;
  2. 仓库组没有Release和Snapshot的区别,这不同于宿主仓库和代理仓库;
  3. 仓库组所包含的仓库的顺序决定了仓库组遍历其所含仓库的次序,因此最好将常用的仓库放在前面;

配置Maven从Nexus下载构件

1 在POM中配置Nexus仓库

<project>
……
<repositories>
<repository>
<id>nexus</id>
<name>Nexus/name>
<url>http://localhost:8081/nexus/content/groups/public//url>
<releases><enabled>true/enabled></releases>
<snapshots><enabled>true/enabled></snapshots>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>nexus</id>
<name>Nexus/name>
<url>http://localhost:8081/nexus/content/groups/public//url>
<releases><enabled>true/enabled></releases>
<snapshots><enabled>true/enabled></snapshots>
</pluginRepository>
</pluginRepositories>
……
</project>

这样的配置只对当前Maven项目有效。

2 在settings.xml中配置Nexus仓库

<settings>
……
<profiles>
<profile>
<id>nexus</id>
<repositories>
<repository>
<id>nexus</id>
<name>Nexus/name>
<url>http://localhost:8081/nexus/content/groups/public//url>
<releases><enabled>true/enabled></releases>
<snapshots><enabled>true/enabled></snapshots>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>nexus</id>
<name>Nexus/name>
<url>http://localhost:8081/nexus/content/groups/public//url>
<releases><enabled>true/enabled></releases>
<snapshots><enabled>true/enabled></snapshots>
</pluginRepository>
</pluginRepositories>
</profile>
</profiles>
<activeProfiles>
<activeProfile>nexus</activeProfile>
</activeProfiles>
……
</settings>

该配置中使用了一个id为nexus的profile,这个profile包含了相关的仓库配置,同时配置中又使用activeProfile元素将nexus这个profile激活,这样当执行Maven构建的时候,激活的profile会将仓库配置应用到项目中去。

3 配置镜像让Maven只使用私服

Maven除了从Nexus下载构件之外,还会不时地访问中央仓库central,我们希望的是所有Maven下载请求都仅仅通过Nexus,以全面发挥私服的作用。可以创建一个匹配任何仓库的镜像,镜像的地址为私服,这样,Maven对任何仓库的构件下载请求都会转到私服中。

<settings>
……
<mirrors>
<mirror>
<id>nexus</id>
<mirrorOf>*/mirrorOf>
<url>http://localhost:8081/nexus/content/groups/public/url>
</mirror>
</mirrors>
<profiles>
<profile>
<id>nexus</id>
<repositories>
<repository>
<id>central</id>
<url>http://central</url>
<releases>
<enabled>true/enabled>
</releases>
<snapshots>
<enabled>true/enabled>
</snapshots>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>central</id>
<url>http://central</url>
<releases>
<enabled>true/enabled>
</releases>
<snapshots>
<enabled>true/enabled>
</snapshots>
</pluginRepository>
</pluginRepositories>
</profile>
</profiles>
<activeProfiles>
<activeProfile>nexus</activeProfile>
</activeProfiles>
……
</settings>

仓库及插件仓库配置,它们的id都为central,也就是说,覆盖了超级POM中央仓库的配置,它们的url已无关紧要,因为所有请求都会通过镜像访问私服地址。配置仓库及插件仓库的主要目的是开启对快照版本下载的支持,当Maven需要下载发布版或快照版构件的时候,它首先检查central,看该类型的构件是否支持,得到正面的回答之后,再根据镜像匹配规则转而访问私服仓库地址。

部署构件至Nexus

如果只为代理外部公共仓库,那么Nexus的代理仓库就已经能够完全满足需要了。对于另一类Nexus仓库——宿主仓库来说,它们的主要作用是储存组织内部的,或者一些无法从公共仓库中获得的第三方构件,供大家下载使用。用户可以配置Maven自动部署构件至Nexus的宿主仓库,也可以通过界面手动上传构件。

使用Maven部署构件至Nexus

日常开发生成的快照版本构件可以直接部署到Nexus中策略为Snapshot的宿主仓库中,项目正式发布的构件则应该部署到Nexus中策略为Release的宿主仓库中。

<project>
……
<distributionManagement>
<repository>
<id>nexus-releases</id>
<name>Nexus Releases Repository/name>
<url>http://localhost:8081/nexus/content/repositories/releases//url>
</repository>
<snapshotRepository>
<id>nexus-snapshots</id>
<name>Nexus Snapshots Repository/name>
<url>http://localhost:8081/nexus/content/repositories/snapshots//url></snapshotRepository>
</distributionManagement>
……
</project>

Nexus的仓库对于匿名用户是只读的。为了能够部署构件,还需要在settings.xml中配置认证信息。

<settings>
……
<servers>
<server>
<id>nexus-releases</id>
<username>admin</username>
<password>*****/password>
</server>
<server>
<id>nexus-snapshots</id>
<username>admin</username>
<password>*****/password>
</server>
</servers>
……
</settings>

手动部署第三方构件至Nexus

某些Java Jar文件(如Oracle)的JDBC驱动,由于许可证的因素,它们无法公开地放在公共仓库中。此外,还有大量的小型开源项目,它们没有把自己的构件分发到中央仓库中,也没有维护自己的仓库,因此也无法从公共仓库获得。这个时候用户就需要将这类构件手动下载到本地,然后通过Nexus的界面上传到私服中。

要上传第三方构件,首先选择一个宿主仓库如3rd party,然后在页面的下方选择Artifact Upload选项卡。在上传构件的时候,Nexus要求用户确定其Maven坐标,如果该构件是通过Maven构建的,那么可以在GAV Definition下拉列表中选择From POM,否则就选GAV Parameters。

定义好坐标之后,单击Select Artifact(s)to Upload按扭从本机选择要上传的构件,然后单击Add Artifact按钮将其加入到上传列表中。Nexus允许用户一次上传一个主构件和多个附属构件(即Classifier)。最后,单击页面最下方的Upload Artifact(s)按钮将构件上传到仓库中。

在这里插入图片描述

Nexus的权限管理

  1. 使用Nexus往往会有一些安全性需求,例如希望只有管理员才能配置Nexus,只有某些团队成员才能部署构件。或者更细一些的要求,例如每个项目都有自己的Nexus宿主仓库,且只能部署项目构件至该仓库中;
  2. Nexus提供了全面的权限控制特性,能让用户根据需要配置Nexus用户、角色、权限等;

Nexus的访问控制模型

  1. Nexus是基于权限(Privilege)做访问控制的,服务器的每一个资源都有相应的权限来控制,因此用户执行特定的操作时就必须拥有必要的权限;
  2. 管理员必须以角色(Role)的方式将权限赋予Nexus用户。例如要访问Nexus界面,就必须拥有Status-(read)这个权限,而Nexus默认配置的角色UI:Basic UI Privileges就包含了这个权限,再将这个角色分配给某个用户,这个用户就能访问Nexus界面了;
  3. 用户可以被赋予一个或者多个角色,角色可以包含一个或者多个权限,角色还可以包含一个或者多个其他角色;
  4. Nexus预定义了三个用户: admin(拥有对Nexus服务的完全控制)、deployment(能够访问Nexus,浏览、搜索仓库、上传部署构件,但无法对Nexus进行任何配置)、anonymous(对应所有未登录的匿名用户,可以浏览、搜索仓库);
  5. Nexus预定义的一些常用且重要的角色:1 UI:Basic UI Privileges:包含了访问Nexus界面必须的最基本的权限;2 UI:Repository Browser:包含了浏览仓库页面所需要的权限;3 UI:Search:包含了访问快速搜索栏及搜索页面所需要的权限;4 Repo:All Repositories(Read):给予用户读取所有仓库内容的权限;5 Repo:All Repositories(Full Control):给予用户完全控制所有仓库内容的权限。用户不仅可以浏览、下载构件,还可以部署构件及删除仓库内容;6 UI:Logs and Config Files:包含了访问系统日志文件及配置文件所需要的权限;

为项目分配独立的仓库

  1. 在组织内部,如果所有项目都部署快照及发布版构件至同样的仓库,就会存在潜在的冲突及安全问题,我们不想让项目A的部署影响到项目B,反之亦然;
  2. 解决的方法就是为每个项目分配独立的仓库,并且只将仓库的部署、修改和删除权限赋予该项目的成员,其他用户只能读取、下载和搜索该仓库的内容;
  3. 假设项目名称为foo,首先为该项目建立两个宿主仓库Foo Snapshots和Foo Releases,分别用来部署快照构件和发布构件;
  4. 有了仓库之后,就需要创建基于仓库的增、删、改、查权限;
  5. 下一步是创建一个包含上述权限的角色;
  6. 角色创建完成之后,根据需要将其分配给Foo项目的团队成员。这样,其他团队的成员默认只能读取Foo Releases和Foo Snapshots的内容,而拥有Foo Deployer角色的用户就可以执行部署构件等操作。
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

hellosc01

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值