java jigsaw_Jigsaw项目确实在Java 9中出现

java jigsaw

当Jigsaw项目最终以Java 9发行时,将有8年多的历史了。

在开始的第一年,它必须与两个类似的Java规范请求竞争,即JSR 277 Java模块系统JSR 294改进的模块化 支持 。 这也导致了与OSGi社区冲突 ,后者担心Project Jigsaw将是不必要且功能不足的重复功能,从而迫使Java开发人员使用两个不兼容的模块系统之一。

在最初的几年中,该项目的人员不足,甚至在2010年将Sun合并为Oracle时中止。 直到2011年,才重新提出了对Java模块系统的迫切需求,并在全体人员的陪同下恢复了工作。

随后是三年的探索阶段,该阶段于2014年7月结束,当时发布了多个Java增强提案( JEP 200模块化JDKJEP 201模块化源代码JEP 220模块化运行时图像 ),并最终发布了JSR 376 Java平台模块系统 。 。 最后提到的定义了将在新的JEP下在JDK中实现的实际Java模块系统。

截至2015年7月,已基本确定了将JDK拆分为的模块(请参阅JEP 200),已对JDK源代码进行了重组以适应它们(请参见JEP 201),并且已为模块化准备了运行时映像(请参见JEP 220)。 )。 所有这些都可以在当前的JDK 9早期访问版本中获得

作为JSR 376的一部分开发的代码预计将很快部署到JDK存储库中 ,但是不幸的是,到目前为止,还没有办法对模块系统本身进行试验。

动机

拼图计划的动机在其历史上略有变化。 最初仅是为了模块化JDK。 当很明显,库和应用程序将通过在其自己的代码上使用此工具而受益匪浅时,该范围得到了扩展。

不断增长且不可分割的Java运行时

Java运行时的规模一直在增长。 但是在Java 8之前,无法安装JRE的子集。 不管您是否需要,所有Java安装都随XML,SQL和Swing等API的库一起分发。

尽管这对于中型计算设备(例如台式机或笔记本电脑)可能不是十分重要,但对于路由器,电视盒,汽车以及使用Java的所有其他微小角落和缝隙等小型设备而言,却非常重要。 在当前的容器化趋势下,它在服务器上也获得了新的关注,减少映像的占用空间将降低成本。

Java 8带来了 紧凑配置文件 ,它定义了Java SE的三个子集。 这些在某种程度上但仅在有限的情况下缓解了该问题,而且配置文件过于僵化,无法满足当前和将来对部分JRE的所有需求。

JAR /类路径地狱

JAR Hell和Classpath Hell是可爱的术语,指的是Java的类加载机制不足引起的问题。 特别是对于大型应用程序,它们可能以许多有趣的方式引起很多痛苦。 有些问题是相互依存的。 其他人是独立的。

未表达的依赖性

JAR无法以JVM可以理解的方式来表示它依赖的其他JAR。 因此,通过阅读文档,找到正确的项目,下载JAR并将其添加到项目中,用户可以手动识别并实现依赖关系。

然后是可选的依赖项,如果用户要使用某些功能,则JAR可能仅需要另一个JAR。 这进一步使过程复杂化。

直到实际需要时,Java运行时才会检测到未实现的依赖关系。 这将导致NoClassDefFoundError崩溃正在运行的应用程序。

像Maven这样的构建工具可以帮助解决此问题。

传递依存关系

为了使应用程序正常工作,可能只需要少数几个库。 每个反过来可能需要少量其他库,依此类推。 随着问题的复杂化,它的工作量越来越大,而且容易出错。

再次,这是由构建工具来帮助的。

遮蔽

有时,类路径上的不同JAR包含具有完全相同名称的类,例如,当它们是同一库的两个不同版本时。 由于类将从类路径上的第一个JAR加载,以包含它们,因此该变体将“阴影”所有其他类并使它们不可用。

如果变体在语义上有所不同,则可能导致从微妙到通知错误的行为到破坏破坏性错误的一切。 更糟糕的是,此问题本身表现出来的形式似乎不确定。 它取决于JAR在类路径中列出的顺序。 这在不同的环境中可能会有所不同,例如在开发人员的IDE和最终将运行代码的生产机之间。

版本冲突

当两个所需的库依赖于第三个库的不同版本时,就会出现此问题。

如果将两个版本都添加到类路径中,则该行为将不可预测。 首先,由于阴影问题,两个版本中都存在的类将仅从其中一个加载。 更糟糕的是,如果访问一个中存在但另一个不存在的类,则该类也将被加载。 因此,调用该库的代码将找到两个版本的混合。

充其量,如果库代码尝试访问已加载类中不存在的代码,则可能会由于NoClassDefFoundError大声失败。 在最坏的情况下,版本仅在语义上有所不同,实际行为可能会被微妙地更改,从而引入难以发现的错误。

将其识别为意外行为的来源可能很困难。 直接解决它是不可能的。

复杂类加载

默认情况下,所有类均由同一ClassLoader 。 在某些情况下,可能有必要添加其他加载程序,例如,允许用户通过加载新类来扩展应用程序。

这会Swift导致复杂的类加载机制,从而产生意外的和难以理解的行为。

跨封装的弱封装

Java的可见性修饰符非常适合在同一包中的类之间实现封装。 但是跨程序包边界只有一种可见性: public

由于类加载器将所有已加载的程序包折叠成一个大泥球,因此所有其他类都可以看到所有公共类。 无法创建可见的功能,例如,在整个JAR中可见但不在其外部。

手动安全

跨软件包边界的弱封装的直接后果是,与安全相关的功能将暴露给在同一环境中运行的所有代码。 这意味着恶意代码可以访问关键功能,从而可能使其绕过安全措施。

从Java 1.1开始,这已被黑客阻止:在每个代码路径上将SecurityManager调用到与安全相关的代码中,并检查是否允许访问。 或者更确切地说: 应该每一个这样的路径上调用。 某些地方忽略了这些调用,从而导致了过去困扰Java的某些漏洞。

启动表现

最后,当前需要一些时间才能加载Java运行时并JIT编译所有必需的类。 原因之一是,类加载对类路径上的所有JAR执行线性扫描。 类似地,识别所有出现的特定注释需要检查类路径上的所有类。

目标

Jigsaw项目旨在通过引入语言级机制来模块化大型系统来解决上述问题。 此机制将在JDK本身上使用,开发人员也可以在自己的项目上使用。

重要的是要注意,并非所有目标对于JDK和我们的开发人员都同样重要。 许多与JDK更为相关,并且大多数不会对日常编码产生巨大影响(与lambda表达式和默认方法之类的最新语言修改相反)。 尽管如此,它们仍将改变大型项目的开发和部署方式。

可扩展平台

通过将JDK模块化,用户将有可能选择自己需要的功能,并创建仅由所需模块组成的自己的JRE。 这将有助于保持Java作为小型设备和容器的关键角色的地位。

提出的规范将允许Java SE平台及其实现分解为一组组件,开发人员可以将这些组件组装成自定义配置,这些自定义配置仅包含应用程序实际需要的功能。 -JSR 376

可靠的配置

该规范将使各个模块能够声明其对其他模块的依赖性。 运行时将能够在编译时,构建时和启动时分析这些依赖关系,因此可以因缺少或冲突的依赖关系而快速失败。

强封装

Project Jigsaw的主要目标之一是使模块仅导出特定的软件包。 然后,所有其他软件包对模块都是私有的。

模块私有的类应该以私有字段完全私有的方式私有。 换句话说,模块边界不仅应确定类和接口的可见性,还应确定其可访问性。 -Mark Reinhold –拼图计划:将全局视为焦点

改进的安全性和可维护性

模块内部API的强大封装将大大提高安全性,因为关键代码现在已从不需要使用的代码中有效地隐藏了。 由于模块的公共API可以更容易地保持较小,这也使维护更加容易。

随意使用Java SE Platform实现内部的API既有安全风险,又有维护负担。 提议的规范提供的强大封装将允许实现Java SE平台的组件阻止对其内部API的访问。 -JSR 376

性能提升

通过明确使用代码的范围,可以更有效地利用现有的优化技术。

当已知某个类只能引用其他一些特定组件中的类,而不引用运行时加载的任何类时,许多提前进行的全程序优化技术可能更有效。 -JSR 376

核心理念

由于模块化是目标,因此Jigsaw项目将介绍模块的概念,这些模块是:

命名的,自描述的程序组件,由代码和数据组成。 模块必须能够包含组织成包的Java类和接口,以及动态加载库形式的本机代码。 模块的数据必须能够包含静态资源文件和用户可编辑的配置文件。 -Java平台模块系统:要求(草案2)

为了给模块一些环境,请考虑一些知名的库,例如Google Guava Apache Commons (例如Collections或IO)作为模块。 根据作者想要拆分的粒度,每个作者本身都可以分为几个模块。

应用程序也是如此。 它可能是单个整体模块,但也可能被拆分。 项目的规模和凝聚力将是决定如何将其拆分为模块的重要因素。

计划是模块将成为开发人员中用来组织代码的常规工具。

开发人员已经在语言方面考虑了标准种类的程序组件,例如类和接口。 模块应该只是另一种程序组件,像类和接口一样,它们应该在程序开发的所有阶段都具有含义。 - 马克·雷因霍尔德Mark Reinhold)的拼图项目:聚焦全局

然后,可以在开发的所有阶段(即在编译时,构建时,安装时或运行时)将模块组合成各种配置。 它们将对像我们这样的Java用户可用(在这种情况下有时称为开发人员模块 ),但也将用于剖析Java运行时本身(通常称为平台模块 )。

实际上,这是JDK模块化的当前计划。

(点击图片放大)

特征

那么模块如何工作? 看项目拼图要求 JSR 376将帮助我们对他们有所了解。

依赖管理

为了解决“ JAR / Classpath地狱”的问题,Project Jigsaw的核心功能之一是依赖管理。 让我们看一下组件。

声明与决议

一个模块将声明需要编译和运行其他哪些模块 。 模块系统将使用它来传递性地标识编译或运行初始模块所需的所有模块

也有可能不依赖于特定模块而是依赖于一组接口。 然后该模块系统将尝试识别模块 实现这些接口并由此满足的依赖性 适当地结合它们的界面

版本控制

模块 将被 版本化 。 他们将能够指示自己的版本(只要完全排序即可使用几乎任何格式),以及对其依赖项的约束。 在任何阶段都可以覆盖这两条信息。 模块系统将在每个阶段强制配置满足所有约束。

拼图项目不一定会在单个配置中支持模块的多个版本。 但是,等等,那这如何解决JAR Hell? 好问题!

规范不要求版本选择-从同一模块的一组不同版本中选择适当的版本的操作。 因此,当我在上面写到模块系统将确定编译或运行另一个模块所需的模块时,这是基于每个模块只有一个版本的假设。 如果存在多个,则上游步骤(例如,开发人员,或者更可能是他使用的构建工具)必须进行选择,并且系统只会验证它是否满足所有约束。

封装形式

模块系统将在所有阶段强制执行强封装。 这围绕一种导出机制,在该机制中只能访问模块的导出软件包。 封装独立地由任何执行的安全核查任务强加S ecurity M anager可能存在。

该提案的确切语法尚未定义,但JEP 200提供了一些主要语义的XML表示形式。 作为示例,以下是java.sql模块的声明。

<module>

  <!-- The name of this module -->
  <name>java.sql</name>

  <!-- Every module depends upon java.base -->
  <depend>java.base</depend>

  <!-- This module depends upon the java.logging and java.xml
       modules, and re-exports their exported API packages -->
  <depend re-exports="true">java.logging</depend>
  <depend re-exports="true">java.xml</depend>

  <!-- This module exports the java.sql, javax.sql, and
       javax.transaction.xa packages to any other module -->
  <export><name>java.sql</name></export>
  <export><name>javax.sql</name></export>
  <export><name>javax.transaction.xa</name></export>

</module>

从该片段中我们可以看到java.sql依赖于j ava.base, java.loggingjava.xml 。 在介绍了不同的出口机制之后,我们将了解其余的声明。

出口

一个模块将声明要导出的特定程序包 ,并且只导出其中包含的类型。 这意味着只有它们将对其他模块可见并且可访问。 更严格地说,类型将仅导出到明确依赖于包含它们的模块的那些模块

有趣的是,不同的模块将能够包含具有相同名称的包,甚至允许它们导出它们。

在上面的示例中, java.sql导出包java.sqljavax.sqljavax.transaction.xa

再出口

一个模块也可以重新导出其依赖的任何其他模块的API(或其部分)。 这将通过提供在不破坏依赖关系的情况下拆分和合并模块的能力来支持重构 ,因为原始模块可以继续存在。 即使它们可能不包含所有代码,它们也将导出与以前完全相同的软件包。 在极端情况下,所谓的聚合器模块可能根本不包含任何代码,并且只能作为一组模块的抽象。 实际上,来自Java 8的紧凑配置文件就是这样。

从示例中我们可以看到java.sql重新导出了依赖项java.loggingjava.xml的API。

合格出口

为了帮助开发人员(尤其是那些模块化JDK的开发人员)保持较小的导出API曲面,可选的合格导出机制将允许模块指定要单独导出到声明的模块集的其他软件包。 因此,尽管使用“标准”机制,导出模块将不知道(也不关心)谁访问软件包,但是使用合格的导出将允许模块限制可能的依赖项集合。

配置,阶段和保真度

如前所述, JEP 200目标 是在开发的所有阶段都可以将模块组合成各种配置。 对于平台模块而言,这是正确的,可用于创建与完整JRE或JDK,Java 8中引入的紧凑配置文件相同的映像,或仅包含指定模块集(及其传递依赖项)的任何自定义配置。 同样,开发人员可以使用该机制来组合自己的模块化应用程序的不同变体。

编译 - 时间 ,被编译的代码只能看到由一组被配置的模块导出类型。 在构建时 ,一个新工具(可能称为JLink )将允许创建包含特定模块及其依赖项的二进制运行时映像。 在启动 - 时间 ,图像可以使看起来好像它仅包含了模块的子集。

也可以替换实现以下功能的模块 认可标准 每个阶段都有更新版本的独立技术 。 这将替换已弃用的认可标准替代机制和扩展机制(请参见下文)。

除非由于特定原因无法实现,否则模块系统的所有方面(例如依赖项管理,封装等) 在所有阶段都将以相同的方式工作

所有模块特定的信息(例如版本,依赖项和包导出)都将在代码文件中表示,而与IDE和构建工具无关。

性能

整个程序优化技术

在具有强封装性的模块系统中,自动推断将要使用特定代码段的所有位置要容易得多。 这使得某些程序分析和优化技术更加可行:

快速查找JDK和应用程序类; 早期字节码验证; 主动内联例如lambda表达式和其他标准编译器优化; 构造特定于JVM的内存映像,该映像可以比类文件更有效地加载; 提前将方法主体编译为本地代码; 并删除未使用的字段,方法和类。 - 拼图项目:目标与要求(草案3)

这些被标记为整个程序的优化技术 ,并且至少有两种这样的技术将在Java 9中实现。它还将包含一个工具,该工具可以分析给定的一组模块,并应用这些优化来创建性能更高的二进制映像。

注解

自动发现带注释的类(例如,Spring带注释的配置类)目前需要扫描某些指定软件包中的所有类。 这通常是在程序启动时完成的,并且可能会大大降低它的速度。

模块将具有一个API,允许调用者使用给定的注释标识所有类。 一种设想的方法是创建将在编译模块时创建的此类的索引。

与现有概念和工具的集成

诊断工具(例如堆栈跟踪) 将被升级,以传达有关模块的信息。 此外,它们将被完全集成到反射API中,该反射API可用于以与类相同的方式来操作它们。 这将包括可以在运行时反映并覆盖版本信息

该模块的设计将允许“以最小的麻烦”使用构建工具 。 模块的编译形式可以在类路径上使用,也可以作为模块使用,这样库开发人员就不必为类路径和基于模块的应用程序创建多个构件

还计划与其他模块系统(尤其是OSGi)实现互操作性

尽管模块可以隐藏其他模块的信息,将有可能进行白盒测试 ING包含的类和接口。

特定于操作系统的包装

该模块系统在设计时考虑了软件包管理器文件格式 “ RPM,Debian和Solaris IPS”。 开发人员不仅可以使用现有工具从一组模块中创建特定于操作系统的软件包。 这样的模块也将能够调用以相同机制安装的其他模块。

开发人员还将能够将构成应用程序的一组模块打包到特定于OS的软件包中,“最终用户可以按照目标系统的惯常方式来安装和调用这些模块”。 基于以上内容,仅必须打包目标系统上不存在的那些模块。

动态配置

运行中的应用程序将可能创建,运行和发布多个隔离的模块配置 。 这些配置可以包含开发人员和平台模块。 这对于诸如IDE,应用程序服务器或Java EE平台的容器体系结构将非常有用。

不兼容

与Java一样,这些更改的实现重点是向后兼容性。 所有标准化和不推荐使用的API和机制都将继续起作用。 但是项目可能依赖于其他未记录的构造,在这种情况下,要切换到Java 9,需要做一些工作。

内部API变得不可用

通过强大的封装,每个模块将能够显式声明哪些类型作为其API的一部分可用。 JDK将使用此功能来正确封装所有内部API,因此将不可用。

事实证明,这可能是与Java 9不兼容的最大根源。由于它会引起编译错误,因此它肯定是最不敏感的。

那么什么是内部API? 绝对是所有生活在sun.* -包装。 如果它位于com.sun.*并带有@jdk.Exported ,它将在Oracle JDK上仍然可用; 如果没有注释,它将不可用

一个可能证明特别有问题的示例是sun.misc.Unsafe 。 它在相当多的项目,任务和性能关键代码和其挂起交通不便刮起了使用相当 一个 讨论 。 但有人指出,在进行这种交换时,仍然可以通过专用的命令行标志来使用它 。 考虑到并非所有功能都可以进入公共API中,所以这可能是必要的邪恶。

另一个示例是com.sun.javafx.*所有内容。 这些类是正确构建JavaFX控件的重要组成部分,还需要解决许多错误。 这些类的大多数功能都面向发布

JDK和JRE的合并

借助可伸缩的Java运行时,它允许灵活地创建运行时映像,JDK和JRE失去了与众不同的特性,而成为一系列模块组合中的两个可能的点。

这意味着两个工件都将具有相同的结构,包括文件夹结构以及依赖于该文件夹结构的任何代码(例如,通过利用JDK文件夹包含子文件夹jre的事实)将无法正常工作。

内部JAR变得不可用

内部JAR(如lib/rt.jarlib/tools.jar将不再可访问。 它们的内容将以故意未指定且可能更改的格式存储在特定于实现的文件中。

任何假定这些文件存在的代码都将停止正常工作。 这也可能导致在IDE或严重依赖这些文件的类似工具中出现过渡上的麻烦。

运行时图像内容的新URL架构

一些API在运行时将URL返回到类和资源文件(例如 ClassLoader.getSystemResource )。 在Java 9之前,这些是jar URL ,它们具有以下形式:

jar:file:<path-to-jar>!<path-to-file-in-jar>

Jigsaw项目将使用模块作为代码文件的容器,并且不再提供各个JAR。 这需要一种新格式,因此此类API会返回jrt URL

jrt:/<module-name>/<path-to-file-in-module>

使用此类API返回的实例来访问文件的代码(例如 URL.getContent )将继续保持今天的状态。 但是,如果它取决于jar URL的结构 (例如,通过手动构造它们或解析它们),它将失败。

取消认可的标准替代机制

Java API的某些部分被标记为“ 独立技术”,并在Java Community Process之外创建(例如 JAXB )。 可能需要独立于JDK进行更新或使用替代实现。 的 认可的标准覆盖机制允许将这些标准的替代版本安装到JDK中。

此机制在Java 8中已弃用,在Java 9中将被删除,由上述可升级模块代替。

删除扩展机制

随着 扩展机制自定义API可以提供给JDK上运行的所有应用程序使用,而不必在类路径上命名它们。

此机制在Java 8中已弃用,在Java 9中将被删除。一些有用的功能将保留。

下一步

我们浏览了Jigsaw项目的历史,了解了它的动机,并讨论了其目标以及如何通过特定功能实现这些目标。 除了等待Java 9之外,我们还能做什么?

准备

我们应该准备我们的项目,并检查它们是否依赖Java 9中不可用或已删除的任何内容。

至少不必手动搜索对内部API的依赖关系。 从Java 8开始,JDK包含Java依赖关系分析工具JDeps一些内部软件包的介绍 Windows Unix ),它可以列出项目所依赖的所有软件包。 如果使用参数jdkinternals运行,它将输出项目使用的几乎所有内部​​API。

“几乎所有”,因为它尚未识别Java 9中将不可用的所有软件包。这至少影响了属于JavaFX的那些软件包,如在 JDK-8077349 。 ( 使用此搜索,我找不到与缺少功能有关的其他问题。)

还有至少三个用于Maven的JDeps插件: ApachePhilippe Marschall我本人 。 如果jdeps - jdkinternals报告对内部API的依赖关系,则后者是当前唯一无法通过构建的版本。

讨论

有关拼图项目的最新信息的主要来源是 Jigsaw-Dev邮件列表 。 我还将继续 博客 讨论此主题。

如果您担心某些特定的API在Java 9中不可用,则可以检查相应的OpenJDK项目邮件列表,因为它们将负责开发它们的公共版本。

采用

Java 9早期访问版本已经可用。 尽管JSR 376仍在开发中,而实际的模块系统在那些构建中尚不可用,但许多准备工作却在进行。 实际上,除了强封装之外,其他任何东西都已经存在。

通过将这种方式收集的信息发布到Jigsaw-Dev邮件列表中,可以将其返回给项目。 引用JEP 220的(几乎)最后的话:

不可能确定摘要中这些更改的全部影响。 因此,我们必须依靠广泛的内部测试,尤其是外部测试。 […]如果其中某些更改对开发人员,部署人员或最终用户而言是无法克服的障碍,那么我们将研究减轻其影响的方法。

还有全球Java用户组AdoptOpenJDK ,对于早期采用者来说是一个很好的联系。

翻译自: https://www.infoq.com/articles/Project-Jigsaw-Coming-in-Java-9/?topicPageSponsorship=c1246725-b0a7-43a6-9ef9-68102c8d48e1

java jigsaw

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值