- maven项目中需要使用到其它依赖时,则需要在pom.xml中配置<dependency>元素也就是依赖声明,这样在编写项目时就可以使用依赖了,并且会在打包时自动将依赖的jar包打包到项目中。
1.依赖声明
- 一个依赖声明可以包含如下的一些元素。
<project>
<dependencies>
...
<dependency>
<groupId>...</groupId>
<artifactId>...</artifactId>
<version>...</version>
<type>...</type>
<scope>...</scope>
<optional>...</optional>
<exclusions>
<exclusion>
...
</exclusion>
</exclusions>
</dependency>
...
</dependencies>
</project>
-
gourpId、artifactId、version:依赖的基本坐标,必须填写,maven根据这3个信息找到需要的依赖。
-
type:依赖的类型,大部分情况是jar,默认是jar。
-
scope :依赖的范围,后面详解。
-
optional: 标记依赖是否可选,值为true或false,默认为false, 如果为可选依赖,则依赖不具有传递性。即B->X(可选依赖),A->B。此时A的依赖中不包含X。
-
exclusions:用来排除传递性依赖。
大部分依赖声明只包含基本坐标,然而在一些特殊情况下,其他元素至关重要。
2.依赖范围
-
classpath:用于指定.class文件存放的位置,类加载器会从该路径中加载所需的.class文件到内存中。详细看类加载器
-
maven在编译项目主代码时需要使用一套classpath ,比如在使用spring框架的项目中,项目主代码需要用到spring-core依赖,scope为compile。该文件以依赖的方法被引入到classpath中。
-
maven在编译和执行测试代码时会使用另一套classpath。比如JUint,scope为test,该文件以依赖的方式引入到测试使用的calsspath中。
-
maven实际运行项目时又会使用一套classpath,上面的spring-core需要在该classpath中,而JUnit不需要。
maven依赖范围就是用来控制依赖与这三种classpath(编译classpath、测试classpath、运行classpath)的关系。
编译classpath:编译主代码有效
测试classpath:编译、运行测试代码有效
运行classpath:项目运行时有效
-
maven的依赖范围:
-
compile
编译依赖范围。(默认方式),有效范围:编译classpath+测试classpath+运行classpath
比如:spring-core,在编译测试运行阶段都需要使用。 -
test
测试依赖范围。有效范围:测试classpath
比如:JUnit,只在测试时使用,在编译主代码和运行时不需要此依赖。 -
provided
已提供依赖范围。有效范围:编译classpath+测试classpath。
比如:servlet-api。编译和测试项目时候需要该依赖,但在运行项目时,由于web容器已经提供,就不需要maven重复引入一遍了。 -
runtime
运行时依赖范围。有效范围:测试classpath+运行classpath
比如:JDBC驱动实现(mysql-connector-java),项目主代码编译时只需要JDK提供的JDBC接口,只有在执行测试或者运行项目时候才需要具体的JDBC驱动。 -
system
系统依赖范围。有效范围:编译classpath+测试classpath
使用system范围的依赖时必须通过systemPath元素显示地指定依赖文件的路径,因为此类依赖不是通过maven仓库解析的,而且往往与本地及其系统绑定,可能造成构建的不可移植,慎用。systemPath元素可以引用环境变量。
比如:
<dependency>
<groupId>javax.sql</groupId>
<artifactId>jdbc-stdext</artifactId>
<version>2.0</version>
<scope>system</scope>
<systemPath>${JAVA_HOME}/lib/rt.jar</systemPath>
</dependency>
3.依赖传递
- 一个maven项目A依赖spring-core依赖范围为compile,因为spring-core又依赖 commons-logging依赖范围为compile,那么commons-logging就会成为A的compile范围依赖,commons-logging就是A的一个传递性依赖。
假设A依赖与B,B依赖与C,则A对于B是第一直接依赖,B对于C是第二直接依赖,A对于C是传递性依赖。第一直接依赖的范围和第二直接依赖的范围决定了传递性依赖的范围。如下表,最左边是第一直接依赖范围,上面是第二直接依赖范围,中间交叉单元格表示传递性依赖范围。
第一直接依赖\第二直接依赖 | compile | test | provided | runtime |
---|---|---|---|---|
compile | compile | - | - | runtime |
test | test | - | - | test |
provided | provided | - | provided | provided |
runtime | runtime | - | - | runtime |
- 规律:
- 第二直接依赖范围是compile时,传递依赖的范围与第一直接依赖范围一致。
- 第二直接依赖范围是test时,依赖不会传递
- 第二直接依赖范围是provided时,只传递第一直接依赖为provided的依赖。
- 第二直接依赖范围是runtime时,传递依赖的范围与第一直接依赖范围一致,但是compile例外,此时传递依赖范围为runtime。
4.依赖优先
当C依赖A和B后,如果A和B的依赖中存在相同的依赖,那么C会依赖A中传递性依赖还是B中的传递性依赖呢?在maven中有两个原则来解决这一问题,第一原则解决不了才会使用第二原则。
C->A
C->B
- 第一原则:路径最近者优先
A和B对于E的依赖情况
A->M(1.2)->E(1.3)
B->E(1.5)
C对于E的依赖情况。
C->A,B =>C->E1.5
C->B,A =>C->E1.5
- 第二原则:第一声明者优先
A->D(1.2)
B->D(1.3)
C->A,B =>C->D(1.2)
C->B,A =>C->D(1.3)
5.可选依赖
-
假设A依赖B,B依赖X和Y,依赖范围都是compile,但是B对于X和Y的依赖都是可选依赖,此时X,Y对于A而言不会传递。
B->X(可选)
B->Y(可选)
A->B -
什么情况下使用可选依赖?如果一个项目实现了两个特性,其中一个特性依赖X,另一个依赖Y,而这两个特性是互斥的,用户不可能同时使用两个特性。
-
比如B是一个持久层隔离工具包,它支持多种数据库,包括mysql、PostgreSQL等,在构建这个工具包时需要这两种数据库驱动依赖,但是在A项目中使用这个工具包B时,A只会依赖一种数据库,因为mysql、PostgreSQL依赖不会传递到A,所以A要在单独配置一下依赖的 数据库。
6.排除依赖
传递性依赖会给项目引入很多依赖,简化项目依赖管理,但是也会带来问题
- 需求
- 比如当前项目有一个第三方依赖,而第三方依赖依赖了另一个类库的SNAPSHOT版本,那么这个SNAPSHOT就会成为当前项目的传递性依赖,而SNAPSHOT的不稳定性会直接影响到当前项目。这时候就应该排除掉SNAPSHOT。并且声明该类库的正式发布版本。
- 由于版权原因一些类库不在中央仓库中,你想要替换掉它。
- 当前项目需要依赖B(1.2),而当前项目又没有显示声明B(1.2),而是通过传递性依赖引入了B(1.0),此时就会出现问题,这时就需要来排除低版本的依赖或者显示声明。
- 想显示声明某些必要的依赖,而不使用传递依赖,这样更方便查看和管理,而且如果依靠传递依赖,当依赖升级后有可能传递性依赖就不存在了。
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>3.6.10.Final</version>
<exclusions>
<exclusion>
<groupId>slf4j-api</groupId>
<artifactId>slf4j-api</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.12</version>
</dependency>
- 上述代码中hibernate-validator依赖slf4j-api,但是当前项目不想使用传递过来的slf4j-api,所以通过exclusions声明排除依赖,exclusions可以包含多个exclusion元素。当前项目声明了自己需要的1.7.12版本的slf4j-api
7.归类依赖
- 需求
关于springframework的依赖有好多,org.springframework:spirng-core:2.5.6、org.springframework:sprng-beans:2.5.6、org.springframework:spring-context:2.5.6、org.springframework:spring-context-support:2.5.6,,他们来自同一个项目不同模块,因此版本都是相同的,可以预见在升级spring时这些依赖都会一起升级,为了方便统一所以使用properties元素定义maven属性。
如下:
<project>
....
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<springframework.version>4.3.2.RELEASE</springframework.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</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-core</artifactId>
<version>${springframework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>${springframework.version}</version>
</dependency>
</dependencies>
...
</project>
- 通过使用${springframework.version}替换掉实际值,将所有spring依赖的版本值都使用这一引用值表示。
8.优化依赖
- 需求
在开发过程中,程序员应该对程序的依赖有清楚的了解,知道程序中都会有哪些依赖,并且不断的优化,去掉多余的依赖,显示声明必要的依赖。
为此可以借助一些maven命令帮助我们更好的优化自己的依赖
8.1查看已解析依赖
-
maven会自动解析项目所有直接依赖和传递依赖,并根据规则和依赖范围确保任何一个构件只有唯一的版本在依赖中存在。在这些工作之后,最后得到的依赖被称为已解析依赖(Resolved Dependency)。
-
查看当前项目已解析依赖:mvn dependency:list
8.2 查看依赖树
-
在已解析依赖的信息上还能查看到彼此的依赖关系,将直接在Pom.xml声明的依赖定义为顶层依赖,而顶层依赖的依赖为第二层依赖,依次类推还有第三层第四层依赖。经过maven解析后会构成一个依赖树,通过树就可以查看某个依赖是通过哪条路径引入的。
-
查看命令:mvn dependency:tree
8.3分析依赖树
- 在依赖树基础上还可以分析当前项目的依赖,分析会得到两个结果:
- 一个是说明项目中使用到的,但是没有显示声明的依赖
- 一个是说明项目中未使用的,但显示声明的依赖(没使用是指没有在编译主代码和测试代码中使用到的依赖,所以有可能项目运行中会使用到,需要自己分析)
*分析命令:mvn dependency:analyze
分析依赖-
在执行 maven-dependency-plugin:2.8:analyze目标之前会执行依次执行如下任务
maven-resources-plugin:2.7:resources
maven-compiler-plugin:3.3:compile
maven-resources-plugin:2.7:testResources
maven-compiler-plugin:3.3:testCompile
即分析结果是依赖对主代码和测试代码进行编译后的到的。 -
结果分析
分析结果有如下两个部分: -
Used undeclared dependencies found:说明项目中使用到的,但是没有显示声明的依赖。
-
Unused declared dependencies found:一个是说明项目中未使用的,但显示声明的依赖,没使用是指没有在编译主代码和测试代码中使用到的依赖,所以有可能项目运行中会使用到,需要自己分析,所以对于该依赖不应直接删除,需要分析,比如mysql:mysql-connector-java:jar:5.1.37:compile依赖在测试和编译时候不会用到,但是在运行时会用到。
- 有什么不懂的一起探讨一下吧,我也是在学习的路上。喜欢给我点个赞吧(哈哈),我会继续努力的。
网友评论