欢迎您访问 最编程 本站为您分享编程语言代码,编程技术文章!
您现在的位置是: 首页

搞定Java包管理:Maven的神奇之处

最编程 2024-08-02 12:59:08
...

众所周知,Java包管理是学习Java过程中一个很重要的内容。本篇文章将着重介绍Java包管理以及Maven包管理的原理,以及解决包冲突的方法。

JVM工作原理

首先,我们先来了解一下JVM的工作原理,其实简单地概括性地说,JVM只会做两件事情:

  1. 执行一个类的字节码
  2. 在执行这个类的字节码的时候,若碰到了新的类,则加载它
  3. 不断重复以上两个过程

可见,JVM的工作是如此的简单和枯燥,但读到这里的你,可能会产生一个问题,JVM是怎么知道在哪里读取这些类的呢?
针对这个问题,我也不卖关子了,直接告诉你答案,JVM是通过classpath参数来获取到这个路径的。
那么新的问题又出现了,我明明没有给JVM传递这个参数呀,它是怎么获取到的呢?

是的,没错,你没有给JVM传递classpath这个参数,但是你的编译器偷偷帮你干了这件事情了!

(不相信的话,每次用编译器编译的时候,控制台都会有一串命令,在命令里面你可以清楚地看到编译器给JVM偷偷传递了classpath参数)

给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上面,从而导致出现不可预期的错误。

一般来说,当你看到你的代码在编译运行之后,出现了以下的错误,那就代表最麻烦的包冲突出现了:

  1. AbstractMethodError
  2. NoClassDefFoundError
  3. ClassNotFoundException
  4. LinkageError

Maven包管理的原理

在Maven没有诞生之前,包冲突只能通过手动寻找冲突的包依赖,并把对应的包进行升级或者替换,但问题是,一个项目一般存在着很多很多的包依赖,手动寻找费时费力,效率太低。直到后来Maven的诞生,才使得解决包依赖的解决变得不再那么麻烦。

首先我们先来了解一下Maven是如何对包进行管理的
我们需要首先知道的是,Maven有一套约定俗成的规范,其中规定了,生产代码需要放在src/main目录下面,而测试代码则需要放在test/main目录下面。这个将在之后讲包管理的scope中有用。

Maven会有*仓库和本地仓库两个仓库
本地仓库即字面意思在本地你电脑中存在的仓库,它默认位于~/.m2目录中,里面会放置一些经过下载的第三方包的缓存。
而*仓库即线上仓库的意思,一个包会含有groupIdartifactIdversion三个字段,因此在*仓库中,一个包存放的路径也是以这三个字段来存放的,具体会存放在groupId/artifactId/version这个位置。

当一个项目需要使用一些第三方包的时候,你可以在pom文件中添加这些包的信息,这样Maven就会自动帮你下载这些包以及其相关依赖包到本地中缓存起来,具体的添加方式如下图所示:

pom文件
pom文件


如何解决包冲突

好了,说到这里,相信你已经基本了解了Maven是如何对第三方包进行管理的了,接下来,我们就来讲一下Maven是如何解决包冲突的。
首先我们需要知道解决包冲突的一个原则:绝对不允许最终的classpath出现同名不同版本的jar包

在Maven中,当出现包冲突的问题的时候,Maven会保留离项目最近的包,而去除其他有冲突的包,拿之前的例子来说:
解决依赖冲突
解决依赖冲突

相比于C1来说,C2这个第三方包离项目更接近,因此Maven会自动帮你把C1去除,而保留C2。但是这种策略有时候是不完美的,因此有时候也需要我们人为地去维护它,但不管怎么说,由于Maven的诞生,使得我们对于第三方包的很多操作都变得轻松和简单了。

接下来我们来说一下人为解决冲突的三种办法:

  1. 直接依赖高版本依赖,这样Maven就能去除所有低版本的不合适的依赖了,具体来说如下所示:
解决依赖冲突
解决依赖冲突

C1、C2和C3三个版本冲突了,但我们只要直接依赖了最高版本C3,把它作为项目的直接依赖,这样C1、C2这两个不合适的第三方包就会自动被Maven去除掉,从而解决了冲突。

  1. 通过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标签,来指定一个包是否可以被生产代码和测试代码所引用:

  1. complie(生产代码以及测试代码均可见)
  2. test(只有测试代码可见)
  3. provided(只在编译的生产代码的时候生效,在运行时无效)

以上,就是本篇文章的所有内容,谢谢阅读~