写点什么

Jigsaw 项目会解决 Java 的 JAR 地狱问题么?

  • 2015-12-31
  • 本文字数:2011 字

    阅读完需:约 7 分钟

Nicolai Parlog 是一位热情的软件工程师,数字版权与开源软件的狂热拥护者;他对 AssertJ、ControlsFX、FindBugs 及 Property Alliance 等项目都做出过重要的贡献。近日,Parlog 就 Jigsaw 项目撰写了一篇文章,谈到了 Jigsaw 项目的一些不足以及改进之处。Jigsaw 项目有着雄心勃勃的宏伟目标,其目标之一就是彻底摆脱极易出错且问题多多的类路径机制中的 JAR 地狱问题。不过,虽然该项目的其他目标会在不久的将来得以实现,但解决 JAR 地狱问题这一目标似乎并不是那么容易的。

为了更好地理解我们接下来要讨论的内容,首先来看一下 JAR 地狱问题,接下来介绍 Jigsaw 项目将会解决问题的哪些方面,以及为什么说 Jigsaw 所尝试解决的问题并不会对整个问题域产生本质的影响。最后,我们来看一下官方对于这个话题的立场,并给出如何防止出现模块地狱的提案。

JAR 地狱问题

JAR 地狱存在着如下循环问题:

  • 表述不清以及传递性依赖
  • 遮蔽
  • 版本冲突
  • 复杂的类加载

根据构建工具与组件系统(JDK 开发者称之为容器)为我们所带来的诸多功能与特性,我们可以认为表述不清以及传递性依赖问题已经在很大程度上得到了解决,遮蔽问题至少得到了缓解,而复杂的类加载也不再是老生常谈的问题了。这样,版本冲突就成为 JAR 地狱中最为严重的一个问题了,它影响到了很多很多项目每天的更新决策。

Jigsaw 将会带来哪些改变?

我之前曾就 Jigsaw 项目会为 Java 9 带来哪些新特性专门写过文章进行过介绍,不过这里将从不同的视角进行阐述。首先,它会受到当前的早期访问构建版的影响;其次,我们这里只从与 JAR/ 模块地狱相关的角度进行介绍。

Jigsaw 为 Java 带来的核心概念就是模块化。简而言之,模块就像 JAR 一样,同时带有一些附加信息与特性。这些信息包含了模块的名字以及模块所依赖的其他模块的名字。

依赖

当编译器与 JVM 在处理模块时,他们会解析这些信息。在编译或启动时,他们会通过模块路径传递性解析所有依赖。总体来说,这类似于类路径扫描,不过现在寻找的是整个模块而非单个类,对于 JVM 来说,这是在启动期而非运行期进行的。如果在模块路径上无法找到所有依赖,那么解析模块的传递性依赖就会失败。这显然可以解决表述不清,以及无休止的传递性依赖的问题。我认为这是个很棒的做法,Java 语言现在正式知道关于依赖的信息了,所有工具(编译器与 JVM 等)都能理解这一点并正常使用!不过,我认为这并不会对开发者每天的工作产生多少积极的影响,因为现在很多既有的基础设施都已经解决这个问题了,比如说构建工具等。

遮蔽

Jigsaw 消除了遮蔽的问题。模块系统可以确保每个依赖都会被另一个模块所实现,每个模块都会读取至多一个模块,定义了同名包的模块之间并不会相互干扰。更准确地说,模块系统在遇到模糊不清的情况时就会终止并报错,比如说两个模块将相同的包导出到相同模块中。

版本冲突

我们认为第三方库的版本冲突是 JAR 地狱最为难以解决的问题。最直接的解决方案就是一个模块系统能够加载同一个模块的不同版本。这需要确保这些版本之间不存在互相交互的情况。问题在于:在单个配置中,没必要支持一个模块的多个版本。实际上,当前的构建既不会创建,也无法理解模块版本信息。曾有人使用了一些变通办法。最丑陋,同时也是最可行的办法就是重命名出现冲突的构件,这样他们就不再是相同模块的两个不同版本了,而是两个完全不同的模块。不过,这种做法最后证明也是行不通的。显然,确保“定义了同名包的模块之间不会相互干扰”是在两个模块导出相同包时拒绝任何启动配置来实现的。即便没有模块读取他们亦如此!

复杂的类加载

模块与类加载器之间如何交互以及如何改变类加载的复杂性是个很棘手的问题。实际上,模块系统对模块与类加载器之间的关系并没有做多少限制。类加载器可以从一个模块或是多个模块来加载类型,只要模块之间不存在相互干扰的情况,并且每个模块中的类型只由一个加载器加载即可。因此,类加载器与模块之间是一对多的关系。

模块地狱?

既然依赖与遮蔽问题已经得到了解决,并且类加载问题也得到了改进,那我为何还要讨论模块地狱呢?就是因为版本冲突么?没错!如果 Jigsaw 想要解决 JAR 地狱问题,它就需要特别注意版本冲突问题。否则,很多项目并不会出现什么起色。他们依然要面对版本冲突问题,并且会陷入到自定义类加载器的梦魇中。

提案

我的提案是让开发者与构建工具能够传递一些额外的信息,这些信息能够解决一些含糊不清的问题。传递这种信息的两种常见方式是命令行与配置文件。如果使用命令行参数,那么每次启动时都需要输入一次。根据信息的多少以及项目的规模,这种做法可能会变得非常乏味。可以通过构建工具来创建配置文件,然后再通过命令行指定配置文件。这看起来是个不错的解决方案。目前,初始模块与所有的传递性依赖都是通过单个配置来解析的,这形成了单独的一个层次。不过,我们可以在运行期将相同模块的多个版本加载到不同层次中,这正是组件系统要做的事情。总的来说,我的建议就是通过多个层次来显式指定配置。

2015-12-31 02:122782
用户头像

发布了 88 篇内容, 共 272.9 次阅读, 收获喜欢 9 次。

关注

评论

发布
暂无评论
发现更多内容

模块(六)如何设计业务的微服务架构?

我是一只小小鸟

多媒体应用设计师软考备考日记(一)

Changing Lin

10月月更

项目开发中 9 个实用的JavaScript函数

devpoint

JavaScript 函数式 随机数 10月月更

5分钟搞懂Monorepo

俞凡

git 架构

【LeetCode】二叉搜索树中第K小的元素Java题解

Albert

算法 LeetCode 10月月更

趣讲快速排序的两种方法

Regan Yue

快速排序 10月月更

智慧城市的成人礼:城市智能体带来的变革与机遇

脑极体

关于ToB产品与服务的一些思考

石云升

产品思维 职场经验 tob产品 10月月更

019云原生之Kubernetes主要功能

穿过生命散发芬芳

云原生 10月月更

【设计模式】第五篇 - 工厂方法模式

Brave

设计模式 工厂方法模式 10月月更

Leetcode 题目解析:230. 二叉搜索树中第K小的元素

程序员架构进阶

算法 LeetCode Top K问题 10月月更

Android SDK设计规范与心得

轻口味

android sdk 10月月更

官方线索|科大讯飞全球1024开发者节

xcbeyond

1024我在现场

Gas 机制是如何运作的

Rayjun

以太坊

01-设计模式概述

千羽的编程时光

设计模式

谈 C++17 里的 Memento 模式

hedzr

设计模式 备忘录模式 Design Patterns c++17 Undo Manager

as-if-serial &happens-before详解

Fox爱分享

happens-before JMM as-if-serial

linux之crontab使用技巧

入门小站

Linux

【产品】论增长黑客思维如何让B端产品爆发式增长

极光一号。

云原生 用户增长 b端产品经理 增长黑客

阿里P8面试官:如何设计一个扛住千万级并发的架构?

Java 程序员 架构 面试 高并发

区块链是数字治理的平台

CECBC

那些值得称赞的 python 小亮点,断言,with 语句,格式化字符串,听我道来

梦想橡皮擦

10月月更

看山聊 Java:IPv4 与 int 之间互相转换

看山

Java 10月月更

云原生训练营 20211017- docker作业

好吃不贵

Vue进阶(幺肆贰):elementUI 中 el-switch、el-radio 使用注意事项

No Silver Bullet

Vue 10月月更

面试必备!2021金九银十阿里最新Java面试手册(全彩版)出炉

Java 程序员 架构 面试

在线数字转大写金额工具

入门小站

工具

【架构实战营作业】模块五——微博评论计算架构

聆息

02-面向对象设计原则

千羽的编程时光

设计模式

人物访谈|扎根社区的工程师,月影的前端人生

字节跳动终端技术

字节跳动 大前端

Android Studio IDE 插件开发

字节跳动终端技术

android 字节跳动 客户端开发 火山引擎

Jigsaw项目会解决Java的JAR地狱问题么?_Java_张龙_InfoQ精选文章