Maven 的依赖管理
# 45.Maven 的依赖管理
本文介绍关于依赖管理的更多概念
# 配置 properties
当我们需要用到的依赖多之后,一般会使用一个统一的版本,此时我们可以的配置可能是这样的:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
例如本例是使用 5.0.2.RELEASE。此时我们可能遇到这样的问题:升级版本。那么我们需要将所有涉及到的版本都改一遍!而我们可以通过配置 properties 的方式来简化配置:
<properties>
<spring.version>5.0.2.RELEASE</spring.version>
</properties>
2
3
然后在用到的地方使用该 properties:这样就可以统一修改版本号了,其实这是 EL 表达式的一种。
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>${spring.version}</version>
</dependency>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Maven 自己会有一些内置属性:
${basedir}
项目根目录${project.build.directory}
构建目录,缺省为 target${project.build.outputDirectory}
构建过程输出目录,缺省为 target/classes${project.build.finalName}
产出物名称,缺省为${project.artifactId}-${project.version}
${project.packaging}
打包类型,缺省为 jar${project.xxx}
当前 pom 文件的任意节点的内容
# 项目本身的坐标
每个 Maven 项目,都需要定义自己本身的坐标,例如本项目在 pom.xml 文件有这样的定义:
<!--项目名称,定义为组织名+项目名,类似包名-->
<groupId>com.peterjxl.LearnJavaMaven</groupId>
<!-- 模块名称 -->
<artifactId>hello-world</artifactId>
<!-- 当前项目版本号,snapshot 为快照版本即非正式版本,release 为正式发布版本 -->
<version>0.0.1-SNAPSHOT</version>
2
3
4
5
6
7
8
为什么项目也要定义坐标呢?因为项目本身也可以作为一个依赖,供其他项目使用!有很多开源项目,例如 Log4j 框架 (opens new window),其本身也是用 Maven 来管理的,也有引入其他依赖。
Maven 对依赖的分类:
- 直接依赖:项目中直接导入的 jar 包,就是该项目的直接依赖包。
- 传递依赖:项目中没有直接导入的 jar 包,也就是项目直接依赖的 jar 包所需要的依赖,这些依赖也是项目所需要的。
# 坐标的来源方式
添加依赖需要指定依赖 jar 包的坐标,但是很多情况我们是不知道 jar 包的的坐标,可以通过如下网址查询:
- http://search.maven.org (opens new window)
- http://mvnrepository.com (opens new window)
- 还有很多网站提供类似的服务,不一一介绍了
网站搜索示例:搜索 Spring
然后就可以选择复制 Maven 的坐标,或者下载 JAR 文件
# 依赖范围 scope
A 依赖 B,需要在 A 的 pom.xml 文件中添加 B 的坐标,
添加坐标时需要指定依赖范围 <scope>
,依赖范围包括:
- compile:默认值,在编译、测试、打包和程序运行的时候需要用到的依赖
- provided:只有在当 JDK 或者一个容器已提供该依赖之后才使用, provided 依赖在编译和测试时需要,在运行时不需要,比如:servlet 所用到的依赖,仅仅在编译时用到,在运行时,由于 Tomcat 已经有这些依赖了,因此在运行时不需要用到。
- runtime:在运行和测试时需要,但在编译的时候不需要。比如 jdbc 的驱动包。由于运行时需要,所以 runtime 范围的依赖会被打包。
- test:test 范围依赖 在编译和运行时都不需要,它们只有在测试编译和测试运行阶段可用, 比如:junit。由于运行时不需要所以 test 范围依赖不会被打包。
- system:system 范围依赖与 provided 类似,但是你必须显式的提供一个对于本地系统中 JAR 文件的路径,需要指定 systemPath 磁盘路径,system 依赖不推荐使用。
我们可以列个表格:
依赖范围 | 对于编译 classpath 有效 | 对于测试 classpath 有效 | 对于运行时 classpath 有效 | 例子 |
---|---|---|---|---|
compile | Y | Y | Y | Spring-Core |
test | - | Y | - | Junit |
provided | Y | Y | - | Servlet-api |
Runtime | - | Y | Y | JDBC 驱动 |
# 依赖冲突的问题
有时候,一个开源项目 A,会依赖于另一个开源项目 B;
而我们引入开源项目 A 的时候,Maven 会将项目 A,需要的依赖,也引入进来。例如,当我们引入 Spring 框架的时候:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
2
3
4
5
可以看到 Spring 还依赖于不少依赖:
这样做有好处也有坏处:
- 好处:引入一个依赖,就可以引入所有用到的依赖,不用一个一个的去引入
- 坏处:当一个依赖 A,需要用到依赖 C;而另一个依赖 B,也用到依赖 C,并且需要的依赖 C 的版本还不同的时候...
我们可以演示下,添加一个新的依赖 spring-beans <:
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>4.2.4.RELEASE</version>
</dependency>
</dependencies>
2
3
4
5
6
7
8
9
10
11
12
13
虽然 spring-context 已经包含了 spring-beans <,但我们现在是为了演示问题,所以特地添加的。
我们可以通过 IDEA 来查看项目的依赖情况:首先打开 Maven 的工具面板,然后点击右上角的查看依赖关系(或者快捷键 Ctrl + Shift + Alt + U)
我们可以看到,我们的项目的依赖情况,这里以一个网上的截图为例:
由下往上看,我们的项目,使用了两个依赖:
- spring-beans,其用到了一个依赖 spring-core
- spring-context,其最后也是用到了 spring-core
- 两者的版本是不一样的,最后到底用哪个呢?
我们可以看看 IDEA 的视图,可以看到最后用的还是 spring-core 5.0.2 的版本:
但如果说我们将依赖的顺序调换下:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>4.2.4.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
2
3
4
5
6
7
8
9
10
11
此时,用的就是 4.2.4 的版本了!
因此我们引入依赖的时候,必须考虑如何解决。解决 jar 包冲突的方式:
方式一:第一声明优先原则。哪个 jar 包的坐标在靠上的位置,这个 jar 包就是先声明的。先声明的 jar 包坐标下的依赖包,可以优先进入项目中。
方式二:路径近者优先原则。直接依赖路径比传递依赖路径近,那么最终项目进入的 jar 包会是路径近的直接依赖包
方式三【推荐使用】:直接排除法。当我们要排除某个 jar 包下依赖包,配置 exclusions 标签。内部可以不写版本号,因为此时依赖包使用的版本和默认和本 jar 包一样,例如本例中就是 spring-beans 的版本 4.2.4。
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-beans</artifactId> <version>4.2.4.RELEASE</version> <exclusions> <exclusion> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> </exclusion> </exclusions> </dependency>
1
2
3
4
5
6
7
8
9
10
11
# 锁定依赖版本
maven 工程是可以分父子依赖关系的。
凡是依赖别的项目后,拿到的别的项目的依赖包,都属于传递依赖。比如:当前 A 项目,被 B 项目依赖。那么我们 A 项目中所有 jar 包都会传递到 B 项目中。
B 项目开发者,如果再在 B 项目中导入一套 ssm 框架的 jar 包,对于 B 项目是直接依赖,那么直接依赖的 jar 包就会把我们 A 项目传递过去的 jar 包覆盖掉。
为了防止以上情况的出现。我们可以把 A 项目中主要 jar 包的坐标锁住,那么其他依赖该项目的项目中,即便是有同名 jar 包直接依赖,也无法覆盖。
配置方法:使用 <dependencyManagement>
标签
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>${mybatis.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
注意,锁定的 jar 包仅仅是锁定作用,并没有引入依赖。
# 查找依赖
有时候我们知道类名,但是不知道对应的 Maven 依赖,也可以通过搜索来找到。
打开 https://search.maven.org/
一. 已知全类名:输入 fc: org.apache.poi.xssf.usermodel.XSSFWorkbook (注:fc 代表 full class)
二. 已知类名:输入 c: 类名(注:c 代表 class)
三. 已知 Group ID 或者 Artifact ID,g: com.alibaba a: druid
# 小结
我们讲了什么是依赖,并且介绍了 scope 和依赖冲突的方式,下一篇我们来讲讲插件管理。
- 01
- 中国网络防火长城简史 转载10-12
- 03
- 公告:博客近期 RSS 相关问题10-02