在Java项目开发中,Maven是我们最常用的依赖管理和构建工具了!我们常常通过添加dependency
节点,就能够很方便地加入依赖,而不需要我们自己去手动下载jar
文件并引入。
今天主要是来总结一下在Maven中依赖的作用域和传递。
1,依赖作用域
通过在每个dependency
中设定scope
字段,即可声明其作用域,例如:
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.28</version>
<!-- 声明作用域 -->
<scope>provided</scope>
</dependency>
上面我们就设定了lombok
这个依赖的作用域为provided
。
常用的作用域字段值及其意义如下:
compile
这是默认的scope
(即你不写scope
字段的话,这个依赖作用域就是compile
),表示依赖在编译、测试和运行时都是可用的,并且会参与项目的打包过程,该依赖会传递给依赖该模块的其他模块provided
表示依赖在编译和测试时是可用的,但该依赖不会参与程序运行阶段,即程序运行时无法调用该依赖中的类,它不会参与项目的打包过程,也不会传递给其他模块runtime
表示依赖仅在运行时是可用的,但在编译和测试时不需要,它会传递给依赖该模块的其他模块,但不会参与项目的打包过程test
表示依赖只在测试和运行时使用,不会参与项目的打包过程,也不会传递给其他模块
可见一个Maven项目,从编译到运行会经历三个阶段:编译 → 测试 → 运行
不同作用域在三个阶段的可见性如下表:
编译时可用 | 测试时可用 | 运行时可用 | |
---|---|---|---|
compile |
√ | √ | √ |
provided |
√ | √ | x |
runtime |
x | √ | √ |
test |
x | √ | x |
2,作用域和打包
我们常常会使用maven-assembly-plugin
插件,让我们在打包的时候将所有的依赖都打包至最后的jar
文件中,使得jar
文件可以直接运行。
不过真的是所有的依赖都会被打包到最后的jar
中吗?
其实并不是,在使用maven-assembly-plugin
插件插件时,默认只有scope
为compile
和runtime
的依赖才会被包含在最终的结果中。
因此,为了减小最终jar
的大小,我们应当将运行时不需要的依赖设置为provided
或者test
,当然这也是根据用途选择。
例如lombok
依赖会在编译的时候生成getter
和setter
的代码,但是运行的时候这个依赖就不需要了,因此它常常被设定为provided
。
但是在Spring Boot开发中就不一样了,Spring Boot工程中,使用的是spring-boot-maven-plugin
,这个插件也能完成同样的目的,即打包时将所有的依赖全部打包到一个jar
中,但是这个插件会将所有的依赖都打包进去,无论其scope
是什么。
不过,我们可以在这个插件中进行配置,声明打包时需要排除的依赖,例如:
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<!-- 打包时排除lombok依赖 -->
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
3,依赖的传递
事实上,在Maven中,依赖也是会传递的,我们先创建一个名为first
的项目,并引入lombok
依赖如下:
<?xml version="1.0" encoding="UTF-8"?>
<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.0</modelVersion>
<groupId>com.gitee.swsk33</groupId>
<artifactId>first</artifactId>
<version>1.0.0</version>
<properties>
<java.version>17</java.version>
<maven.compiler.source>${java.version}</maven.compiler.source>
<maven.compiler.target>${java.version}</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<!-- 引入lombok依赖 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.28</version>
</dependency>
</dependencies>
</project>
这是项目first
的pom.xml
文件,其groupId
为com.gitee.swsk33
,其artifactId
为first
,其版本为1.0.0
,现在在其工程目录下执行mvn clean install
安装至本地仓库使得待会可以引用它。
现在在新建项目second
,并引用上述的first
作为依赖,如下:
<?xml version="1.0" encoding="UTF-8"?>
<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.0</modelVersion>
<groupId>com.gitee.swsk33</groupId>
<artifactId>second</artifactId>
<version>1.0.0</version>
<properties>
<java.version>17</java.version>
<maven.compiler.source>${java.version}</maven.compiler.source>
<maven.compiler.target>${java.version}</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<!-- 引用first项目 -->
<dependency>
<groupId>com.gitee.swsk33</groupId>
<artifactId>first</artifactId>
<version>1.0.0</version>
</dependency>
</dependencies>
</project>
好的,现在展开IDEA左侧栏的外部库部分,看看second
项目的依赖:
可见second
项目仅仅是引入了first
项目,但为什么外部库中包含了lombok
依赖呢?这是因为first
依赖lombok
,而second
依赖first
时,lombok
也被传递给了second
。
同样地,如果现在又有一个项目third
依赖second
呢?那么third
也会间接依赖lombok
,也可以使用lombok
中的类。
当然,依赖并不总是会传递的,有下列因素会影响依赖传递。
(1) scope
作用域
scope
不仅仅代表这个依赖的作用域,也会影响依赖的传递,只有scope
为compile
和runtime
的依赖是会传递的。
假设现在把上述first
项目中的lombok
依赖scope
改成provided
或者test
,然后重新执行mvn clean install
,你就会发现在second
的依赖中,就看不到lombok
了!
(2) optional
字段
除了scope
之外,还可以设定依赖的optional
字段,当设定为true
时代表这个依赖是可选的,那么这时无论其scope
是什么,这个依赖都不会传递。默认情况下,即不声明依赖的optional
字段时,它的值是false
。
现在将first
中的lombok
依赖的optional
字段声明为true
:
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.28</version>
<optional>true</optional>
</dependency>
然后重新mvn clean install
,再次打开second
项目,你就会发现lombok
依赖就没有传递过来了!
所以如果在制作外部库需要其他人引用的时候,我们可以将一些仅仅是外部库需要使用但是其它项目不一定要使用的依赖的optional
设定为true
,避免其他开发者引入你的外部库时发生依赖冲突。
4,optional
带来的坑
对于optional
虽然可以阻止依赖传递,但是使用时也需要注意,先来看一个例子。
现在仍然是创建一个名为first
的项目,其groupId
为com.gitee.swsk33
,artifactId
为first
,版本为1.0.0
,只引入hutool
依赖并设定optional
为true
,整个项目pom.xml
如下:
<?xml version="1.0" encoding="UTF-8"?>
<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.0</modelVersion>
<groupId>com.gitee.swsk33</groupId>
<artifactId>first</artifactId>
<version>1.0.0</version>
<properties>
<java.version>17</java.version>
<maven.compiler.source>${java.version}</maven.compiler.source>
<maven.compiler.target>${java.version}</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<!-- Hutool实用工具 -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.16</version>
<optional>true</optional>
</dependency>
</dependencies>
</project>
然后创建一个类Test
并调用hutool
依赖中的类:
package com.gitee.swsk33.first;
import cn.hutool.core.io.FileUtil;
import java.io.File;
public class Test {
public static void ls() {
// 调用Hutool包中的FileUtil类
File[] files = FileUtil.ls("E:\\中转");
for (File file : files) {
System.out.println(file.getName());
}
}
}
好的,现在在这个first
项目目录下执行mvn clean install
,以便后续别的项目引用。
现在再创建名为second
的项目,依赖只引用上述first
如下:
<!-- 引用first -->
<dependency>
<groupId>com.gitee.swsk33</groupId>
<artifactId>first</artifactId>
<version>1.0.0</version>
</dependency>
并在主类中调用first
的Test
类中的方法如下:
import com.gitee.swsk33.first.Test;
public class Main {
public static void main(String[] args) {
// 调用first项目中的Test类的ls方法
// ls方法调用了Hutool中的类
Test.ls();
}
}
运行,发现报错了:
很显然是找不到hutool
依赖中的FileUtil
类。
造成这个错误的原因是因为first
的类Test
中方法ls
调用了外部依赖hutool
的类,因此在其它项目(例如这个second
项目)引用并调用first
中的Test
类的ls
方法时,也是需要使用到依赖hutool
中的类的。
但是我们在first
中将hutool
设置为了optional
,所以second
中只引入first
时,hutool
并没有引入(没有传递到second
项目依赖中):
这导致second
项目中找不到hutool
中的类。
所以解决这个问题的办法有下列几种:
- 去掉
first
中hutool
依赖的optional
字段 - 在
second
项目中引入hutool
依赖
所以在我们自己开发外部库并给其他人使用时,就需要注意这个依赖可选性的问题。