做了开发几年了,项目中Maven没少用,却几乎没有深入了解过,一直把它作为管理各种依赖jar包的工具,平时偶尔有jar包冲突,也是百度解决。今天就好好理一理pom.xml里的一些标签如scope和option相关的作用。
本文的内容更多的是参考了《Maven实战》这本书,所以有些内容或例子会以此书为模板。
这个scope在书中的解释为依赖范围,大意是说在每段代码在编译,运行,以及测试时都会有一套自己的classpath,而每个依赖的scope就是指此依赖在每一个classpath下是否会起作用或者说是否可以被其它代码可见和调用。
在spring框架中,scope一般表示此bean的作用域。maven这里我更愿意称它为依赖(jar包)的作用域。比如说,如果某个jar包的scope为test类型,那么此依赖仅仅可以在Junit测试用例中可以编译运行。但在其它的classpath下不可见。
下面就详细说说每一种scope对应的classpath的作用情况吧。
compile表示编译环境作用域。一般我们不指定scope默认就是此种作用域。此作用域为强依赖,也就是说只要pom文件中的某个依赖指定此作用域,那么代码中就一定可见且可以运行(特殊:jar包冲突会覆盖掉其中一个jar)。上面说过三种classpath,compile作用域的依赖在这三种classpath下都是可见的。
用spring-context依赖举个例子,看一看。
pom.xml
<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.0modelVersion>
<groupId>com.binghuazheng.mavengroupId>
<artifactId>mavendemoartifactId>
<version>1.0.0-SNAPSHOTversion>
<dependencies>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-contextartifactId>
<version>5.1.2.RELEASEversion>
<scope>compilescope>
dependency>
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
<version>4.12version>
dependency>
dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.pluginsgroupId>
<artifactId>maven-compiler-pluginartifactId>
<configuration>
<source>1.8source>
<target>1.8target>
configuration>
plugin>
plugins>
build>
project>
执行结果
test作用域,它的作用如同名字,依赖仅可以在Junit测试用例中编译执行,无法在正常的java目录中编译和运行。
将spring-context的作用域改为test类型。
<dependencies>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-contextartifactId>
<version>5.1.2.RELEASEversion>
<scope>testscope>
dependency>
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
<version>4.12version>
dependency>
dependencies>
下面的Demo.java是我们正常的src/main目录下的类,可以看到,此时编译都过不去,classpath下找不到spring-context相关的jar,但是Junit测试用例中是可以编译运行的。
表示已提供类型的jar包作用域,看起来有点迷惑。此作用域的jar包在src/main下的代码编译阶段以及测试用例阶段这两个classpath下是可见的,但在运行期不可见。通俗点说,就是此jar包在我们的代码中只可远观而不可亵玩,只能看不能摸。专业点说,就是代码只能编译不报错,但运行时找不到此jar。
好,将我们的spring-context的jar包作用域改为provided。
<dependencies>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-contextartifactId>
<version>5.1.2.RELEASEversion>
<scope>providedscope>
dependency>
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
<version>4.12version>
dependency>
dependencies>
然后在运行Demo.java看一看效果。
这里相对于之前的test作用域来讲,首先编译是不报错了,但运行时报了ClassNotFound异常,也就是说运行期的classpath找不到此jar包。
运行期的作用域,此作用域的jar包,只能在代码运行时和测试用例中这两个classpath下可见。在编译期是找不到此jar包的。
<dependencies>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-contextartifactId>
<version>5.1.2.RELEASEversion>
<scope>runtimescope>
dependency>
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
<version>4.12version>
dependency>
dependencies>
再看一看Demo.java,已经变得编译不通过了。这里编译过不去,即使运行期jar包可见,暂时没办法运行了。
系统级别的作用域,它需要和本地主机的环境变量绑定,此作用域用的很少。至少目前为止,我还没有在pom.xml内见过此级别的作用域,这里就不示例了。
import和上边的作用域其实没什么关系。它一般是和dependencyManagement关联使用的。当我们的工程依赖一个pom工程时,就需要指定scope为import。如果你自己搭建Spring Cloud的Demo工程,就会接触到Import。这里我们直接举个例子验证一下。
首先是我们的父工程,一般作为项目依赖管理,它的打包方式是pom,指定依赖版本–Finchley.SR2。
<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.0modelVersion>
<groupId>com.binghuazheng.mavengroupId>
<artifactId>mavendemoartifactId>
<packaging>pompackaging>
<version>1.0.0-SNAPSHOTversion>
<modules>
<module>child-demomodule>
modules>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-dependenciesartifactId>
<version>Finchley.SR2version>
<type>pomtype>
<scope>importscope>
dependency>
dependencies>
dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.pluginsgroupId>
<artifactId>maven-compiler-pluginartifactId>
<configuration>
<source>1.8source>
<target>1.8target>
configuration>
plugin>
plugins>
build>
project>
然后在看看它的子工程,子工程就可以依赖具体的jar了。
<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">
<parent>
<artifactId>mavendemoartifactId>
<groupId>com.binghuazheng.mavengroupId>
<version>1.0.0-SNAPSHOTversion>
parent>
<modelVersion>4.0.0modelVersion>
<artifactId>child-demoartifactId>
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-netflix-eureka-serverartifactId>
dependency>
dependencies>
project>
可以看到,这里它就将eureka-server相关的依赖引进来了,版本就是它的父工程的版本Finchley.SR2。
这里要注意,父工程的scope必须为import,且type必须为pom。否则子工程是引不到相关依赖的。
最后贴上《maven实战》一书中关于依赖范围和对应的classpath之间的关系,方便记忆。
实际开发的项目中,各种依赖相互之间的关系很混乱。不同的依赖范围会导致不同的结果,看一下下面简单的例子。
这里A工程依赖了B工程,依赖范围是test;而B工程内部又依赖了C工程,依赖范围是compile,那A工程也会将C工程导入进来,那C工程在A工程的classpath下的依赖范围Scope是什么呢?
先说下结果吧,C在A的classpath下是test作用域。
下面我们就研究一下关于不同依赖范围对依赖传递的影响。
在《Maven实战》这本书中,关于依赖传递有个专有名词—直接依赖。以上边demo为例,A直接依赖了B,A就是B的第一直接依赖;B依赖了C,B就是C的第二直接依赖;而C相对于A来说,它就是传递性的依赖。
上边几句话,用大白话解释。A通过B,进而拥有了C。A能不能使用B,取决于A中对B的scope。B能不能使用C,取决于B中对C的scope。那A能不能使用C,就取决于这其中两个scope的关系了。它们之间的判断关系如下图所示。
用书中的解释对照上图关系,A对B的scope,就是第一列的关系;而B对C的scope,就是第一行的关系;最后A对C的scope就是内部具体对应的位置所示。横线表示依赖无法传递。
以上边A—B---C来说,A对C就是test传递范围。C只能在A的测试用例中编译运行,其它classpath下无法使用。
实际用代码验证看一看结果。
A工程依赖了B工程,这里B的scope为test。
pom.xml
<dependencies>
<dependency>
<groupId>com.binghuazheng.mavengroupId>
<artifactId>dependency-demoartifactId>
<version>1.0.0-SNAPSHOTversion>
<scope>testscope>
dependency>
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
<version>4.12version>
dependency>
dependencies>
B工程依赖了C工程,这里C的scope为compile。
<dependencies>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-contextartifactId>
<version>5.2.4.RELEASEversion>
<scope>compilescope>
dependency>
dependencies>
回到A的代码里,首先看一看src/main/java里是否可用。
编译错误,test作用域的jar,编译和运行classpath是看不见的,只能在测试classpath下可用,看一看Junit下是否可用。
我们再测试一下,A中B的scope为provided,B中C的scope为runtime。那么A中C的Scope是否如传递依赖关系图中所标注的作用域provided?
分别修改对应的pom文件中的scope。然后看一看结果,记得要重新发布B工程的jar包。
回到src/main/java中的Demo类中,发现它已经可以正常编译,但运行时就会报异常,找不到指定的class,符合provided的预期。
好了,关于maven中依赖传递范围和传递性依赖的范围就到这里。平时的开发,可能遇到关于maven的bug不多,但一旦遇到了,如果没有相关的知识储备,基本就只能靠百度了,但这里的bug一般不会在百度找到一模一样的,或多或少都会关联一点具体的业务,所以还是有必要了解一下的。