JVM的工作原理
JVM的设计理念极其简单,JVM只会做两件事情:
- 执行一个类的字节码
- 执行这个类的时候,若遇到了新的类,那么就加载它
- 不断重复以上两个过程,直到结束
JVM通过classpath参数来获取加载类的路径,这些包以冒号分割,当JVM遇到了依赖的类时,就会去classpath路径中依次寻找直到找到这个类为止。
什么是包管理
在没有包管理这样一个概念时,程序员需要手动编译这些类,类之间的相互依赖使得这个过程非常的繁琐,而且,还会出现依赖冲突。于是乎,就出现了包管理的概念
包管理的本质就是告诉JVM如何找到所需要的第三方类库,并且陈工地解决依赖冲突的问题。
依赖冲突
在Maven诞生前,依赖冲突是一个非常容易发生且难以解决的问题。全限定类名是JVM眼中识别类的唯一标识,如果多个同名类的不同版本出现在classpath中,就可能会带来冲突的问题,例如:
你的项目依赖了B包和C包的两个版本,包的依赖路径被完整地写到了classpath上,JVM会从前向后在classpath上寻找需要加载的类,所以如果C1包在C2包的前面,那么JVM就会忽视C2包,C2包的类是C包的后期版本,可能新添加了某些功能,修复了一些bug,这样一来,项目就有可能因为依赖冲突而出错。
像以下几种异常:
- AbstractMethodError
- NoClassDefFoundError
- ClassNotFoundException
- LinkageError
如果项目中报了以上的异常,很有可能一个原因是因为依赖冲突引起的。
Maven 解决冲突的原则
在了解Maven是如何解决冲突之前,我们先来了解一下Maven是如何对包进行管理的
Maven规定了生产代码在src/main目录。测试代码在src/test目录,这个是不成文的规定,这也是Maven的约定优于配置原则(Convention over configuration),在没有强制的规定时,因为每个人定义的目录不同,这就造成了很多问题,Maven则强制规定了这样的目录结构。
Maven有中央仓库以及本地仓库。
本地仓库默认在~/.m2
下,这个目录存放着下载的第三方包缓存。
中央仓库则是线上仓库,通过groupId,artifactId,version这样的坐标信息定位到我们需要的第三方包
我们在pom.xml文件中配置好坐标信息,Maven就会自动下载这些包以及相关的依赖包,并在本地仓库中缓存起来。
那么 Maven是如何解决包的冲突的呢?
拿之前的例子说明:
Maven会保留离项目最近的包,删去其他的包,本示例中,离项目最近的包是C2。Maven会去除掉C1而保留C2,当然这种策略也是不完美的,不过相对于自己操作实在是方便多了。
如何解决Maven的依赖冲突
有的时候,即便是Maven包管理工具,还是可能造成项目依赖冲突,解决Maven的冲突有如下几种办法:
-
直接依赖最高版本
在没有添加红色的依赖时,Maven会比较C1和C2两个包离项目的远近,之后会比较先后的次序,所以Maven会舍弃掉C2包,我们解决这个冲突最直接的办法就是,直接让该项目依赖C3最高版本的包,这样就可以解决掉冲突的问题。
- 使用exclusions排除保重的后代指定的依赖
<dependency>
<groupId>xxx</groupId>
<artifactId>xxx</artifactId>
<version>1.0.0</version>
<exclusions>
<exclusion>
<groupId>yyy</groupId>
<artifactId>yyy</artifactId>
</exclusion>
</exclusions>
</dependency>
如果使用IDEA可以下载mvn helper 插件更加直观有效解决包冲突的问题。
参考文章 Java包管理以及Maven包管理