依赖结构
之前提到可以用maven 命令查看项目的依赖结构,比如输入一下命令
mvn dependency:tree
maven 就把我们项目依赖结果输出成树的结构。
[INFO] +- thirdparty_tools:Spring:pom:3.0.5_full:compile
[INFO] | +- thirdparty_lib:org.springframework.aop:jar:3.0.5_full:compile
[INFO] | +- thirdparty_lib:org.springframework.asm:jar:3.0.5_full:compile
[INFO] | +- thirdparty_lib:org.springframework.aspects:jar:3.0.5_full:compile
声明
上面的输出涉及到几个概念。下面简单说下。一般情况下我们会在 pom.xml 中设置下面类似的依赖。
<dependency>
<groupId>thirdparty_lib</groupId>
<artifactId>commons-jexl</artifactId>
<version>2.1.1</version>
<type>jar</type>
<scope>compile</scope>
<exclusions>
<exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
groupId、artifactId 和 version 很简单啦,就是一个jar包的坐标,maven就是根据这个信息在本地仓库或远程仓库中找到指定的jar包的。
type : 依赖类型。大部分情况下不需要这个声明,默认jar。
scope : 依赖范围。
exclusions : 用来排除传递依赖。
其实,大部分依赖我们只需要声明坐标即可。
下面说下几个概念。
classpath
相信很多人对这个不默认吧。用于告诉Java执行环境,在哪些目录下可以找到您所要执行的Java程序所需要的类或者包。简单的来说就是一些包和类的集合。
其实maven在帮我们完成工作时,不同时期的classpath有所不同。
1. Maven在编译项目主代码的时候需要使用一套 classpath。
2. 之后 Maven在编译和执行测试的时候会使用另外一套 classpath。
3. 最后实际运行 Maven项目的时候,又会使用一套 classpath.
也就是说我们在使用Maven时,一共会涉及三套 classpath。
依赖范围
上面 dependency 里面 scope 的声明就是指名 依赖范围。而依赖范围就是用来控制依赖的当前jar包与这三种 classpath (编译classpath,测试 classpath,运行classpath) 的关系。
依赖范围有如下几种
compile:编译依赖范围。默认使用。对于三种classpath都有效。spring-core
test:测试依赖范围。只对 测试classpath有效。编译代码或运行时无法使用此类依赖。JUnit。编译测试代码和运行测试的时候需要。
provided:已提供依赖范围。即运行环境所在容器已经提供。可知 对于 编译和测试classpath有效。运行时无效。 servlet-api.
runtime:运行时依赖。该范围的依赖对于测试运行classpath有效,对于编译主代码无效。 JDBC驱动。我们知道编译代码只需要JDK的JDBC接口即可。而JDBC驱动是上述接口的实现。所以执行测试或运行项目时才会需要。
system:系统依赖范围。其支持的classpath与provided依赖范围一致。只是不通过maven仓库解析。而是通过systemPath 元素显示指定依赖文件的路径。因此为了系统构建可以多处运行,不建议使用此依赖范围。
<dependency>
<groupId>javax.sql</groupId>
<artifactId>jdbc-stdext</artifactId>
<version>2.0</version>
<scope>system</scope>
<optional></optional>
<systemPath>${java.home}/lib/rt.jar</systemPath>
</dependency>
综合上述,依赖范围与 classpath 的关系如下:
依赖范围 | 对编译classpath有效 | 对测试classpath有效 | 对运行时classpath有效 | 举例 |
compile | Y | Y | Y | spring-core |
test | Y | JUnit | ||
provided | Y | Y | servlet-api | |
runtime | Y | Y | JDBC驱动的实现,如oracle_jdbc.jar | |
system | Y | Y | 本地环境变量中的jar |
传递性依赖
我们知道,很多jar包都是有依赖的,比如 A 依赖 B,而B依赖 C,也就是我们所说的A 间接依赖 C。即 A => B => C。此时 C就是 A的一个传递性依赖。在项目中,我们不需要考虑传递性依赖,maven的传递性依赖会解析我们的间接依赖,并将它们引入我们的项目。这里说下,依赖范围对传递性依赖是有影响的。当依赖关系是 A => B => C 时,我们假定 A => B 为第一依赖,B => C为第二依赖。这里将它们列成表格,第一列是第一依赖,第一行是第二依赖。因此就会组合成如下传递性依赖范围的范围。
compile | test | provided | runtime | |
compile | compile | runtime | ||
test | test | test | ||
provided | provided | provided | provided | |
runtime | runtime | runtime |
下面举个例子,下面是两个Project,mavenprjA 和 mavenprjB。各自 pom.xml如下
mavenprjA
<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/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com</groupId>
<artifactId>mavenprjA</artifactId>
<packaging>jar</packaging>
<version>0.0.1-SNAPSHOT</version>
<name>my Maven Webapp</name>
<url>http://maven.apache.org</url>
<dependencies>
<dependency>
<groupId>com</groupId>
<artifactId>mavenprjB</artifactId>
<version>0.0.1-SNAPSHOT</version>
<scope>test</scope><!------------------A ---------------->
</dependency>
<!-- Test start -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<!-- Test end -->
</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/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com</groupId>
<artifactId>mavenprjB</artifactId>
<packaging>jar</packaging>
<version>0.0.1-SNAPSHOT</version>
<name>my Maven Webapp</name>
<url>http://maven.apache.org</url>
<dependencies>
<!-- Util -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.4</version>
<scope>test</scope><!------------------B ---------------->
</dependency>
</dependencies>
</project>
从上面可以发现, mavenprjA => mavenprjB, 而 mavenprjB => org.apache.commons.commons-lang3, 依赖范围都是 test。 查看 mavenprjA 的依赖树。 从下面输出看,当我们设置第一依赖scope 为test 时, mavenprjA的依赖树中 ,mavenprjB下是 没有commons-langs 的 ,即是不传递的。
[INFO] ------------------------------------------------------------------------
[INFO] Building my Maven Webapp 0.0.1-SNAPSHOT
[INFO] ------------------------------------------------------------------------
[INFO]
[INFO] --- maven-dependency-plugin:2.1:tree (default-cli) @ mavenprjA ---
[INFO] com:mavenprjA:jar:0.0.1-SNAPSHOT
[INFO] +- com:mavenprjB:jar:0.0.1-SNAPSHOT:test
[INFO] \- junit:junit:jar:4.12:test
[INFO] \- org.hamcrest:hamcrest-core:jar:1.3:test
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
然后我们不停地改变A、B 两处依赖的 scope,然后查看依赖树。就可以验证了。