美文网首页
Java包管理以及Maven包管理

Java包管理以及Maven包管理

作者: 宣泽彬 | 来源:发表于2019-11-15 17:24 被阅读0次

    众所周知,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(只在编译的生产代码的时候生效,在运行时无效)

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

    相关文章

      网友评论

          本文标题:Java包管理以及Maven包管理

          本文链接:https://www.haomeiwen.com/subject/cxvsictx.html