依赖机制介绍html
依赖管理是Maven众所周知的特性之一,也是Maven擅长的领域之一.管理单个项目的依赖并非太困难,可是当你开始处理由数十个甚至上百个模块组成的多模块项目或者应用时,Maven将会很好的帮助你保持项目的高可控性和稳定性.java
传递依赖sql
传递依赖是Maven 2.0 的新特性.它让你再也不须要发现并指定你所须要的依赖库并自动包含它们.apache
这个特性是经过从你指定的远程仓库阅读你所依赖的项目文件来简化的.一般来说,那些项目的全部依赖被使用在你的项目中,它们可能继承自它们的父项目或者来自它们的依赖等等.maven
依赖能够汇集的层级数量没有限制,只有在发现循环依赖时才会引起问题.ide
因为依赖的传递,包含库的图标能够迅速膨胀到至关大.鉴于这个缘由,这里有一些额外的特性将会限制依赖的包含:测试
依赖调解 - 它决定了使用什么版本的依赖当遭遇到一个工件的多个版本时.通常的,Maven 2.0 只支持"最近定义",这意味着它会采用依赖树中最近的依赖版本提供给你的项目.你也能够经过在你的POM中显示声明一个依赖来确保它的版本.注意,若是两个依赖版本在依赖树中的相同层次,直到 Maven 2.0.8 它都没有准肯定义哪个将胜出,可是自从 Maven 2.0.9 开始宣布第一个声明将胜出.("最近定义"意味着被采用的依赖版本将会是在依赖树中离你项目最近的那个,例如:依赖被定义成A->B->C->D 2.0 和A->E->D 1.0,那么当你构建A时,D1.0将会被采用,由于它的线路比较短.你能够在POM中显示声明D 2.0来强制指定版本).插件
依赖管理 - 它容许项目做者直接指定工件的使用版本当他们在传递依赖或者在那些没有指定版本的依赖中遭遇时.在前面部分的例子中有一个依赖直接添加给了A尽管A没有直接使用它.取而代之的是,A能直接包含D做为依赖在依赖管理部分而且直接控制D要使用的版本.code
依赖范围 - 这容许你只为当前构建阶段包含适当的依赖.后面会详细描述.xml
排除依赖 - 若是X依赖Y,Y依赖Z,那么X的全部者能够经过"exclusion"元素来显式排除对Z的依赖.
可选依赖 - 若是Y依赖Z,Y的全部者能够经过"optional"元素来标记Z做为可选依赖.当X依赖Y时,X就只会依赖Y而不会依赖Z.X的全部者能够显式添加Z做为依赖.(把可选依赖想象成"默认的依赖排除"有助于理解).
依赖范围
依赖范围用来限制依赖的传递,也影响各类构建任务使用的类路径.
这里有6种范围:
compile
这是默认范围,当没有指定范围的时候默认使用它.这些依赖在项目的全部类路径下都是可用的,另外,它们会传播到依赖它们的项目中.
provided
这个compile很像,可是它代表你指望在运行时提供给依赖的JDK或者一个容器.例如,当构建一个J2EE的Web应用的时候,你将会设置Servlet API和相关的J2EE API依赖范围为provided由于Web容器提供这些类.这个范围只有在编译和测试类路径可用,并且它不会传递.
runtime
这个范围代表编译的时候不须要这些依赖,可是执行的时候须要.它们会在运行时和测试类路径下,可是不会在编译类路径下.
test
这个范围代表一般的使用状况下都不须要这些依赖,它们只针对测试编译和执行阶段有用.
system
这个范围和provided相似除了你不得不显式提供包含它的JAR.这个工件老是可用的并且它不会在仓库中进行查找
import(Maven 2.0.9+)
这个范围只能被用在<dependencyManagement>部分中类型为pom的依赖.它代表指定的POM应该被替换成在那个POM中 <dependencyManagement>部分里的依赖.由于它们用于替换,因此范围是import的依赖它们实际不参与限制依赖的传递.
上述每一种范围(除了import)都以不一样的方式来影响依赖传递,下面的表格演示了这种影响.第一列表明一个依赖的设置范围,第一行表示那个依赖传递通过的依赖范围,交汇处表示结果范围.若是没有结果范围被列出,表示这个依赖将被忽略.
compile | provided | runtime | test | |
compile | compile(*) | - | runtime | - |
provided | provided | - | provided | - |
runtime | runtime | - | runtime | - |
test | test | - | test | - |
请注意:(*)意指这应该被runtime范围取代,志抑郁于全部compile依赖必须被显式的罗列出来.可是,存在这样的状况,你所依赖的库继承自另外一个库的类,会强制你在编译时期就可用.因为这个缘由,在编译时期这些依赖保持compile范围甚至当它们是可传递的时候.
依赖管理
依赖管理部分是一个集中化依赖信息的机制.当你有一堆项目都继承了一个通用的父项目,那么这就能够把全部依赖信息放进父项目的POM从而让子项目的POM变得简单.能够经过几个例子来阐明这个机制.给出两个有相同继承的POM:
项目A:
<project> ... <dependencies> <dependency> <groupId>group-a</groupId> <artifactId>artifact-a</artifactId> <version>1.0</version> <exclusions> <exclusion> <groupId>group-c</groupId> <artifactId>excluded-artifact</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>group-a</groupId> <artifactId>artifact-b</artifactId> <version>1.0</version> <type>bar</type> <scope>runtime</scope> </dependency> </dependencies> </project>
项目B:
<project> ... <dependencies> <dependency> <groupId>group-c</groupId> <artifactId>artifact-b</artifactId> <version>1.0</version> <type>war</type> <scope>runtime</scope> </dependency> <dependency> <groupId>group-a</groupId> <artifactId>artifact-b</artifactId> <version>1.0</version> <type>bar</type> <scope>runtime</scope> </dependency> </dependencies> </project>
这两个示例POM共享了一个通用的依赖并且它们各自都有一个重要的依赖.这些信息能够被放进父POM:
<project> ... <dependencyManagement> <dependencies> <dependency> <groupId>group-a</groupId> <artifactId>artifact-a</artifactId> <version>1.0</version> <exclusions> <exclusion> <groupId>group-c</groupId> <artifactId>excluded-artifact</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>group-c</groupId> <artifactId>artifact-b</artifactId> <version>1.0</version> <type>war</type> <scope>runtime</scope> </dependency> <dependency> <groupId>group-a</groupId> <artifactId>artifact-b</artifactId> <version>1.0</version> <type>bar</type> <scope>runtime</scope> </dependency> </dependencies> </dependencyManagement> </project>
而后两个子POM就更简单了:
<project> ... <dependencies> <dependency> <groupId>group-a</groupId> <artifactId>artifact-a</artifactId> </dependency> <dependency> <groupId>group-a</groupId> <artifactId>artifact-b</artifactId> <!-- This is not a jar dependency, so we must specify type. --> <type>bar</type> </dependency> </dependencies> </project>
<project> ... <dependencies> <dependency> <groupId>group-c</groupId> <artifactId>artifact-b</artifactId> <!-- This is not a jar dependency, so we must specify type. --> <type>war</type> </dependency> <dependency> <groupId>group-a</groupId> <artifactId>artifact-b</artifactId> <!-- This is not a jar dependency, so we must specify type. --> <type>bar</type> </dependency> </dependencies> </project>
注意:这些依赖中有两个,咱们必须指定<type>元素.这是由于在<dependencyManagement>部分用于匹配一个依赖引用的最小信息集合其实是{groupId,artifactId,type,classifier}.在不少状况下,这些依赖将引用没有classifier的jar工件,这就容许咱们能够将标识集合简写成{groupId,artifactId},由于type的默认值就是jar,而classifier的默认值是null.
第二点,也是很是重要的关于依赖管理的用处是用来在传递依赖中控制工件的版本.做为一个示例,考虑这些项目:
项目A:
<project> <modelVersion>4.0.0</modelVersion> <groupId>maven</groupId> <artifactId>A</artifactId> <packaging>pom</packaging> <name>A</name> <version>1.0</version> <dependencyManagement> <dependencies> <dependency> <groupId>test</groupId> <artifactId>a</artifactId> <version>1.2</version> </dependency> <dependency> <groupId>test</groupId> <artifactId>b</artifactId> <version>1.0</version> <scope>compile</scope> </dependency> <dependency> <groupId>test</groupId> <artifactId>c</artifactId> <version>1.0</version> <scope>compile</scope> </dependency> <dependency> <groupId>test</groupId> <artifactId>d</artifactId> <version>1.2</version> </dependency> </dependencies> </dependencyManagement> </project>
项目B:
<project> <parent> <artifactId>A</artifactId> <groupId>maven</groupId> <version>1.0</version> </parent> <modelVersion>4.0.0</modelVersion> <groupId>maven</groupId> <artifactId>B</artifactId> <packaging>pom</packaging> <name>B</name> <version>1.0</version> <dependencyManagement> <dependencies> <dependency> <groupId>test</groupId> <artifactId>d</artifactId> <version>1.0</version> </dependency> </dependencies> </dependencyManagement> <dependencies> <dependency> <groupId>test</groupId> <artifactId>a</artifactId> <version>1.0</version> <scope>runtime</scope> </dependency> <dependency> <groupId>test</groupId> <artifactId>c</artifactId> <scope>runtime</scope> </dependency> </dependencies> </project>
当在项目B上运行Maven的时候,a,b,c和d的1.0版本将会被使用不论在它们的pom中指定的版本是什么:
a和c都做为项目的依赖被声明因此依赖调解使用版本1.0是能够预见的.并且它们也都具备runtime的范围由于被直接指定了.
b定义在项目B的父项目中的依赖管理部分并且由于依赖管理对传递依赖的优先级高于依赖调解,
因此版本1.0将被选择,b也将会有compile范围.
最后,由于d被指定在项目B的依赖管理部分,d应该是a或c的一个依赖,版本1.0将会被再次选择,由于依赖管理的优先级高于依赖调解并且也由于当前pom的声明优先级高于它的父pom的声明.
可用的依赖管理标签的参考信息请查阅:https://maven.apache.org/ref/3.3.9/maven-model/maven.html#class_DependencyManagement
导入依赖
这个特性只有在Maven 2.0.9或者以后的版本可用.这意味着poms声明范围import在早期的Maven版本中不能被解析.在你决定使用它以前请仔细考量这个信息,若是你决定使用它,咱们建议你使用强制插件来要求至少2.0.9的Maven版本.
以前的例子描述了如何经过继承来指定托管的依赖.然而,在大型项目中这基本上是不能实现的由于一个项目只能继承自一个父项目.为了实现这一点,项目能够从其余项目导入托管的依赖.这经过描述一个带有范围"import"的pom工件来做为依赖来实现.
项目B:
<project> <modelVersion>4.0.0</modelVersion> <groupId>maven</groupId> <artifactId>B</artifactId> <packaging>pom</packaging> <name>B</name> <version>1.0</version> <dependencyManagement> <dependencies> <dependency> <groupId>maven</groupId> <artifactId>A</artifactId> <version>1.0</version> <type>pom</type> <scope>import</scope> </dependency> <dependency> <groupId>test</groupId> <artifactId>d</artifactId> <version>1.0</version> </dependency> </dependencies> </dependencyManagement> <dependencies> <dependency> <groupId>test</groupId> <artifactId>a</artifactId> <version>1.0</version> <scope>runtime</scope> </dependency> <dependency> <groupId>test</groupId> <artifactId>c</artifactId> <scope>runtime</scope> </dependency> </dependencies> </project>
假设项目A是以前例子中定义过的,最终结果将是同样的.全部A管理的依赖都将被合并到B中除了d,由于d在这个POM中已经定义了.
项目X:
<project> <modelVersion>4.0.0</modelVersion> <groupId>maven</groupId> <artifactId>X</artifactId> <packaging>pom</packaging> <name>X</name> <version>1.0</version> <dependencyManagement> <dependencies> <dependency> <groupId>test</groupId> <artifactId>a</artifactId> <version>1.1</version> </dependency> <dependency> <groupId>test</groupId> <artifactId>b</artifactId> <version>1.0</version> <scope>compile</scope> </dependency> </dependencies> </dependencyManagement> </project>
项目Y:
<project> <modelVersion>4.0.0</modelVersion> <groupId>maven</groupId> <artifactId>Y</artifactId> <packaging>pom</packaging> <name>Y</name> <version>1.0</version> <dependencyManagement> <dependencies> <dependency> <groupId>test</groupId> <artifactId>a</artifactId> <version>1.2</version> </dependency> <dependency> <groupId>test</groupId> <artifactId>c</artifactId> <version>1.0</version> <scope>compile</scope> </dependency> </dependencies> </dependencyManagement> </project>
项目Z:
<project> <modelVersion>4.0.0</modelVersion> <groupId>maven</groupId> <artifactId>Z</artifactId> <packaging>pom</packaging> <name>Z</name> <version>1.0</version> <dependencyManagement> <dependencies> <dependency> <groupId>maven</groupId> <artifactId>X</artifactId> <version>1.0</version> <type>pom</type> <scope>import</scope> </dependency> <dependency> <groupId>maven</groupId> <artifactId>Y</artifactId> <version>1.0</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> </project>
在上面的例子中,Z从X和Y导入了它们托管的依赖.然而,X和Y都声明了依赖a.在这里,a版本1.1将被使用,由于X是首先声明的,并且a没有在Z的依赖管理中声明.
这个过程是递归的.例如,若是X导入了另外一个pom,Q,当Z在处理的时候,你会发现Q的全部依赖被定义在X中.
导入是颇有效的当用来定义由一系列相关的工件构成的一个"库"时.一个项目使用来自这些"库"的一个或多个工件是至关常见的.然而,有时候很难保持在项目中使用的工件版本和分布在那些库里的版本的同步一致.下面的模式阐明了如何建立一个材料清单(BOM)来供其余项目使用.
项目的根是BOM pom.它定义了全部将会被建立在库里的工件的版本.其余项目想要使用这些库应该导入这个pom到它们的POM中的依赖管理部分.
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.test</groupId> <artifactId>bom</artifactId> <version>1.0.0</version> <packaging>pom</packaging> <properties> <project1Version>1.0.0</project1Version> <project2Version>1.0.0</project2Version> </properties> <dependencyManagement> <dependencies> <dependency> <groupId>com.test</groupId> <artifactId>project1</artifactId> <version>${project1Version}</version> </dependency> <dependency> <groupId>com.test</groupId> <artifactId>project2</artifactId> <version>${project1Version}</version> </dependency> </dependencies> </dependencyManagement> <modules> <module>parent</module> </modules> </project>
子项目用BOM做为它的父项目.
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>com.test</groupId> <version>1.0.0</version> <artifactId>bom</artifactId> </parent> <groupId>com.test</groupId> <artifactId>parent</artifactId> <version>1.0.0</version> <packaging>pom</packaging> <dependencyManagement> <dependencies> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.12</version> </dependency> <dependency> <groupId>commons-logging</groupId> <artifactId>commons-logging</artifactId> <version>1.1.1</version> </dependency> </dependencies> </dependencyManagement> <modules> <module>project1</module> <module>project2</module> </modules> </project>
接下来是真正的项目POMs
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>com.test</groupId> <version>1.0.0</version> <artifactId>parent</artifactId> </parent> <groupId>com.test</groupId> <artifactId>project1</artifactId> <version>${project1Version}</version> <packaging>jar</packaging> <dependencies> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> </dependency> </dependencies> </project> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>com.test</groupId> <version>1.0.0</version> <artifactId>parent</artifactId> </parent> <groupId>com.test</groupId> <artifactId>project2</artifactId> <version>${project2Version}</version> <packaging>jar</packaging> <dependencies> <dependency> <groupId>commons-logging</groupId> <artifactId>commons-logging</artifactId> </dependency> </dependencies> </project>
下面的项目展现了如今"库"如何可以被另外一个项目使用而不须要指定依赖的项目版本.
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.test</groupId> <artifactId>use</artifactId> <version>1.0.0</version> <packaging>jar</packaging> <dependencyManagement> <dependencies> <dependency> <groupId>com.test</groupId> <artifactId>bom</artifactId> <version>1.0.0</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <dependencies> <dependency> <groupId>com.test</groupId> <artifactId>project1</artifactId> </dependency> <dependency> <groupId>com.test</groupId> <artifactId>project2</artifactId> </dependency> </dependencies> </project>
最后,在建立项目导入依赖时小心下面几点:
不要试图导入一个定义在当前pom子模块中的pom.视图这么作的话将会致使构建失败由于它无法定位pom.
永远不要声明pom导入一个pom做为它的父pom.无法处理循环并将致使抛出异常.
当引用了那些pom中有传递依赖的工件时,项目将须要为那些工件指定版本做为托管依赖.不这么作的话将会致使构建失败由于工件没有指定的版本.(在任何状况下这都应该被认为是一个最佳实践,由于它防止工件的版本从一个构建到下一个构建的变化)
系统依赖
范围指定为"system"的依赖老是可用的并且它们不会在仓库中查找.它们一般用来告诉Maven那些由JDK或者VM提供的依赖.所以,系统依赖对于解决那些如今由JDK提供,可是又能够提早独立下载的依赖是颇有用的.典型的示例是JDBC的标准扩展和JAAS.
简单的示例:
<project> ... <dependencies> <dependency> <groupId>javax.sql</groupId> <artifactId>jdbc-stdext</artifactId> <version>2.0</version> <scope>system</scope> <systemPath>${java.home}/lib/rt.jar</systemPath> </dependency> </dependencies> ... </project>
若是你的工件由JDK的tools.jar提供,系统路径应该被定义成这样:
<project> ... <dependencies> <dependency> <groupId>sun.jdk</groupId> <artifactId>tools</artifactId> <version>1.5.0</version> <scope>system</scope> <systemPath>${java.home}/../lib/tools.jar</systemPath> </dependency> </dependencies> ... </project>