众所周知,Java包管理是学习Java过程中一个很重要的内容。本篇文章将着重介绍Java包管理以及Maven包管理的原理,以及解决包冲突的方法。
JVM工作原理
首先,我们先来了解一下JVM的工作原理,其实简单地概括性地说,JVM只会做两件事情:
- 执行一个类的字节码
- 在执行这个类的字节码的时候,若碰到了新的类,则加载它
- 不断重复以上两个过程
可见,JVM的工作是如此的简单和枯燥,但读到这里的你,可能会产生一个问题,JVM是怎么知道在哪里读取这些类的呢?
针对这个问题,我也不卖关子了,直接告诉你答案,JVM是通过classpath
参数来获取到这个路径的。
那么新的问题又出现了,我明明没有给JVM传递这个参数呀,它是怎么获取到的呢?
是的,没错,你没有给JVM传递classpath
这个参数,但是你的编译器偷偷帮你干了这件事情了!
(不相信的话,每次用编译器编译的时候,控制台都会有一串命令,在命令里面你可以清楚地看到编译器给JVM偷偷传递了classpath
参数)
另外,由于一个包有可能又依赖于其他很多个包,因此一个项目下来,可能classpath
下的依赖路径会变得又臭又长。
在Java刚诞生的时候,人们是需要通过手写这些classpath
路径来让JVM读懂读取jar包(一堆类的集合)的路径的,后面再人们的不断努力下,强大的IDEA和Maven的诞生,才让这个繁琐的过程变得无比简单。
依赖地狱
在Maven诞生之前,依赖冲突是一个很容易发生且很难解决的问题,我们把这种依赖冲突又称为classpath hell
(依赖地狱)。
什么是依赖冲突,由于全限定类名是类的唯一标示,因此当多个同名类不同版本同时出现在classpath的时候,就是噩梦的开始。
如上图,A包依赖了B包和C2包,而B包又依赖了C1包,在这个时候,由于所有的依赖包的路径都会写在
classpath
上面,让JVM从前往后地在这些路径上面寻早需要的依赖包,因此,若JVM先读取到了C1依赖包的classpath
路径,那么C2这个依赖包,由于和C1只是版本上面的不同,因此JVM会误把C1路径中找到的依赖包也同样作用在C2上面,从而导致出现不可预期的错误。
一般来说,当你看到你的代码在编译运行之后,出现了以下的错误,那就代表最麻烦的包冲突出现了:
- AbstractMethodError
- NoClassDefFoundError
- ClassNotFoundException
- LinkageError
Maven包管理的原理
在Maven没有诞生之前,包冲突只能通过手动寻找冲突的包依赖,并把对应的包进行升级或者替换,但问题是,一个项目一般存在着很多很多的包依赖,手动寻找费时费力,效率太低。直到后来Maven的诞生,才使得解决包依赖的解决变得不再那么麻烦。
首先我们先来了解一下Maven是如何对包进行管理的
我们需要首先知道的是,Maven有一套约定俗成的规范,其中规定了,生产代码需要放在src/main
目录下面,而测试代码则需要放在test/main
目录下面。这个将在之后讲包管理的scope中有用。
Maven会有中央仓库和本地仓库两个仓库
本地仓库即字面意思在本地你电脑中存在的仓库,它默认位于~/.m2
目录中,里面会放置一些经过下载的第三方包的缓存。
而中央仓库即线上仓库的意思,一个包会含有groupId
、artifactId
、version
三个字段,因此在中央仓库中,一个包存放的路径也是以这三个字段来存放的,具体会存放在groupId/artifactId/version
这个位置。
当一个项目需要使用一些第三方包的时候,你可以在pom文件中添加这些包的信息,这样Maven就会自动帮你下载这些包以及其相关依赖包到本地中缓存起来,具体的添加方式如下图所示:
如何解决包冲突
好了,说到这里,相信你已经基本了解了Maven是如何对第三方包进行管理的了,接下来,我们就来讲一下Maven是如何解决包冲突的。
首先我们需要知道解决包冲突的一个原则:绝对不允许最终的classpath
出现同名不同版本的jar包
相比于C1来说,C2这个第三方包离项目更接近,因此Maven会自动帮你把C1去除,而保留C2。但是这种策略有时候是不完美的,因此有时候也需要我们人为地去维护它,但不管怎么说,由于Maven的诞生,使得我们对于第三方包的很多操作都变得轻松和简单了。
接下来我们来说一下人为解决冲突的三种办法:
- 直接依赖高版本依赖,这样Maven就能去除所有低版本的不合适的依赖了,具体来说如下所示:
C1、C2和C3三个版本冲突了,但我们只要直接依赖了最高版本C3,把它作为项目的直接依赖,这样C1、C2这两个不合适的第三方包就会自动被Maven去除掉,从而解决了冲突。
- 通过pom文件来排除包中的后代指定依赖
具体操作如以下代码所示:
<dependency>
<groupId>xxx</groupId>
<artifactId>xxx</artifactId>
<version>1.0.0</version>
<exclusions>
<exclusion>
<groupId>yyy</groupId>
<artifactId>yyy</artifactId>
</exclusion>
</exclusions>
</dependency>
通过以上的代码,即可排除了xxx依赖中的后代yyy依赖,也可以解决包冲突的问题
3.通过Maven helper插件来解决包冲突问题,由于这个是工具性的操作,因此这里不过多介绍,大家可以自行去尝试。
最后,说一下pom文件中,可以通过设置scope标签,来指定一个包是否可以被生产代码和测试代码所引用:
- complie(生产代码以及测试代码均可见)
- test(只有测试代码可见)
- provided(只在编译的生产代码的时候生效,在运行时无效)
以上,就是本篇文章的所有内容,谢谢阅读~
网友评论