Maven

  Java不仅是一门编程语言,还是一个平台通过JRuby和Jython我们可以在Java平台上编写和运行Ruby和Python程序。

  Maven不仅是构建工具,还是一个依赖管理工具和项目信息管理工具。提供了中央仓库,能帮助自动下载构件。

  Java应用都会借用一些第三方的开源类库,这些类库都可以通过依赖的方式引入到项目中来。
随着依赖的增多,版本不一致,版本冲突,依赖臃肿等问题。
maven提供了一个优秀的解决方案,它通过一个坐标系统准确地定位每一个构件(artifact),也就通过一组坐标maven能够找到任何一个java类库。
Maven给这个类库世界引入了经纬,让他们变得有秩序,于是我们可以借助它来有序地管理依赖,轻松地解决那些繁杂的依赖问题

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

  Maven提供了免费的中央仓库,通过Maven的衍生工具(Nexus)我们还能进行快速地搜索,只要定位了坐标,Maven就能够帮我们自动下载,省去了手工劳动。

  Maven对于项目目录结构,测试用例命令方式等内容都有既定的规则,遵循这些规则,节约学习成本,约定优于配置(Convention Over Configuration)

安装目录说明

配置环境变量

  • M2_HOME = 安装目录( bin的上级目录)
  • Path = %M2_HOME%\bin

bin

bin 目录包含了mvn运行的脚本
这些脚本用来配置Java命令,准备好classpath和相关的Java系统属性,然后执行java命令。
其中mvn是基于unix平台的shell脚本,mvn.bat是基于Windows平台的bat脚本。在命令行输入任何一条mvn时实际上就是在调用这些脚本。
该目录还包含了mvnDebug和mvnDebug.bat两个文件,mvn和mvnDebug基本一样,mvnDebug多了一条Maven_DEBUG_OPTS配置,其作用就是在运行Maven时开启debug,以便调试maven本身。
此外,该目录还包含m2.conf文件,这是classworlds的配置文件。

boot

boot目录包含一个文件,plexus-classworlds-2.2.3.jar。
plexus-classworlds是一个类加载器框架,相对于默认的java类加载器,他提供了更丰富的语法以方便配置,Maven使用该框架加载自己的类库。更多信息。一般用户不必关心该文件。

Conf

该目录包含一个非常重要的文件 settings.xml 直接修改该文件,就能在机器上全局地定制maven的行为,建议复制该文件到 ***~/.m2/***目录下。
该命令打印出所有java系统属性和环境变量。

1
mvn help:system

默认情况下 .m2文件夹放置了Maven本地仓库 .m2/repository
所有的Maven构件都被存储到该仓库中,以方便复用。
Maven根据一套规则来确定任何一个构件在仓库中的位置,由于maven仓库是通过简单文件系统透明地展示给Maven用户的,所以可以绕过Maven直接查看或修改仓库文件,遇到问题这种方式十分有用。
默认情况下不使用 ~/.m2/repository作为本地仓库,
而是复制 M2_HOME/conf/settings.xml文件到 ~/.m2/settings.xml 修改自定义仓库配置

1
<localRepository>E:/maven/repository</localRepository>

Lib

该目录包含了所有maven运行时需要的java类库,maven本身是分模块开发的,因此用户能看到诸如maven-core-3.0.jar、maven-model-3.0.jar之类的文件。
此外这里还包含一些Maven用到的第三方依赖。可以说lib目录就是真正的Maven,用户可以在这个目录中找到Maven内置的超级pom。

其他

  • LICENSE.txt 记录Maven使用的软件许可证Apache License Version 2.0
  • NOTICE.txt 记录了Maven包含的第三方软件
  • README.txt 包含了Maven的简要介绍,包括安装需求以及如何安装简要指令

生命周期

Maven有三套相互独立的生命周期

  • clean
  • default
  • site

每个生命周期包含一些阶段phase,这些阶段是有顺序的,并且后面的阶段依赖于前面的阶段
用户与maven最直接的交互方式就是调用这些生命周期
用户调用某个生命周期的某个阶段,不会影响其他的生命周期

clean

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

  • pre-clean 执行一些清理前需要完成的工作。调用 pre-clean 的时候只有 pre-clean 阶段得以执行
  • clean 清理上一次构建生成的文件。调用clean,pre-clean和clean阶段会得以顺序执行
  • post-clean 执行一些清理后需要完成的工作。调用post-clean,pre-clean,clean,post-clean会顺序执行

default

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

  • Validate:
  • Initialize:
  • Generate-sources:
  • Process-sources:处理项目主资源文件,一般来说是对src/main/resources目录的内容进行变量替换等工作后,复制到项目输出的主classpath目录中
  • Generate-resources:
  • Process-resources:
  • Compile:编译项目的主源码,一般来说,是便宜src/main/java目录下的Java文件至项目输出的主classpath目录中
  • Process-classes:
  • Generate-test-sources:
  • Process-test-sources:处理项目测试资源文件,一般来说是对src/test/resources目录的内容进行变量替换等工作后,复制到项目输出的测试classpath目录中
  • Generate-test-resources:
  • Process-test-resources:
  • Test-compile:编译项目的测试代码,一般来说是编译src/test/java目录下的java文件至项目输出的测试classpath目录中,
  • Process-test-classes:
  • Test:
  • Prepare-package:
  • Package:接受编译好的代码,打包成可发布的格式,jar,war等
  • Pre-integration-test:
  • Integration-test:
  • Post-integration-test:
  • Verify:
  • Install:将安装包到maven本地仓库,供本地其他项目使用
  • Deploy:将最终的包复制到远程仓库,供其它开发人员和maven项目使用

site

site 生命周期的目的是建立项目站点。建立和发布项目站点,maven 能够给予 pom 所包含的信息,自动生成一个友好的站点,方便团队交流和发布项目信息。包含以下阶段

  • Pre-site:执行一些在生成项目站点之前需要完成的工作
  • Site:生成项目站点文档
  • Post-site:执行一些在生成项目站点之后需要完成的工作
  • Site-deploy:将生成项目站点发布到服务器上

命令行与生命周期

命令行执行 maven 任务的最主要方式就是调用 maven 的生命周期阶段,需要注意的是,各个生命周期是相互独立的,而一个生命周期的阶段有前后依赖关系。maven中主要的生命周期阶段并不多,而常用的maven命令实际都是基于这些阶段简单组合而成的。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
# 该命令调用clean生命周期的clean阶段,实际执行的阶段为clean生命周期的pre-claen和clean阶段。
mvn clean

# 生命周期的test阶段,执行阶段为defaul生命周期的validate,initialize。。。直到test的所有阶段
# 所以执行测试的时候,项目的代码能够自动得以编译。
mvn test default

# 调用clean的clean阶段,和default的install阶段,在执行真正的项目构建之前清理项目是一个很好的实践。
mvn clean install

# 该命令调用clean生命周期的clean阶段,default生命周期的deploy阶段以及site生命周期的site-deploy阶段
mvn clean deploy site-deploy

总结

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
# 任何maven项目中都可以执行这些命令

# 编译
mvn clean compile

# 执行test之前会先执行compile
mvn clean test

# 打包 执行package之前会先执行test
mvn clean package

# 安装 执行install之前会执行package
mvn clean install

error

20171017 cmd 执行 mvn clean compile 报错

错误描述:

1
No compiler is provided in this environment. Perhaps you are running on a JRE rather than a JDK?

问题触发条件:
使用 JDK1.8 安装后,会在 Win10 系统 path 中新增 C:\ProgramData\Oracle\Java\javapath
cmd 命令行执行 mvn clean compile 会出现上述错误

解决方法:
C:\ProgramData\Oracle\Java\javapath 从(系统)path 中删除

命令解析:

  • clean maven 清理输出目录 target/
  • compile maven 编译项目

从日志输出中看到 maven 首先执行了 clean:clean 任务,删除 target/ 目录。默认情况下maven构建的所有输出都在 target/ 目录中;
接着执行 resources:resources 任务(未定义项目资源,暂且略过);
最后执行 compile:compile 任务,将项目主代码编译至 target/classes

20171018 cmd mvn clean test

1
mvn clean test

maven实际执行的还有

  • clean:clean
  • resources:resources
  • compiler:compiler
  • resources:testResources
  • compiler:test:Compiler

在Maven执行测试test之前,会先自动执行项目主资源处理,主代码编译,测试资源处理,测试代码编译等工作,这是maven生命周期的一个特性。
测试代码通过编译之后在 target/test-classes 下生成二进制文件,surefile:test 任务运行测试,surefire 是 maven 中 负责执行测试的插件,这里它运行测试用例 HelloWorldTest,并且输出测试报告,显示一共运行了多少测试,失败多少,出错多少,跳过多少

1
mvn clean package

项目编译,测试之后一个重要步骤是打包,HelloWorld的POM中没有指定打包类型,使用默认打包类型jar
类似地maven会在打包之前执行编译,测试等操作。
jar:jar 任务负责打包,实际上是jar插件的jar目标将项目主代码打包成一个名为 hello-world-1.0-SNAPSHOT.jar的文件,该文件也位于 ***target/***输出目录中,他是根据 artifact-version.jar 规则进行命名的,有需要还可以使用 finalName来定义改文件的名称。
可以复制 jar 文件到其他项目的 classpath中从而使用 HelloWorld 类

1
mvn clean install

其他 maven 项目直接引用这个 jar 需要一个安装步骤
在打包之后又执行了安装任务 install:install,从输出可以看到该项目输出的jar安装到了Maven本地仓库中,只有将 HelloWorld 的构件安装到本地仓库后,其他maven项目才能通过 GAV坐标使用该 jar

可运行 jar 包 默认打包生成的 jar 不能够直接运行的,因为带有 main 方法的类信息不会添加到 manifest 中(打开jar文件中的 META-INF/MANIFEST.MF文件,将无法看到 Main-Class行)。
借助 maven-shade-plugin 生成可执行的jar文件,在pom中配置该插件

插件

插件目标

   Maven 的核心仅仅定义了抽象的生命周期,具体的任务是交由插件完成的,插件以独立的构件形式存在,因此,Maven核心的分发包只有不到3MB的大小,Maven会在需要的时候下载并使用插件。 对于插件本身,为了能够复用代码,它往往能够完成多个任务。
  maven-dependency-plugin能够基于项目的依赖分析项目依赖,帮助找出潜在的无用依赖;它能够列出项目的依赖树,帮助分析来源,列出项目所有已解析的依赖。为每个这样的功能编写一个独立的插件显然是不可取的,因为这些任务背后有很多可以复用的代码,因此,这些功能聚集在一个插件里,每个功能就是一个插件目标。
Maven-dependency-plugin有十多个目标,每个目标对应一个功能。通用写法,冒号前是插件前缀,冒号后面是插件目标

  • Dependency:analyze 分析依赖
  • Dependency:tree 列出依赖树
  • Dependency:list 列出所有已解析的依赖

插件绑定

  Maven的生命周期与插件相互绑定,用以完成实际的构建任务,具体而言是生命周期的阶段与插件的目标相互绑定,以完成某个具体的构建任务。
例如项目编译对应default生命周期的compile阶段,maven-compile-plugin这一插件的compile目标能够完成该任务,因此他们绑定就能实现项目编译的目的。

  • 内置绑定: 为了能让用户不用任何配置就能构建maven项目,maven在核心为一些主要的生命周期阶段绑定了很多插件的目标,当用户通过命令行调用生命周期阶段的时候没对应的插件目标就会执行相应的任务
  • 自定义绑定: 用户可以将某个插件目标绑定到生命周期的某个阶段上,这种自定义绑定方式能让Maven项目在构建过程中执行更多更丰富的特色的任务
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
  <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>

  查看帮助

1
2
# 该命令输出对应插件的详细信息
mvn help:describe -Dplugin org.apache.maven.plugins:maven-source-plugin:2.1.1 -Ddetails

当插件目标被绑定到不同的生命周期阶段的时候,其执行顺序由生命周期阶段的先后顺序决定,如果多个目标被绑定到同一阶段,他们的执行顺序由插件声明的先后顺序决定。在 POM 文件的 build 元素下的 plugins 子元素中声明插件的使用。该例中用到的是 maven-source-plugin

  • groupId: org.apache.maven.plugins(属于 maven官方插件的 groupId)
  • artifactId: maven-source-plugin
  • version: 2.1.1

对于自定义绑定的插件,用户总是应该声明一个非快照版本,这样可以避免由于插件版本变化造成构建不稳定
插件执行配置,executions 下每个 execution 子元素可以用来配置执行一个任务
该例中配置了一个 id 为 attach-sources 的任务,通过 phrase 配置,将其绑定到 verify 生命周期阶段上,再通过 goals 配置指定要执行的插件目标
运行 mvn verify,maven-source-plugin:jar-no-fork 会得以执行,它会创建一个以 -sources.jar 结尾的源码文件包
有时候即使不通过 phase 元素配置生命周期阶段,插件目标也能绑定到生命周期中去,如删除 phase 这一行,再执行 mvn verify 仍然可以看到 maven-source-plugin:jar-no-fork 得以执行,这是因为有很多插件的目标在编写时已经定义了默认绑定阶段,可以使用 maven-help-plugin 查看插件详细信息,了解插件目标的默认绑定阶段。

插件配置

  配置插件目标的参数,进一步调整插件目标所执行的任务以满足项目的需求,几乎所有 maven 插件的目标都有一些可配置的参数,用户可以通过命令行和POM配置等方式来配置这些参数。
Maven-surefire-plugin 提供了一个 maven.test.skip 参数,当其值为 true 时会跳过执行测试。于是在运行命令的时候加上如下 -D 参数就能跳过测试。
-D 是 java 自带的,其功能是通过命令行设置一个 java 系统属性,maven 简单地重用了该参数,在准备插件的时候检查系统属性,便实现了插件参数的配置。

1
2
# 命令行插件配置
mvn install –Dmaven.test.skip=true

并不是所有插件参数都适合命令行配置,有些参数的值从项目创建到项目发布都不会改变,或者说很少改变,对于这种情况,在 POM文件中一次性配置就显然比重复在命令行输入要方便。这样绑定到 compile 阶段的maven-compiler-plugin:testCompiler 任务,就能够使用该配置,基于 Java1.5 版本进行编译

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
<!-- POM插件全局配置 -->
<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>2.1</version>
            <configuration>
                <!-- 配置maven-compiler-plugin告诉它编译JAVA1.5版本的源文件-->
                <source>1.5</source>
                <!-- 配置maven-compiler-plugin告诉它生成与JVM1.5兼容的字节码文件-->
                <target>1.5</target>
            </congiguration>
        </plugin>
    </plugins>
</build>

传递性依赖和依赖范围

依赖范围不仅可以控制依赖与三种classpath的关系,还对传递性依赖产生影响。
A依赖于B,B依赖于C,我们说A对于B是第一直接依赖,B对于C是第二直接依赖,A对于C是传递性依赖。
第一直接依赖的范围和第二直接依赖的范围决定了传递性依赖的范围

传递性依赖的范围第二直接依赖→compiletestprovidedruntime
第一直接依赖↓
compilecompile--runtime
testtest--test
providedprovided-providedprovided
runtimeruntime--runtime

依赖调解

引入传递性依赖机制,大大简化和方便依赖声明,大部分情况只需要关心项目的直接依赖是什么,而不用考虑这些直接依赖会引入什么传递性依赖
但是当传递性依赖造成问题( jar 包版本冲突)的时候就需要清楚地知道传递性依赖是从那条依赖路径引入的
如果有两个版本,maven依赖调解 Dependency Mediation 的第一原则:路径最近者优先
如果路径长度一样,maven2.0.9 开始 maven 定义了依赖调解第二原则:第一声明者优先
在 POM 中依赖声明的顺序决定了谁会被解析使用,顺序最靠前的那个依赖优胜
但是还是要尽量避免版本冲突,因为高版本有的,低版本不一定有,造成不必要的错误

可选依赖

A依赖B,B有两个互斥特性,分别依赖于 X,Y,用户不可能同时使用两个特性,

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
<!-- B的依赖声明:使用 optional 元素表示 mysql 和 postgresql 这两个依赖为可选依赖,只会对 B 产生影响,当其他项目依赖 B 时这两个依赖不会被传递 -->
<project>
    <modelVersion>4.0.0</modelVersion>
    <groupId>cn.aiclr.mvn</groupId>
    <artifactId>project-b</artifactId>
    <version>1.0.0</version>
    <dependencies>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.10</version>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>postgresql</groupId>
            <artifactId>postgresql</artifactId>
            <version>8.4-701.jdbc3</version>
            <optional>true</optional>
        </dependency>
    </dependencies>
</project>
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
<!-- A的依赖声明当项目A依赖B时,如果A使用mysql数据库,那么A需要显示地声明msql依赖 -->
<project>
    <modelVersion>4.0.0</modelVersion>
    <groupId>cn.aiclr.mvn</groupId>
    <artifactId>project-a</artifactId>
    <version>1.0.0</version>
    <dependencies>
        <dependency>
            <groupId>cn.aiclr.mvn</groupId>
            <artifactId>project-b</artifactId>
            <version>1.0.0</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.10</version>
        </dependency>
    </dependencies>
</project>

在理想情况下,不应该使用可选依赖,在面向对象设计中,有单一职责性原则,意指一个类应该只有一项职责,而不是糅合太多的功能,这个原则在规划 Maven 项目的时候也同样适用
上述例子更好的处理方式是为 mysql 和 postgesql 分别创建一个 maven 项目,基于同样的 groupId 分配不同的 artifactId。
cn.aiclr.mvn:project-b-mysql

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
<project>
    <modelVersion>4.0.0</modelVersion>
    <groupId>cn.aiclr.mvn</groupId>
    <artifactId>project-b-mysql</artifactId>
    <version>1.0.0</version>
    <dependencies>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.10</version>
            <optional>true</optional>
        </dependency>
    </dependencies>
</project>

cn.aiclr.mvn:project-b-postgresql

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
<project>
    <modelVersion>4.0.0</modelVersion>
    <groupId>cn.aiclr.mvn</groupId>
    <artifactId>project-b-postgresql</artifactId>
    <version>1.0.0</version>
    <dependencies>
        <dependency>
            <groupId>postgresql</groupId>
            <artifactId>postgresql</artifactId>
            <version>8.4-701.jdbc3</version>
            <optional>true</optional>
        </dependency>
    </dependencies>
</project>

在各自的 pom 中声明对应的 JDBC 驱动依赖,用户根据需要选择使用 mysql 或者 postgresql 。利用传递性依赖,就不用再次声明 JDBC 驱动依赖

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
<!-- A的依赖声明当项目A依赖B时,如果A使用mysql数据库,那么A不需要显示地声明msql依赖 -->
<project>
    <modelVersion>4.0.0</modelVersion>
    <groupId>cn.aiclr.mvn</groupId>
    <artifactId>project-a</artifactId>
    <version>1.0.0</version>
    <dependencies>
        <dependency>
            <groupId>cn.aiclr.mvn</groupId>
            <artifactId>project-b-mysql</artifactId>
            <version>1.0.0</version>
        </dependency>
    </dependencies>
</project>

排除依赖

传递性依赖会给项目隐式地引入很多依赖,这极大地简化了项目依赖的管理,但是有时候需要排除一些不需要的依赖
项目A依赖B,不想引入传递性依赖C,而是自己显示地声明C,1.1.0版本的依赖
使用<exclusions><exclusion>元素声明排除依赖,声明exclusion时只需要<groupId><artifactId>不需要version元素
因为只需要groupId和artifactId就能唯一定位依赖图中的某个依赖 Maven解析后的依赖中,不可能出现groupId和artifactId相同version不同的两个依赖

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<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>

归类依赖

在邮箱验证模块有很多关于 springframework 的依赖

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-core</artifactId>
    <version>2.5.6</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-beans</artifactId>
    <version>2.5.6</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>2.5.6</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context-support</artifactId>
    <version>2.5.6</version>
</dependency>

使用 Maven 属性,properties元素定义 Maven 属性,定义了 springframework.version 子元素其值为 2.5.6。
Maven 运行时会将 POM 中所有的 ${springframework.version} 替换为 2.5.6 美元符号加大括号环绕的方式引用 Maven属性。可以优化为如下,更简洁

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<properties>
	<springframework.version>2.5.6</springframework.version>
</properties>
 <dependencies>
 	<dependency>
		<groupId>org.springframework</groupId>
		<artifactId>spring-core</artifactId>
		<version>${springframework.version}</version>
	</dependency>
	<dependency>
		<groupId>org.springframework</groupId>
		<artifactId>spring-beans</artifactId>
		<version>${springframework.version}</version>
	</dependency>
	<dependency>
		<groupId>org.springframework</groupId>
		<artifactId>spring-context</artifactId>
		<version>${springframework.version}</version>
	</dependency>
	<dependency>
		<groupId>org.springframework</groupId>
		<artifactId>spring-context-support</artifactId>
		<version>${springframework.version}</version>
	</dependency>
 <dependencies>

优化依赖

Maven会自动解析所有项目的直接依赖和传递性依赖,并且根据规则正确判断每一个依赖的范围。对于一些依赖冲突,也能进行调节,以确保任何一个构件只有唯一的版本在依赖中存在,在上面这些工作之后,最后得到的依赖称为已解析依赖。

1
2
3
4
5
6
7
8
# 查看依赖
mvn dependency:list

# 查看依赖树
mvn dependency:tree

# 分析当前项目依赖
mvn dependency:analyze

多模块

  • DepencyManagement: 在顶层的POM文件中,通过 DepencyManagement 元素来管理jar包的版本,让子项目中引用一个依赖而不用显示的列出版本号。Maven会沿着父子层次向上走,直到找到一个拥有 dependencyManagement 元素的项目,然后它就会使用在这个 dependencyManagement 元素中指定的版本号。可以统一管理项目的版本号,确保应用的各个项目的依赖和版本一致,保证测试和发布是相同的成果,因此在顶层 pom 中定义共同的依赖关系,同时可以避免在每个使用的子项目中都声明一个版本号,这样想升级或者切换版本,只需在父类容器更新。如果子项目需要另外一个版本号时,只需要在自己的 pomdependencies 中声明一个版本号,子项目就可以使用自己声明的版本号,不继承父类版本号。
  • Dependencies:相对于 depencyManagement,所有声明在 dependencies 里的依赖都会自动引入,并默认被所有子项目继承

区别:

  • dependencies 即使在子项目中不写该依赖项,那么子项目仍然会从父项目中继承该依赖项(全部继承)
  • dependencyManagement 里只是声明依赖,并不实际引入,因此子项目需要显示的声明需要用的依赖
    • 不在子项目中声明依赖,不会从父项目中继承依赖
    • 只有在子项目中写了该依赖项,并且没有指定具体版本,才会从父项目中继承该项,并且 versionscope 都读取自父 pom。如果子项目中指定了版本号,那么会使用子项目中指定的jar版本