Java中的传递性依赖

我们先来看一下JVM的基本工作流程

  1. 执行一个类的字节码
  2. 如果碰到了新的类,加载它
  3. 从 -classpath里面找,classpath包含了很多的文件,用’:‘分隔,JVM一个一个的找
  4. 循环以上过程

这样会产生一个问题,你依赖的类还依赖了别的类,别的类又依赖了新的别的类,这样一直搜索 并加载下去,这就叫做传递性依赖。

Classpath Hell

我们知道对于JVM而言,全限定类名(fully qualified class name)是所有类的唯一标识, 如果classpath里面出现了同名的类, 在classpath的位置靠前的那一个会被加载。设想一下 如果classpath中引入了某个包的不同版本,这有可能导致错误的版本里面 的类被加载。所以 我们需要进行包管理,包管理的本质就是告诉JVM去哪里找到所需的第三方类库以及解决类名 冲突问题。

包管理

启蒙时代 Aache Ant

  • 手动下载Jar包,放在一个目录中
  • 写XML配置,指定编译的源代码目录,依赖的jar包,输出目录等
  • 缺点很明显: 每个人都要自己造一套轮子,第三方库需要手动下载,没有解决classpath hell问题

Maven时代

Maven简介

首先,Maven远远不只是包管理,更是自动化构建工具!pom。xml就是项目的说明书。 Maven遵循约定优于配置(convention over configuration)的原则, 比如生产代码放在src/main,测试代码放在src/test。Maven有两个仓库,中央仓库在远端的服务器上面, Maven的中央仓库,按照一定的约定存储包。Maven的本地仓库,默认位于~/.m2,下载的第三方包放在这里缓存。 其次,Maven对所有 包建立了一层抽象,每个包有groupID, artifactID, version, 按照约定为所有包编号并且 通过这三个标识找唯一的包。注意,Maven 只下载中央仓库里需要的部分(字节码),在IDEA里面点击download sources下载源代码。Maven提供语义化版本,比如snapshot(快照版本): 在开发过程中需要频繁修改, 5.0.0-M1: M是milestone的意思,5.0.0-RC2: RC是release candidate的意思。

为什么说Maven是跨时代的
  1. 传递性依赖的自动管理

    • 原则: 绝对不允许最终的classpath中出现同名的不同版本的jar包
    • 解决原则: 就近原则,距离项目最近的胜出(延伸链最短的)。如果距离是一样的,谁的声明靠前谁获胜
  2. 依赖冲突的解决 大部分情况都没问题,如果需要手动解决,在IDEA的右侧Maven里面看dependency(人肉扫描), 或者在IDEA的terminal里面输入 mvn dependency:tree(展示的是冲突解决之后的)。查看版本的区别: 在中央仓库找到包,包里面有.pom文件,里面有代码的github 地址,在github里面通过tag来查看不同版本的源码

    • 解决方法1: 直接依赖冲突包,在pom.xml里面加入dependency
    • 解决方法2: 强行告诉Maven我需要哪一个, pom.xml的某个dependency的子树里面写入exclusions,这样依赖 关系就断掉了
    • 解决方法3: 在IDEA里面下载插件,maven helper。
  3. 依赖的scope 实现依赖的隔离 重要的是两个(compile, test),compile是在main和test里面都有效,test只在test中有效。provided是编译的时候有效, 运行的时候无效,一个例子是tomcat,他自己提供servlet的包

  4. 依赖冲突可能会产生的错误信息

    • AbstractMethodError
    • NoClassDefFoundError
    • ClassNotFoundException
    • LinkageError