Q每周精要。
马上订阅

中文
Java 9终于要包含Jigsaw项目了 1喜欢
登陆InfoQ,与你最关心的话题互动。
E-mail
密码
使用Google账号登录使用Microsoft账号登录使用Weibo账号登录
忘记密码?没有帐号?立即注册


关注你最喜爱的话题和作者
快速浏览网站内你所感兴趣话题的精选内容。

内容自由定制
选择想要阅读的主题和喜爱的作者定制自己的新闻源。

获取更新
设置通知机制以获取内容更新对您而言是否重要
作者 Nicolai Parlog ,译者 张卫滨 发布于 2016年2月3日
估计阅读时间: 2 分钟
添加至阅
读列表
查看我的
阅读列表
当Jigsaw在Java 9中最终发布时,这个项目的历史已经超过八年了。
在最初的几年中,它必须要与另外两个类似的Java规范请求(Java Specification Request)进行竞争,这两个规范名为JSR 277 Java模块系统(Java Module System)以及JSR 294 增强的模块化支持(Improved Modularity Support)。它还导致了与OSGi社区的冲突,人们担心Jigsaw项目会成为不必要且不完备的功能性重复,逼迫Java开发人员必须在两种互不兼容的模块系统中做出选择。
在早期,这个项目并没有充足的人手,在2010年Sun并入Oracle的时候,甚至一度中断。直到2011年,在Java中需要模块系统的强烈需求被重申,这项工作才得到完全恢复。
接下来的三年是一个探索的阶段,结束于2014年的7月,当时建立了多项Java增强提议( Java Enhancement Proposal),包括JEP 200 模块化JDK(Modular JDK)、JEP 201 模块化源码(Modular Source Code)和JEP 220 模块化运行时镜像(Modular Run-Time Image),以及最终的JSR 376 Java平台模块系统(Java Platform Module System)。上述的最后一项定义了真正的Java模块系统,它将会在JDK中以一个新JEP的形式来实现。
在2015年7月,JDK划分为哪些模块已经大致确定(参见JEP 200),JDK的源码也进行了重构来适应这种变化(参见JEP 201),运行时镜像(run-time image)也为模块化做好了准备(参见JEP 220)。所有的这些都可以在当前JDK 9的预览版中看到。
针对JSR 376所开发的代码很快将会部署到JDK仓库中,但是令人遗憾的是,现在模块化系统本身尚无法体验。(目前,Java 9的预览版本已经包含了模块化功能。——译者注)
驱动力
在Jigsaw项目的历史中,它的驱动力也发生过一些变化。最初,它只是想模块化JDK。但是当人们意识到如果能够在库和应用程序的代码中也使用该工具的话,将会带来非常大的收益,于是它的范围得到了扩展。
不断增长且不可分割的Java运行时
Java运行时的大小在不断地增长。但是在Java 8之前,我们并没有办法安装JRE的子集。所有的Java安装包中都会包含各种库的分发版本,如XML、SQL以及Swing的API,不管我们是否需要它们,都要将其包含进来。
对于中等规模(如桌面PC和笔记本电脑)以上的计算设备来说,这不算是什么严重的问题,但是对于小型的设备来说,这就很严重了,比如在路由器、TV盒子和汽车上,还有其他使用Java的小地方。随着当前容器化的趋势,在服务器领域也有相关的要求,因为减少镜像的大小就意味着降低成本。
Java 8引入了 compact profile的功能,它们定义了三个Java SE的子集。在一定程度上缓解了这个问题,但是它们只有在严格限制的场景下才能发挥作用,profile过于死板,无法涵盖现在和未来所有使用JRE部分功能的需求。
JAR/Classpath地狱
JAR地狱和Classpath地狱是一种诙谐的说法,指的是Java类加载机制的缺陷所引发的问题。尤其是在大型的应用中,它们可能会以各种方式产生令人痛苦的问题。有一些问题是因为其他的问题而引发的,而有一些则是独立的。
无法表述依赖
JAR文件无法以一种JVM能够理解的方式来表述它依赖于哪些其他的JAR。因此,就需要用户手动识别并满足这些依赖,这要求用户阅读文档、找到正确的项目、下载JAR文件并将其添加到项目中。
而且,有一些依赖是可选的,只有用户在使用特定功能的特性时,某个JAR才会依赖另外一个JAR。这会使得这个过程更加复杂。
Java运行时在实际使用某项依赖之前,并不能探测到这个依赖是无法满足的。如果出现这种情况,将会出现NoClassDefFoundError异常,进而导致正在运行的应用崩溃。
像Maven这样的构建工具能够帮助解决这个问题。
传递性依赖
一个应用程序要运行起来可能只需依赖几个库就足够了,但是这些库又会需要一些其他的库。问题组合起来会变得更加复杂,在所消耗的体力以及出错的可能性上,它会呈指数级地增长。
同样,构建工具能够在这个问题上提供一些帮助。
遮蔽
有时候,在classpath的不同JAR包中可能会包含全限定名完全相同的类,比如我们使用同一个库的两个不同版本。因为类会从classpath中的第一个JAR包中加载,所以这个版本的变种将会“遮蔽”所有其他的版本,使它们变得不可用。
如果这些不同的变种在语义上有所差别,那将会导致各种级别的问题,从难以发现的不正常行为到非常严重的错误都是有可能的。更糟糕的是,问题的表现形式是不确定的。这取决于JAR文件在classpath中的顺序。在不同的环境下,可能也会有所区别,例如开发人员的IDE与代码最终运行的生产机器之间就可能有所差别。
版本冲突
如果项目中有两个所需的库依赖不同版本的第三个库,那么将会产生这个问题。
如果这个库的两个版本都添加到classpath中的话,那么最终的行为是不可预知的。首先,因为前面所述的遮蔽问题,两个版本的类中,只会有一个能够加载进来。更糟糕的是,如果某个类位于一个JAR包中,但是它所访问的其他类却不在这个包中,这个类也能够加载。所导致的结果就是,对这个库的代码调用将会混合在两个版本之中。
在最好的情况下,如果试图访问所加载的类中不存在的代码,将会导致明显的NoClassDefFoundError错误。但是在最坏的情况下,版本之间的差别仅仅是在语义上,实际的行为会有细微的差别,这会引入很难发现的bug。
识别这种情况所导致的难以预料的行为是很困难的,也无法直接解决。
复杂的类加载机制
默认情况下,所有的类由同一个ClassLoader负责加载,在有些场景下,可能有必要引入额外的加载器,例如允许用户加载新的类,对应用程序进行扩展。
这很快就会导致复杂的类加载机制,从而产生难以预期和难以理解的行为。
在包之间,只有很弱的封装机制
如果类位于同一个包中,那Java的可见性修饰符提供了一种很棒的方式来实现这些类之间的封装。但是,要跨越包之间边界的话,那只能使用一种可见性:public。
因为类加载器会将所有加载进来的包放在一起,public的类对其他所有的类都是可见的,因此,如果我们想创建一项功能,这项功能对某个JAR是可用的,而对于这个JAR之外是不可用的,这是没有办法实现的。
手动的安全性
包之间弱封装性所带来的一个直接结果就是,安全相关的功能将会暴露在同一个环境中的所有代码面前。这意味着,恶意代码有可能绕过安全限制,访问关键的功能。
从Java 1.1开始,有一种hack的方式,能够防止这种状况:每当进入安全相关的代码路径时,将会调用SecurityManager,并判断是不是允许访问。更精确地讲,它应该在每个这样的路径上都进行调用。过去,在有些地方遗漏了对它们的调用,从而出现了一些漏洞,这给Java带来了困扰。
启动性能
最后,Java运行时加载并JIT编译全部所需的类需要较长的时间。其中一个原因在于类加载机制会对classpath下的所有JAR执行线性的扫描。类似的,在识别某个注解的使用情况时,需要探查classpath下所有的类。
目标
Jigsaw项目的目标就是解决上面所述的问题,它会引入一个语言级别的机制,用来模块化大型的系统。这种机制将会用在JDK本身中,开发人员也可以将其用于自己的项目之中。
需要注意的是,对于JDK和我们开发人员来说,并不是所有的目标都具有相同的重要性。有一些与JDK具有更强的相关性,并且大多数都对日常的编程不会带来巨大的影响(这与最近的语言修改形成了对比,如lambda表达式和默认方法)。不过,它们依然会改变大型项目的开发和部署。
可扩展性的平台
JDK在模块化之后,用户就能挑出他们需要的功能,并创建自己的JRE,在这个JRE中只包含他们需要的模块。这有助于在小型设备和容器领域中,保持Java作为关键参与者的地位。
在这个提议的规范中,允许将Java SE平台及其实现分解为一组组件,开发人员可以把这些组件组装起来,形成自定义的配置,里面只包含应用实际需要的功能。—— JSR 376
可靠的配置
通过这个规范,某个模块能够声明对其他模块的依赖。运行时环境能够在编译期(compile-time)、构建期(build-time)以及启动期(launch-time)分析这些依赖,如果缺少依赖或依赖冲突的话,很快就会发生失败。
强封装
Jigsaw项目的一个主要目标就是让模块只导出特定的包,其他的包是模块私有的。
模块中的私有类就像是类中的私有域。换句话说,模块的边界不仅确定了类和接口的可见性,还定义了它的可访问性。——Mark Reinhol