Java 9中的模块化

Java 9的主要创新是引入了模块化。关于此功能的讨论很多,发布日期被推迟了几次,以正确完成所有操作。在本文中,我们将讨论如何提供模块机制以及Java 9带来了哪些有用的东西。该职位的依据是我的同事谢尔盖·马尔科维奇Sergei Malkevich)的报告




为了在此版本的Java中实现模块,分配了一个完整的项目-Project Jigsaw,其中包括几个JEP和JSR。



对于官方文档的拥护者,您可以在此处阅读有关每个JEP的更多信息

有关拼图项目的更多信息


实施模块化的Jigsaw项目于2005年开始开发:第一个JSR 277发布,并且在2008年,开始了对该项目的直接工作。发布仅在2017年进行。也就是说,为了用Java固定模块,花了将近10年的时间。实际上,这强调了工作的全部规模以及在实施模块化过程中所做的更改。

开发人员设定的目标是什么:

  • 促进大型应用程序和库的开发;
  • 总体上提高Java SE的安全性,尤其是JDK的安全性;
  • 提高应用程序性能;
  • 创建减小在小型设备上运行的JRE大小的能力,以免消耗过多的内存;
  • JAR HELL(稍后会详细介绍)。

Java 9带来了什么


在版本9之前,JDK和JRE是整体的。每个发行版的大小都会增加。Java 8已经占用了数百兆字节,开发人员每次都必须“随身携带”所有这些才能运行Java应用程序。rt.jar仅重约60 Mb。好吧,这里我们还增加了启动速度慢和内存消耗高的问题。Java 9获救了

JDK 9中引入了模块分离,即JDK分为73个模块。随着每个新版本的发布,这些模块的数量也在增长。在版本11中,该数字接近100。这种分隔允许开发人员创建JLINK实用程序。使用JLINK,您可以创建自定义JRE集,其中仅包含应用程序真正需要的“必需”模块。因此,一个简单的应用程序和一些带有最少(或很小)模块集的customJRE最终可以容纳20 Mb,这是一个好消息。

可以在此处找到模块列表

随着Java 9的出现,JDK结构发生了变化:现在,它与JRE结构相同。如果早期的JDK包含JRE文件夹,其中bin再次存在并且文件被复制,那么现在一切看起来像这样:



模组


其实。什么是模块?模块是软件包和资源的聚合的新高度(原始名称:“相关软件包的唯一命名的,可重用的组,以及资源和模块描述符”)。

模块以JAR文件的形式提供,其中包含软件包和模块描述符
module-info.javamodule-info.java文件包含对模块的描述:
名称,依赖项,导出的程序包,已使用和提供的服务,反射访问权限。

模块描述符描述的示例:

module java.sql {
    requires transitive java.logging;
    requires transitive java.transaction.xa;
    requires transitive java.xml;

    exports java.sql;
    exports javax.sql;

    uses java.sql.Driver;
}

module jdk.javadoc {
   requires java.xml;
   
   requires transitive java.compiler;
   requires transitive jdk.compiler;
   
   exports jdk.javadoc.doclet;
   
   provides java.util.spi.ToolProvider with
       jdk.javadoc.internal.tool.JavadocToolProvider;
   
   provides javax.tools.DocumentationTool with
       jdk.javadoc.internal.api.JavadocTool;
   
   provides javax.tools.Tool with
      jdk.javadoc.internal.api.JavadocTool;   
}

在关键字模块之后,我们有jdk.javadoc包的名称,该包依赖于另一个java.xml包,并且可传递地依赖于其他包。

让我们仔细看一下每个关键字:

  • require指示当前模块所依赖的模块;

  • 需要传递性 -传递性依赖关系-表示以下含义:如果模块m1可传递地依赖于模块m2,并且我们有一些依赖于m1的第三个模块mX则模块mX也可以访问m2

  • require static允许您指定编译时依赖项;

  • exports , ( “”);

  • exports...to… : export com.my.package.name to com.specific.package; - () () ;

  • uses , :

    uses java.sql.Driver;

    , ;

  • provides , :

    provides javax.tools.Tool with
        jdk.javadoc.internal.api.JavadocTool;

    javax.tools.Tool, with — .

关于服务的一些知识,

假设我们连接了几个实现抽象服务的模块MyService。在构建应用程序时,我们有机会通过将所需的服务实现模块“拖到” --module-path来决定使用哪个服务实现

Iterable<MyService> services = 
        ServiceLoader.load(MyService.class);

因此,返回的Iterator包含MyService接口的实现列表。实际上,它将包含在--module-path上的模块中找到的所有实现

原则上为什么要引入服务?需要它们来说明如何使用我们的代码。即,存在语义作用。而且,模块化是关于封装和安全性的,因为我们可以使实现私有化,并排除通过反射进行未经授权的访问的可能性。

同样,使用服务的一种选择是相当简单的插件实现。我们可以为我们的应用程序实现插件接口,并连接模块以使用它们。

让我们返回描述模块的语法:

直到9ki,通过反思,我们几乎可以访问所有内容,并且可以做我们想做的任何事情。如前所述,第9版允许您保护自己免受“非法”反射访问的侵害。

我们可以通过声明open来完全打开用于反射访问的模块

open module my.module {
}

或者,我们可以通过声明opens来指定任何用于反射访问的包

module my.module {
    opens com.my.coolpackage;
}

这也可以使用打开 com.my.coolpackage ......,到从而使反射访问com.my.coolpackage从包中,我们会后表示

模块类型


拼图项目将模块分类如下:

  • System Modules — Java SE JDK . , java --list-modules.

  • Application Modules — , , ( ), .

  • Automatic Modules — , Java JAR-. , , - . JAR- --module-path Java , JAR-.

  • Unnamed Module — , JAR-, --class-path. Java .

Class-path vs module-path


随着模块的出现,出现了一个新概念-module -path。本质上,这是相同的class-path,但对于模块而言。

模块化应用程序的启动如下:



在正常启动模式下,我们指定选项和主类的完整路径。如果我们要使用模块,我们还要指定选项和-m-module参数,这仅表明我们将运行模块。也就是说,我们会自动将应用程序转换为模块化模式。接下来,我们指示模块的名称以及从模块到主类的路径。

另外,如果在普通模式下,我们习惯于使用-cp--class-path选项在模块化模式下,我们指定了一个新参数-p--module-path,用于指示应用程序中使用的模块的路径。

我经常遇到这样的事实,即开发人员不会切换到版本9+,因为他们认为他们将必须使用模块。尽管事实上,我们可以在旧模式下运行应用程序,而无需编写参数或使用模块,而仅使用其他新芯片即可。

地狱


我还想对讲Jar Jar Hell问题。



简而言之,Jar Hell是什么?例如,我们有某种应用程序,它取决于X Y库。同时,这两个库都依赖于Z库,但是依赖于不同的版本:X依赖于版本1Y依赖版本2。好吧,如果版本2与版本1向后兼容,那么没问题。如果不是这样,很明显我们遇到了版本冲突,即同一类加载器无法将同一库加载到内存中。

您如何摆脱这种情况?自从第一个Java以来​​,开发人员一直在使用标准方法,例如exclude,有人使用Maven插件,该插件将库的根软件包的名称重命名。或者,开发人员正在寻找X库的不同版本,以找到兼容的选项。

我为什么?最初的Jigsaw原型暗示该模块具有一个版本,并允许通过不同的ClassLoader加载多个版本,但后来被放弃了。结果,许多人在等待的“银色子弹”没有解决。

但是,开箱即用,我们对这些问题有些放心。 Java 9禁用拆分软件包-分为几个模块的软件包。也就是说,如果在一个模块中com.my.coolpackage软件包,则不能在同一应用程序的另一个模块中使用它。当您使用包含相同软件包的模块启动应用程序时,我们只会崩溃。这个小改进消除了与下载Split软件包有关的不可预测行为的可能性。

另外,除了模块本身之外,还有一个图层机制或拼图图层,它也有助于解决Jar Hell问题。

拼图层可以定义为某些本地模块化系统。这里值得注意的是,上面提到的Split包仅在一个Jigsaw层的框架内被禁止。具有相同包装的模块有放置的位置,但是它们必须属于不同的层。

看起来是这样的:



当应用程序启动时,将创建启动,其中包括Bootstrap加载的平台模块,平台加载器加载的其他平台模块以及应用程序加载器加载的应用程序模块。

在任何时候,我们都可以在其中创建自己的图层并“放置”不同版本的模块,并且不会掉落。

YouTube上有一个非常详尽的话题,主题是:用拼图层逃避Jar Hell

结论


Java 9的模块引擎为我们打开了新的可能性,而今天对库的支持却很小。是的,人们运行Spring,Spring Boot等。但是大多数库尚未切换为完全使用模块。因此,显然,所有这些更改都被技术界怀疑地认为。模块为我们提供了新的机会,但需求问题仍然悬而未决。

最后,我提供有关该主题的精选材料:

Project Jigsaw

JDK模块摘要

Paul

Deitel- 了解Java 9模块baeldung.com-Project Jigsaw简介

Alex Buckley- 使用JDK 9进行模块化开发

Evgeny Kozlov-Java中的模块

All Articles