maven快速入门第五讲——依赖的作用范围
引子
在前一讲中,使用maven里面带有的tomcat7插件运行maven项目成功后,在Google Chrome浏览器访问HelloServlet时,发现报如下错误。
为什么会报错呢?这是因为我们本地tomcat服务器的lib目录中已经存在servlet-api.jar和jsp-api.jar这俩jar包了。
而我们的maven项目又再次引入了这俩jar包。
这样,当把咱们的maven项目部署到本地tomcat服务器上去时,就会存在这俩jar包冲突的问题,所以才会报错。即使现在使用的是maven里面带有的tomcat7插件运行maven项目,那也会报相同的错误。
要想解决该问题,就不得不知道依赖的作用范围了。下面我将会花大量的篇幅来介绍它。
依赖管理
依赖配置
在maven中,是在pom.xml文件中完成依赖的配置的,我们先来看看依赖配置的语法。
<project>
...
<!-- 添加依赖 -->
</dependencies>
<dependency>
<groupId>...</groupId>
<artifactId>...</artifactId>
<version>...</version>
<type>...</type>
<scope>...</scope>
<optional>...</optional>
<exclusions>
<exclusion>
...
</exclusion>
</exclusions>
</dependency>
...
</dependencies>
...
<project>
乍一看,这个配置还是蛮复杂的,其实我们常用的没有这么多,而且这些用起来也是非常简单的。在pom.xml文件中,根元素project下的dependencies标签中可以包含一个或者多个dependency元素,以声明一个或者多个项目依赖。每个依赖dependency标签中都应该包含以下元素:
很多时候,大部分依赖声明只包含groupId、artifactId和version这三个指定基本坐标的元素;而在一些特殊情况下,其它元素至关重要,也就是上面提到的scope、optional和exclusions。下面我只对scope这个元素进行详细的介绍。
依赖范围
依赖范围是什么?
我们需要知道,maven在编译项目主代码的时候需要使用一套classpath。举例来说:
所以,依赖范围就是用来控制依赖与这三种classpath(编译classpath、测试classpath、运行classpath)的关系的。
依赖范围具体有哪些?
scope(依赖范围)共有五种,它们分别是compile、provided、runtime、test、system。如果你记不起来这五种依赖范围也没关系,借助eclipse这个IDE就能知晓了,按照下图所示的步骤来操作即可。
下面我会一一详细地介绍每一种依赖范围。
compile
编译依赖范围。如果没有指定scope值,那么就会默认使用该依赖。使用该依赖范围的maven依赖,对于编译、测试、运行这三种classpath都有效。
就以前一讲中的maven项目来说,由于没有显示指定scope值,所以下面这两个依赖都是使用的编译依赖范围。
如果将该maven项目部署到本地tomcat服务器中,并启动本地tomcat服务器,那么就能在本地tomcat服务器的webapps目录下看到该maven项目了。
而且还会发现该maven项目的WEB-INF\lib目录下有如下3个jar包。
而我们知道本地tomcat服务器的lib目录中已经存在servlet-api.jar和jsp-api.jar这俩jar包了,所以这个时候就会发生jar包冲突的情况。只要在Google Chrome浏览器中访问HelloServlet,就会报一开始出现的错误。
provided
已提供依赖范围。使用此依赖范围的maven依赖,对于编译和测试classpath有效,但在运行时无效。最典型的例子是servlet-api,编译和测试项目的时候需要该依赖,但在运行项目的时候,由于tomcat容器已经提供,所以就不需要maven重复地引入一遍了。
还是以前一讲中的maven项目来说,现在我们将下面两个依赖的scope置为provided。也有两种方式来完成这一操作,第一种方式是手动在每一个依赖中添加<scope>provided</scope>
。
<!-- 添加依赖 -->
<dependencies>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jsp-api</artifactId>
<version>2.0</version>
<scope>provided</scope>
</dependency>
</dependencies>
第二种方式是直接利用eclipse就可以很方便地为每一个依赖添加<scope>provided</scope>
了,大家按照下图所示的步骤进行操作即可。
点击OK按钮,再保存一下pom.xml文件,这时你会发现pom.xml文件变成了下面这个样子。
再将该maven项目部署到本地tomcat服务器中,并启动本地tomcat服务器,虽然你会发现本地tomcat服务器的webapps目录下还是会有该maven项目,但是该maven项目的WEB-INF目录下直接没有lib目录了。
这就说明没有jar包冲突的情况发生了。这时,在Google Chrome浏览器中访问HelloServlet,你就会看到如下图所示的效果了。
就算是你现在使用maven里面带有的tomcat7插件来运行maven项目,然后同样在Google Chrome浏览器中访问HelloServlet,你依然会看到同样的结果,报错是不可能的了。
runtime
运行时依赖范围。使用此依赖范围的maven依赖,对于测试和运行classpath有效,但在编译主代码时无效。最典型的例子就是JDBC驱动实现,项目主代码的编译只需要JDK提供的JDBC接口,只有在执行测试或者运行项目的时候才需要实现上述接口的具体JDBC驱动。
test
测试依赖范围。使用此依赖范围的maven依赖,只对于测试classpath有效,在编译主代码或者运行项目时都无法使用此依赖。最典型的例子就是JUnit,它只有在编译测试代码及运行测试用例的时候才需要。也就是说,在编译和测试时是需要的,但在运行时不需要,因为在测试的时候,我们是用来跑测试用例的,而将来把项目打成jar包或者war包,丢到服务器上去,还要跑测试用例吗?肯定是不需要的。
system
系统依赖范围。该依赖与三种classpath的关系和provided依赖范围完全一致。但是,使用system范围的依赖时必须通过systemPath元素显式地指定依赖文件的路径。由于此类依赖不是通过maven仓库解析的,而且往往与本机系统绑定,可能造成构建的不可移植,因此谨慎使用。system的使用例子如下:
<dependencies>
<dependency>
<groupId>com.jellythink.BookStore</groupId>
<artifactId>BookStore-SSO</artifactId>
<version>1.0</version>
<scope>system</scope>
<systemPath>${basedir}/lib/BookStore-SSO-1.0.jar</systemPath>
</dependency>
</dependencies>
对于system系统依赖范围,在进行以上配置以后,编写代码时已经可以引入jar包中的class了,但是在打包时,由于scope为system,默认并不会将依赖包打进war包中,所以需要通过插件进行打包。例如:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<version>2.10</version>
<executions>
<execution>
<id>copy-dependencies</id>
<phase>compile</phase>
<goals>
<goal>copy-dependencies</goal>
</goals>
<configuration>
<outputDirectory>${project.build.directory}/${project.build.finalName}/WEB-INF/lib</outputDirectory>
<includeScope>system</includeScope>
</configuration>
</execution>
</executions>
</plugin>
总结
为了更好的理解和记忆依赖范围与classpath的关系,可以将上述内容总结成一张表格。