Maven 依赖管理
# Maven 依赖管理
# Maven 依赖管理
# 1. 依赖配置格式
Maven 项目中的依赖指的是项目运行所需的外部 JAR 包,一个项目通常会配置多个依赖。依赖的配置通过 pom.xml
文件进行,典型的配置格式如下:
<!-- 定义项目的所有依赖 -->
<dependencies>
<!-- 配置具体的依赖项 -->
<dependency>
<!-- 依赖所属的组织或公司 ID,通常使用反向域名 -->
<groupId>com.alibaba</groupId>
<!-- 依赖的项目或模块 ID -->
<artifactId>fastjson</artifactId>
<!-- 依赖的版本号,确保项目使用指定版本 -->
<version>1.2.75</version>
</dependency>
</dependencies>
2
3
4
5
6
7
8
9
10
11
12
配置项说明
<groupId>
:指定依赖所属的组织或公司,一般为域名反写,例如com.alibaba
。<artifactId>
:指定依赖的项目或模块名称,例如fastjson
。<version>
:指定依赖的版本号,例如1.2.75
。不同版本可能存在功能差异或兼容性问题,因此明确版本号是必要的。
在 Maven 中,通过这种配置方式可以方便地管理项目的所有依赖。
# 2. 依赖解析原理
为什么上边知道 groupId
、artifactId
、version
这三个信息 maven 就能自动管理这个依赖了,其实原理很简单。
如果你引入了依赖,maven 就会使用这个依赖的信息拼接字符串,到 maven 的中央仓库去寻找这个依赖,并把它下载下来。
默认下载是下载到了用户的根目录下,也就是 ~/.m2/repository 这个目录中,我们也称这个目录为本地仓库。
依赖解析的工作原理如下:
依赖信息的拼接:Maven 根据
groupId
、artifactId
和version
这三部分信息拼接出一个路径,用于定位仓库中的具体依赖。路径格式:依赖的路径格式如下:
仓库地址/{groupId}/{artifactId}/{version}/{artifactId}-{version}.jar
1在路径中,
groupId
中的点.
会被替换为斜杠/
。例如,对于com.alibaba.fastjson
这个依赖,拼接后的路径为:https://repo.maven.apache.org/maven2/com/alibaba/fastjson/1.2.75/fastjson-1.2.75.jar
1依赖下载和缓存:Maven 默认从中央仓库下载依赖,中央仓库的地址为:https://repo.maven.apache.org/maven2/ (opens new window)。下载后的依赖会缓存到本地仓库(默认为
~/.m2/repository
),避免重复下载。
依赖解析过程示例
假设我们在项目中引入了 fastjson
依赖:
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.75</version>
</dependency>
2
3
4
5
Maven 会将其解析为如下路径:
https://repo.maven.apache.org/maven2/com/alibaba/fastjson/1.2.75/fastjson-1.2.75.jar
这个地址可以直接在浏览器中访问并下载对应的 JAR 包。通过这种方式,Maven 实现了依赖的自动管理。
注意事项
由于 Maven 的中央仓库在外网,国内访问速度较慢,有时可能会遇到访问超时或连接失败的问题。这种情况下,建议配置国内的镜像仓库,如阿里云的镜像仓库。
# Maven 依赖传递
依赖具有传递性,包括直接传递和间接传递。
- 直接传递:在当前项目中通过依赖配置建立的依赖关系(A 使用 B,A 和 B 就是直接传递)
- 间接传递:被依赖的资源如果依赖其他资源,当前项目间接依赖其他资源(比较拗口,意思是如果 A 依赖 B,而 B 依赖 C,那么 A 和 C 之间就是间接传递)
依赖传递的冲突问题:
路径优先:
- 当依赖中出现相同的资源时,Maven 优先选择层级较浅的依赖版本。
- 例如,项目 A 依赖 B 和 C,而 B 依赖 D1,C 依赖 D2。如果 B 和 C 都被引入,Maven 会优先选择路径较浅的依赖(例如 D1)。
声明优先:
- 当相同层级中存在相同资源时,Maven 会优先选择配置顺序靠前的依赖。
- 例如,在
pom.xml
中,先声明的依赖会覆盖后声明的。
特殊优先:
- 当同一层级中存在相同资源的不同版本时,后配置的版本会覆盖前配置的版本。
- 这种策略主要用于应对特定版本需求。
# Maven 可选依赖 (optional
)
# 1. 可选依赖的概念
可选依赖指的是对外隐藏的依赖,不会传递给其他依赖当前项目的项目。适用于在某些情况下,项目本身需要某个依赖,但不希望其被传递给上游项目。
# 2. 可选依赖的配置语法
可选依赖通过在 <dependency>
元素中添加 <optional>
标签来实现,具体配置如下:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<version>2.5.4</version>
<!-- 将该依赖设置为可选 -->
<optional>true</optional>
</dependency>
2
3
4
5
6
7
# 3. 可选依赖的作用机制
当某个依赖被标记为 optional=true
时,Maven 在解析依赖传递时不会将该依赖传递给其他项目。例如:
- 项目 A 依赖项目 B,项目 B 依赖项目 C,并且项目 B 将项目 C 设置为可选依赖。
- 在这种情况下,项目 A 只能直接使用项目 B,而不会间接获取项目 C。
# 4. 可选依赖的使用场景
- 防止不必要的依赖传递:当项目只需要在内部使用某个依赖时,通过可选依赖避免不必要的依赖被传递到其他项目中。
- 减少依赖冲突:某些依赖可能在不同项目中存在版本不兼容的问题,设置为可选依赖可以降低这种风险。
- 增强代码健壮性:可选依赖在运行时即使缺失,程序仍然可以正常运行。因此,适用于不影响核心功能的辅助库。
# Maven 排除依赖 (exclusions
)
# 1. 排除依赖的概念
排除依赖指的是在引入某个依赖时,主动排除该依赖所传递的部分依赖,防止不需要的库被引入到当前项目中。这种排除机制可以帮助避免依赖冲突和冗余。
# 2. 排除依赖的配置语法
排除依赖通过在 <dependency>
元素中添加 <exclusions>
标签来实现,具体配置如下:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.5.4</version>
<!-- 排除不需要的传递依赖 -->
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
<!-- 可以添加多个 exclusion -->
<exclusion>
<groupId>org.example</groupId>
<artifactId>unwanted-library</artifactId>
</exclusion>
</exclusions>
</dependency>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
配置项说明
<exclusions>
:用于包含一个或多个要排除的依赖。<exclusion>
:指定具体要排除的依赖,需要定义groupId
和artifactId
。版本号不需要指定,因为排除依赖是对该库的所有版本生效。
# 3. 排除依赖的作用机制
当项目中通过传递关系引入了不需要的依赖时,使用 exclusions
可以防止这些依赖被引入。例如:
- 项目 A 依赖项目 B,项目 B 依赖项目 C,但项目 A 不希望引入 C。
- 在 A 的
pom.xml
中,配置 B 作为依赖,并使用exclusions
排除 C,这样 C 就不会被引入到 A 中。
# 4. 排除依赖的使用场景
- 解决依赖冲突:当项目中不同依赖之间存在版本冲突时,排除掉不需要的依赖版本可以避免冲突。
- 优化项目依赖树:有时传递依赖会引入一些不必要的库,排除这些冗余依赖可以减少打包体积,提高项目的运行效率。
- 替换默认实现:例如,一个项目可能默认使用
Tomcat
作为内嵌服务器,但你希望使用Jetty
。可以通过排除spring-boot-starter-tomcat
,并手动添加jetty
依赖来实现。
# 4. 排除依赖示例
假设项目中默认引入了 spring-boot-starter-tomcat
,但你希望使用其他服务器,可以进行如下配置:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.5.4</version>
<exclusions>
<!-- 排除内置的 Tomcat 依赖 -->
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- 添加其他服务器依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jetty</artifactId>
<version>2.5.4</version>
</dependency>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
通过上述配置,Maven 不再引入 Tomcat
,而是使用 Jetty
作为服务器。
# Maven依赖范围
依赖的 jar 包默认情况可以在任何地方使用,可以通过 scope 标签设定其作用范围。
作用范围:
主程序范围有效(main 文件夹范围内)
测试程序范围有效(test 文件夹范围内)
是否参与打包(package 文件夹范围内)
compile
: scope缺省值。三种范围都生效,参与打包。例如:spring-coreprovided
: build生效,test生效,runtime不生效,不参与打包。例如:servlet-apiruntime
: build不生效、test生效,runtime生效,参与打包。例如:mysql-connector-java(JDBC驱动)system
: build生效,test生效,runtime不生效,不参与打包。不是从maven仓库引入,而是在本地目录的下的jar,十分不推荐使用test
: build不生效,test生效,runtime不生效,不参与打包。例如:JUnit、Mockitoimport
:仅仅在dependencyManagement内部才有这个值,它对范围都不生效。用于引入外部依赖,进行依赖管理
知识点:runtime不生效的,都不会参与打包,不参与打包就不会参与传递,因为在打包阶段,使用的是运行classpath
选择 scope
,主要看在哪个范围不生效。带有依赖范围的资源在进行传递时,作用范围将受到影响。
小插曲:provided和optional的区别
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>
2
3
4
5
6
7
8
9
10
11
从结果来看:二者是一样的。都不会被打入包中,依赖不会传递给上层。所以它哥俩的区别在于使用场景上。
- optional:表示该依赖是可选的,它的侧重点是强调这个依赖是可选的,不会被打入jar包内,也不会传递。注意:即使不传递、不打入jar包的内,运行时也不能报错
- optional更强调一种规范使用,表示可选,但即使没有该依赖也不影响程序运行,所以一般是需要程序进行一些额外的判断逻辑来保证程序的健壮性。
- provided:表示这个依赖是必须的,但是呢这个依赖通常在运行期不需要或者已经被提供了,比如lombok运行期不需要,servlet-api运行期已提供,所以都适合用provided而不适合用optional
最后,分享一个比喻,将二者的区别道出来,我觉得特别好,分享给你:
- optional:吃面时候,酱油、辣椒等就是可选的(强调可选性),加不加都不会影响面的正常食用(强调结果正确性,运行期不会报错)
- provided:吃面时候,筷子、碗是必须的(强调必须性),不过这些一般是店家给顾客备好,不需要顾客自带(强调结果正确性,运行期只是不需要有而已)
# IDEA中查看依赖
- 未直接使用的依赖:有时候,项目中可能包含了一些依赖,但实际上你的代码并没有直接使用这些依赖中的任何类或方法。这些依赖可能是之前添加进来后未被使用,或者是被其他依赖传递引入但实际上在当前项目配置中并不需要。
- 被排除的传递依赖:在Maven中,你可以显式地排除某个依赖的传递依赖。如果一个依赖被排除,它在Maven依赖树中会显示为灰色。这表明虽然这个依赖项在依赖树中可见,但实际上它并不会被包含在最终的项目构建中。
- 作用域影响:有些依赖项可能因为它们的作用域被设置为
provided
或其他非默认作用域,而在特定的构建阶段或运行时环境中不被包括。例如,provided
作用域的依赖在编译时可用,但不会被包含在打包的应用中,因为预期这些依赖项将由运行时环境提供。
灰掉的依赖显示方式帮助开发者快速识别出哪些依赖是实际影响项目构建和运行的,哪些可能是多余的或需要特别处理的。
# IDEA 中的 JDK配置
在 IntelliJ IDEA 中,项目的全局 SDK 和模块 SDK 都是用于设置 JDK 版本的配置,但它们有不同的作用范围和优先级。
# 1. 全局 SDK(Project SDK)
全局 SDK 是在整个项目范围内配置的 JDK,它作为项目默认的 SDK 配置。通常,在项目中没有为模块单独设置 SDK 时,模块会继承项目的全局 SDK。
- 配置路径:
File -> Project Structure -> Project -> Project SDK
作用
- 全局 SDK 决定了整个项目的默认 JDK 版本,如果模块没有设置自己的 SDK,它会自动继承这个全局 SDK。
- 当项目中有多个模块时,全局 SDK 可以作为统一的配置,确保所有模块默认使用相同的 JDK 版本。
# 2. 模块 SDK(Module SDK)
模块 SDK 是针对单个模块配置的 JDK。当模块有特殊的 JDK 需求(如需要不同版本的 JDK),可以在模块设置中指定不同的 SDK。
- 配置路径:
File -> Project Structure -> Modules -> Dependencies -> Module SDK
作用
- 模块 SDK 可以覆盖全局 SDK,确保该模块使用指定的 JDK 版本。
- 如果模块没有指定 SDK,则会默认使用项目的全局 SDK。
# 3. 全局 SDK 与模块 SDK 的关系
- 优先级:模块 SDK 的优先级高于全局 SDK。如果为模块设置了专门的 SDK,模块会使用该 SDK 而不是全局 SDK。
- 默认继承:如果模块没有配置自己的 SDK,它会继承项目的全局 SDK。
假设你在项目中设置了以下内容:
- 全局 SDK:Java 11
- 模块 A 的 SDK:Java 8
- 模块 B 没有单独设置 SDK
在这种情况下:
- 模块 A 会使用 Java 8 进行编译和运行。
- 模块 B 会继承全局 SDK,使用 Java 11 进行编译和运行。
总结
- 全局 SDK 是项目的默认 JDK 配置,所有模块默认会继承它。
- 模块 SDK 是单独为某个模块配置的 JDK,它的优先级高于全局 SDK,可以覆盖全局设置。
- 通过合理设置全局 SDK 和模块 SDK,可以更灵活地管理项目中的 JDK 版本。
# IDEA 和 pom.xml配置的区别
在 IntelliJ IDEA 中,通过项目或模块设置的 SDK 配置 JDK 版本,它的优先级高于 pom.xml
文件中的配置的。这是因为 IDEA 的 SDK 配置直接影响编译器使用的 JDK,而 pom.xml
中的 JDK 配置更多是供 Maven 构建工具使用的。
IDEA 中的 SDK 配置:
- 在 IntelliJ IDEA 中,项目的 JDK 是通过项目 SDK 或模块 SDK 设置的。这种设置直接作用于 IDEA 内部的编译器、代码提示、运行环境等,因此它的优先级更高。
- 当你在 IDEA 中运行项目时,无论
pom.xml
中配置的 JDK 版本如何,IDEA 会优先使用你在项目或模块中配置的 SDK 版本。
pom.xml
中的 JDK 配置:pom.xml
中的 JDK 配置更多是供 Maven 构建工具使用的,特别是在你通过命令行或其他非 IDE 环境中运行 Maven 构建时。常见的配置方式包括:Spring Boot 项目中的 JDK 配置:
在 Spring Boot 项目中,可以通过
<properties>
标签设置java.version
,Spring Boot 的父项目(spring-boot-starter-parent
)会自动引用这个属性来配置maven-compiler-plugin
,确保使用指定的 Java 版本:<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>3.0.0</version> </parent> <properties> <java.version>17</java.version> </properties>
1
2
3
4
5
6
7
8
9在这种情况下,Spring Boot 会自动将
java.version
应用于 Maven 的编译配置,你不需要手动设置maven-compiler-plugin
。通用 Maven 项目中的 JDK 配置:
在非 Spring Boot 的 Maven 项目中,仅在
<properties>
中配置java.version
是不够的。为了确保 Maven 使用指定的 JDK 版本进行编译,你还需要显式配置maven-compiler-plugin
:<properties> <java.version>17</java.version> </properties> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.8.1</version> <configuration> <source>${java.version}</source> <target>${java.version}</target> </configuration> </plugin> </plugins> </build>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17这里的
maven-compiler-plugin
会引用java.version
属性,确保在 Maven 构建时使用指定的 Java 版本。
总结
- IDEA 的 SDK 配置 优先级高于
pom.xml
中的配置,因为它直接控制了 IDEA 内部的编译器、代码提示和运行环境。 pom.xml
的 JDK 配置 主要用于 Maven 构建工具,与 IDEA 的配置分开,适用于命令行下的构建和部署。- 为了避免冲突,建议在 IDEA 中的 SDK 配置与
pom.xml
中的配置保持一致。