依赖详解
<project>
...
<dependencies>
<dependency>
<groupId></groupId>
<artifactId></artifactId>
<version></version>
<type></type>
<scope></scope>
<optional></optional>
<exclusions>
<exclusion><exclusion>
...
</exclusions>
</dependency>
...
<dependencies>
</project>
groupId、artifactId、version依赖的基本坐标,对任何一个依赖来说坐标是最重要的,Maven根据坐标才能找到所需要的依赖
type是依赖的类型,对应项目坐标定义的packaging。大部分情况下该元素不必声明,默认为jar
scope依赖范围:
Maven在编译项目主代码的时候要用一套编译classpath,这个时候域范围为compile;在执行测试的时候会使用另外一套测试classpath,域范围为test;实际运行Maven项目的时候,又会使用一套运行classpath
- compile:编译依赖范围。不指定默认使用该依赖范围,对编译、测试、运行三种classpath都有效。典型例子就是spring-core,在编译、测试、运行时都需要用到。
- test :测试依赖范围。只对测试classpath有效。在编译主项目代码和运行项目时无法使用该类依赖。典型例子就是JUnit,只在编译测试代码和运行测试的时候才需要。
- provided:以提供依赖范围。对于编译和测试classpath有效。典型例子:servlet-api,编译和测试项目时需要引入该依赖,运行项目时,容器已经提供,就不需要Maven重复引入一遍
- runtime:运行时依赖。对于测试和运行classpath有效。典型例子:JDBC驱动实现,项目主代码的编译只需要JDK提供的JDBC接口,只有在执行测试或运行项目时才需要实现上述接口的具体JDBC驱动。
- system:系统依赖范围。和provided依赖范围完全一致。但是使用system依赖范围时必须要通过systemPath元素显式指定依赖文件的路径。此类依赖不是通过Maven仓库解析的,往往与本机系统绑定,可能造成构建的不可移植。systemPath元素可以引用环境变量,eg:
<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产生实际的影响(阅读至8.3.3节回来补充)
optional依赖是否可选。当前有四个项目的依赖关系A→B(项目A依赖于项目B),B→X(可选),B→Y(可选)。根据传递性依赖定义,若这三个依赖范围都是compile,那么X,Y也是A的compile范围传递性依赖。这里由于XY都是可选依赖,依赖就不会传递。即XY对A不会有任何影响。
-
为什么需要可选依赖?项目B实现了两个特性,特性一依赖于X,特性二依赖于Y,而且这两个特性是互斥的,用户不会同时使用这两个特性。比如B时一个持久层隔离工具包,支持多种数据库(MySQL,Oracle等),在构建这个工具包时,需要这两种数据库的驱动程序,但在使用这个工具包时,只会依赖一种数据库。
可选依赖.png - 不推荐使用可选依赖。
使用可选依赖的原因时一个项目实现了多个特性。在面向对象设计中,一个单一职责性原型,即一个类应该只有一项职责,而不是糅合太多的功能,在Maven项目中同样适用。我们看个例子。
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>com.play.myMaven</groupId>
<artifactId>project-b</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.34</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.oracle</groupId>
<artifactId>ojdbc14</artifactId>
<version>10.2.0.1.0</version>
<optional>true</optional>
</dependency>
</dependencies>
</project>
在这个例子中,它们只会对当前项目B产生影响,当其他项目依赖于B的时候,这两个依赖不会被传递。当项目A依赖于项目B时,如果是基于MySQL数据库,那么在项目A中需要显式的声明mysql-connector-java这一依赖:
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>com.play.myMaven</groupId>
<artifactId>project-a</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>com.play.myMaven</groupId>
<artifactId>project-b</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.34</version>
</dependency>
</dependencies>
</project>
更好的做法是为MySQL/Oracle分别创建一个Maven项目,基于相同的groupId,分配不同的artifactId,在各自的POM中声明对应的JDBC驱动依赖,不使用可选依赖,用户根据需要选择使用对应artifactId。
exclusions用来排除传递性依赖。传递性依赖会隐式地引入很多依赖。对于一些隐式的不稳定版本依赖和需要替换的传递性依赖,都需要手动排除。例如
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>com.play.myMaven</groupId>
<artifactId>project-a</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>com.play.myMaven</groupId>
<artifactId>project-b</artifactId>
<version>1.0-SNAPSHOT</version>
<exclusions>
<exclusion>
<groupId>com.play.myMaven</groupId>
<artifactId>project-c</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.play.myMaven</groupId>
<artifactId>project-c</artifactId>
<version>1.10</version>
</dependency>
</dependencies>
</project>
排除依赖.png
项目A对项目C版本1.10的传递依赖,同时排除了B的传递依赖项目C
依赖调解
例如:项目A有这样的依赖关系:A→B→C→X(1,0)、A→D→X(2,0),X是A的传递性依赖,但是两条依赖路径上有两个版本的X,哪个X会被Maven解析?
Maven依赖调解的第一原则:路径最近者优先。X(2,0)路径为2,X(1,0)路径为3,X(2,0)被解析。
但如果是:A→B→Y(1,0)、A→C→Y(2,0),Y(1,0)和Y(2,0)依赖长度相同时,怎么解析呢?
Maven依赖调解的第一原则:第一声明者优先。在POM中依赖声明的顺序决定了谁会被先解析使用。
归类依赖
例如很多关于Spring Framework的依赖,为了避免重复书写同一项目的不同模块的版本,同时可以方便查看个构件版本,我们使用Maven属性归类依赖。
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>com.play.myMaven</groupId>
<artifactId>project-a</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<springframework.version>5.0.5.RELEASE</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-context</artifactId>
<version>${springframework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>${springframework.version}</version>
</dependency>
</dependencies>
</project>
通过在properties中定义一个xxx.version子元素,用${xxx.version}来引用这一属性值。
优化依赖
对依赖进行优化:去除多余的依赖,显式地声明某些必要依赖。
Maven可以自动解析所有项目的直接依赖和间接依赖,并根据规则正确判断每个依赖的范围,对于一些依赖冲突,也能进行调解,以确保任何一个构件只有唯一的版本在依赖中存在。在这些之后,最后得到的那些依赖被称为已解析依赖。查看当前项目的已解析依赖,可以运行mvn dependency:list
[INFO] The following files have been resolved:
[INFO] org.springframework:spring-web:jar:4.3.7.RELEASE:compile
[INFO] org.springframework.boot:spring-boot-test:jar:1.5.2.RELEASE:test
[INFO] com.fasterxml.jackson.core:jackson-annotations:jar:2.8.0:compile
[INFO] org.hamcrest:hamcrest-library:jar:1.3:test
[INFO] org.mockito:mockito-core:jar:1.10.19:test
[INFO] org.springframework:spring-test:jar:4.3.7.RELEASE:test
[INFO] org.slf4j:jul-to-slf4j:jar:1.7.24:compile
[INFO] org.assertj:assertj-core:jar:2.6.0:test
[INFO] org.springframework:spring-expression:jar:4.3.7.RELEASE:compile
[INFO] com.icegreen:greenmail:jar:1.3.1b:test
[INFO] org.skyscreamer:jsonassert:jar:1.4.0:test
...
[INFO] org.springframework.boot:spring-boot:jar:1.5.2.RELEASE:compile
[INFO] net.minidev:accessors-smart:jar:1.1:test
[INFO] org.springframework:spring-context-support:jar:4.3.7.RELEASE:compile
[INFO] com.fasterxml:classmate:jar:1.3.3:compile
[INFO] ch.qos.logback:logback-classic:jar:1.1.11:compile
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
想要看的更清晰,可以以依赖树的方式,看到某个依赖是通过哪条路径引入的。运行:mvn dependency:tree
[INFO] Scanning for projects...
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] Building parent 1.0-SNAPSHOT
[INFO] ------------------------------------------------------------------------
[INFO]
[INFO] --- maven-dependency-plugin:2.10:tree (default-cli) @ hello-maven ---
[INFO] com.play.myMaven:hello-maven:jar:1.0-SNAPSHOT
[INFO] +- org.springframework:spring-core:jar:5.0.5.RELEASE:compile
[INFO] | \- org.springframework:spring-jcl:jar:5.0.5.RELEASE:compile
[INFO] +- org.springframework:spring-context:jar:5.0.5.RELEASE:compile
[INFO] | +- org.springframework:spring-aop:jar:4.3.7.RELEASE:compile
[INFO] | +- org.springframework:spring-beans:jar:4.3.7.RELEASE:compile
[INFO] | \- org.springframework:spring-expression:jar:4.3.7.RELEASE:compile
[INFO] +- org.springframework:spring-context-support:jar:5.0.5.RELEASE:compile
[INFO] +- mysql:mysql-connector-java:jar:5.1.34:compile
[INFO] +- com.oracle:ojdbc14:jar:10.2.0.1.0:compile
[INFO] +- junit:junit:jar:4.11:test
[INFO] | \- org.hamcrest:hamcrest-core:jar:1.3:test
[INFO] +- javax.mail:mail:jar:1.4.1:compile
[INFO] | \- javax.activation:activation:jar:1.1:compile
[INFO] +- com.icegreen:greenmail:jar:1.3.1b:test
[INFO] | \- org.slf4j:slf4j-api:jar:1.7.24:compile
...
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
除此,我们还可以通过mvn dependency:analyze分析当前项目的依赖
[INFO] --- maven-dependency-plugin:2.10:analyze (default-cli) @ hello-maven ---
[WARNING] Used undeclared dependencies found:
[WARNING] org.springframework.boot:spring-boot:jar:1.5.2.RELEASE:compile
[WARNING] org.springframework:spring-beans:jar:4.3.7.RELEASE:compile
[WARNING] org.springframework.boot:spring-boot-autoconfigure:jar:1.5.2.RELEASE:compile
[WARNING] Unused declared dependencies found:
[WARNING] org.springframework.boot:spring-boot-starter-test:jar:1.5.2.RELEASE:test
[WARNING] org.springframework.boot:spring-boot-starter-web:jar:1.5.2.RELEASE:compile
[WARNING] com.oracle:ojdbc14:jar:10.2.0.1.0:compile
[WARNING] mysql:mysql-connector-java:jar:5.1.34:compile
[WARNING] org.springframework.boot:spring-boot-configuration-processor:jar:1.5.2.RELEASE:compile
[WARNING] org.springframework:spring-core:jar:5.0.5.RELEASE:compile
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
- Used undeclared dependencies found:被用到但未被声明的依赖。这种依赖存在潜在的风险。当升级直接依赖时,相关传递性依赖的版本也可能发生变化,这种变化不易察觉,有可能导致当前项目出错。例如接口的改变,当前项目的相关代码无法编译。因此,我们需要显式声明任何项目中直接用到的依赖。
- Unused declared dependencies found:未被使用的显式声明的依赖。对于这种依赖,我们不能简简单单的删除声明,因为dependency:analyze只会分析编译主代码和测试代码需要用到的依赖,一些执行测试和运行时需要的依赖是发现不了的。这个操作确实可以找到一些没用的依赖,但是需要小心测试。
注:《Maven实战》学习笔记
网友评论