抑郁症健康,内容丰富有趣,生活中的好帮手!
抑郁症健康 > scratch拼图编程_使用模块化和项目拼图进行编程。 使用最新的抢先体验版的教程

scratch拼图编程_使用模块化和项目拼图进行编程。 使用最新的抢先体验版的教程

时间:2021-01-25 05:54:36

相关推荐

scratch拼图编程

可以将软件视为交互部分的系统,在Java中,通常将每个部分打包在其自己的JAR中。 从概念上讲,一部分包括三个属性:名称,供世界其他地方使用的公共API以及对其他部分的依赖关系。 这种类似于图形的模型可帮助开发人员和工具剖析,分析软件系统并与之协同工作。

但是,这些属性都不存在于Java运行时中,该运行时使用类路径访问一堆JAR,并将它们全部卷成一个大泥巴。 JAR之间的任何区别都将完全消失,仅保留一套扁平的程序包,而无法进行进一步的验证。 诸如“是否所有这些必需的JAR?”,“这些甚至是正确的JAR?”,“是否存在冲突?”或“仅使用公共API?”等重要问题。 运行的JVM无法回答。

因此,一方面存在一个结构良好的模型,该模型说明了系统如何模块化以及各部分之间如何相互依赖。 另一方面,存在一个几乎完全非结构化名称空间的运行时现实。 这种不匹配是被亲切地称为JAR hell和背后对内部API的依赖的驱动力。 它还会导致不良的启动性能并削弱安全性 。

拼图项目将增强编译器和运行时,使其更接近结构化模型。 它的主要目标是可靠的配置(通过声明依赖项)和强大的封装(通过隐藏内部结构),而这两者的代理是模块的概念。

介绍模块

Oracle首席架构师马克·雷因霍尔德(Mark Reinhold)从强烈推荐的设计概述中引用模块系统状态 :

一个模块是代码和数据的命名,自描述的集合。它的代码被组织为一组包含类型(即Java类和接口)的软件包。其数据包括资源和其他种类的静态信息。

为了控制其代码如何引用其他模块中的类型,模块声明其需要哪些其他模块才能进行编译和运行。为了控制其他模块中的代码如何引用其包中的类型,模块声明要导出的包中的哪个。

因此,与JAR相比,模块具有JVM可以识别的名称,声明其依赖于其他模块,并定义哪些包是其公共API的一部分。

名称

模块名称可以是任意的,但不能冲突。 为此,建议使用标准的反向域名程序包。 尽管这不是必须的,但这通常意味着模块名称是它包含的软件包的前缀。

依赖性和可读性

一个模块列出了要编译和运行的其他模块。 再次从“模块系统状态”:

当一个模块直接依赖于另一个模块时,第一个模块中的代码将能够引用第二个模块中的类型。因此,我们说第一模块读取第二模块,或者等效地,第二模块可被第一模块读取。

[…]

模块系统确保每个依赖关系都由另一个模块精确地满足,没有两个模块可以互相读取,每个模块最多只能读取一个定义给定程序包的模块,并且定义同名程序包的模块不会互相干扰。

这种可读性的概念是可靠配置的基础:当违反任何条件时,模块系统将拒绝编译或启动代码; 对脆性类路径模型的巨大改进。

出口和可达性

一个模块列出了它导出的软件包。 在以下情况下,一个模块中的类型只能由另一模块中的代码访问:

公开类型 包含的包由第一个模块导出 第二个模块读取第一个

这意味着公众不再是真正的公众。 非导出包中的公共类型与导出包中的非公共类型一样,对外界隐藏。 因此,“公共”比当今的包私有类型更加隐蔽,因为模块系统甚至 不允许通过反射进行访问,因为当前已实现了Jigsaw,命令行标志是解决此问题的唯一方法。

因此,可访问性建立在可读性和export子句的基础之上,从而创建了强大的封装基础,模块作者可以在其中清楚地表达模块API的哪些部分是公开的和受支持的。

示例:创建我们的第一个模块

假设我们有一个监视网络中运行的微服务的应用程序。 它会定期与他们联系并使用他们的响应来更新数据库表以及漂亮的JavaFX UI。 目前,让我们假设应用程序是作为一个没有任何依赖项的项目开发的。

现在,让我们使用Jigsaw切换到Java 9! (早期访问版本在上可用 -本文中提供的代码示例和命令是从12月22日开始为版本96创建的。)尽管Java依赖分析器jdeps已经存在于JDK 8中,我们需要使用JDK 9版本,以便它了解模块。

首先要注意的是,我们可以选择忽略模块。 除非代码依赖于内部API或某些其他JDK实现细节(在这种情况下事情会中断 ), 否则可以像使用Java 8一样完全编译和执行应用程序。只需将工件(及其依赖项,如果有)添加到classpath并调用main。 瞧,它运行了!

要将代码移入模块,我们必须为其创建模块描述符。 这是源目录根目录中名为module-info.java的源代码文件:

module com.infoq.monitor {// add modules our application requires// add packages our application exports}

我们的应用程序现在是名为com.infoq.monitor的模块。 为了确定它所依赖的模块,我们可以使用jdeps :

jdeps -module ServiceMonitor.jar

这将列出我们的应用程序使用的软件包,更重要的是,java.logging, java.sql, javafx.base, javafx.controls, javafx.graphics.它们来自哪些模块:java.basejava.logging, java.sql, javafx.base, javafx.controls, javafx.graphics.

覆盖了依赖性之后,我们现在可以考虑可以导出哪些软件包。 由于我们在谈论独立应用程序,因此我们不会导出任何内容。

module com.infoq.monitor {requires java.base; // see more about that belowrequires java.logging;requires java.sql;requires javafx.base;requires javafx.controls;requires javafx.graphics;// no packages to export}

编译与不使用Jigsaw相同,只是我们必须在源文件列表中包括module-info.java

javac -d classes/com.infoq.monitor ${source files}

现在,所有类都被编译为classes/com.infoq.monitor,我们可以从它们创建一个JAR:

jar -c \--file=mods/com.infoq.monitor.jar \--main-class=com.infoq.monitor.Monitor \${compiled class files}

新的--main-class可用于指定包含应用程序入口点的类。 结果就是所谓的模块化JAR,我们将在下面进一步讨论。

与旧模型形成鲜明对比的是,有一个全新的序列可以启动该应用程序。 我们使用新的-mp开关指定在哪里查找模块,并使用-m命名要启动的模块:

java -mp mods -m com.infoq.monitor

为了更好地理解这一点,让我们讨论一下编译器和虚拟机如何处理模块。

Java和模块

各种模块

JDK本身是模块化的,由大约80个平台模块组成(请使用java -listmods)。 将针对Java 9 SE标准化的前缀为“java。”,特定于JDK的前缀为“jdk”。

并非所有Java环境都必须包含所有平台模块。 相反,Jigsaw的一个目标是可扩展平台,这意味着仅使用所需模块即可轻松创建运行时。

所有Java代码都取决于Object,并且几乎所有代码都使用诸如线程和集合之类的基本功能。 这些类型可以在java.base中找到,它起着特殊的作用。 它是模块系统固有的唯一模块,并且由于所有代码都依赖于该模块,因此所有模块都会自动读取该模块。

因此,在上面的示例中,我们不需要声明对java.base的依赖。

非平台模块称为应用程序模块,用于启动应用程序的模块(包含主模块的模块)是初始模块。

模块化JAR

正如我们在上面看到的那样,尽管具有新的语义,Jigsaw仍会创建JAR。 如果它们包含module-info.class,则它们称为模块化JAR,有关“模块系统状态”的内容为:

模块化JAR文件在所有可能的方式上都与普通JAR文件类似,除了它的根目录中还包含module-info.class文件。

可以将模块化JAR放在类路径以及模块路径上(请参见下文)。 这使项目可以发布单个工件,并让其用户在传统方法或模块化方法之间进行应用程序构建。

模块路径

平台模块是当前使用的环境的一部分,因此很容易获得。 为了让编译器或VM知道应用程序模块,我们必须使用-mp指定模块路径(如上所述)。

当前环境中的平台模块和来自模块路径的应用程序模块一起构成了可观察模块的范围。 给定该集合和其中包含的初始模块,虚拟机可以创建模块图。

模块图

从初始应用程序模块开始,模块系统解析其所有传递依赖项。 结果是模块图,其中模块是节点,一个模块对另一个模块的依赖关系是有向边。

对于我们的示例,如下所示:

平台模块以蓝色显示,其中较亮的模块是我们直接依赖的模块,较暗的模块是我们的依赖。 没有显示无处不在的java.base。 记住,所有模块都隐含地依赖它。

示例:拆分模块

了解了编译器和虚拟机如何处理模块后,让我们开始考虑如何将应用程序划分为模块。

我们的体系结构包括以下部分:

联系微服务并创建统计信息 更新数据库 呈现JavaFX用户界面 接线件

让我们继续为每个模块创建一个模块:

com.infoq.monitor.stats com.infoq.monitor.db com.infoq.monitor.ui com.infoq.monitor

《 Jigsaw 快速入门指南》和JDK本身建议在项目的根源文件夹下为每个模块提供一个文件夹。 在我们的情况下,它将具有以下结构:

Service Monitor└─ src├─ com.infoq.monitor│ ├─ com ...│ └module-info.java├─ com.infoq.monitor.db│ ├─ com ...│ └module-info.java├─ com.infoq.monitor.stats│ ├─ com ...│ └module-info.java└─ com.infoq.monitor.ui├─ com ...└module-info.java

该树在此处被截断,但是每个“ com”目录表示相同名称的程序包,并且将包含更多子目录,并最终包含模块的代码。

统计信息模块依赖于java.base(但据我们了解,我们不必列出它)并使用Java的内置日志记录工具。 它的公共API允许请求汇总和详细的统计信息,并且包含在一个软件包中。

module com.infoq.monitor.stats {requires java.logging;exports com.infoq.monitor.stats.get;}

重申可访问性规则:com.infoq.monitor.stats.get中的非公共类型和其他程序包中的所有类型对所有其他模块完全隐藏。 即使导出的程序包也仅对读取此程序包的模块可见。

我们的数据库模块还记录日志,并且显然需要JavaSQL功能。 它的API由一个简单的编写器组成。

module com.infoq.monitor.db {requires java.logging;requires java.sql;exports com.infoq.monitor.db.write;}

用户界面显然需要包含所使用JavaFX功能的平台模块。 其API包含启动JavaFX UI的方法。 这将返回一个使用JavaFX属性的模型。 客户端可以更新模型,并且UI将显示新状态。

module com.infoq.monitor.ui {requires javafx.base;requires javafx.controls;requires javafx.graphics;exports com.infoq.monitor.ui.launch;exports com.infoq.monitor.ui.show;}

现在,我们已经介绍了实际的功能,现在我们可以将注意力转向主模块,该主模块将所有部件连接在一起。 该模块需要我们三个模块中的每个模块,以及Java的日志记录功能。 由于UI要求其依赖模块才能使用JavaFX属性,因此它也依赖于javafx.base。 而且由于主模块未被其他任何人使用,因此它没有要导出的API。

module com.infoq.monitor {requires com.infoq.monitor.stats;requires com.infoq.monitor.db;requires com.infoq.monitor.ui;requires javafx.base; // to update the UI modelrequires java.logging;// no packages to export}

我们必须显式地要求javafx.base有点笨拙,但是如果没有,我们就无法从javafx.beans.property中调用任何代码,而使用模型必须使用该代码。 因此,com.infoq.monitor.ui在没有javafx.beans.property的情况下被破坏了。 对于这种情况,Jigsaw原型提供了隐式可读性的概念,我们将在稍后讨论。

我们的模块化创建了一个非常不同的模块图:

现在,让我们看一些用于编译,打包和启动我们新的模块化应用程序的命令。 这是我们可以编译和打包不依赖于JDK本身的模块的方式:

javac -d classes/com.infoq.monitor.stats ${source files}jar -c \--file=mods/com.infoq.monitor.stats.jar \${compiled class files}

与Java 8之前一样,与Java 8完全一样,我们编译模块的源代码,将结果文件写入具有模块名称的类的子文件夹中,并在mods中创建JAR。 这是模块化的JAR,因为类文件包含已编译的模块描述符module-info.class

更有趣的是我们如何处理依赖于其他应用程序模块的唯一模块:

javac \-mp mods \-d classes/com.infoq.monitor \${list of source files}jar -c \--file=mods/com.infoq.monitor.jar \--main-class=com.infoq.monitor.Monitor \${compiled class files}java -mp mods -m com.infoq.monitor

编译器需要我们先前创建的应用程序模块,然后通过使用-mpmods指定模块路径将其指向此处。 打包和启动与以前一样,但是现在mods包含单个模块,还包含四个模块。

JVM将首先查找模块com.infoq.monitor,因为我们将其指定为我们的初始模块。 当发现这一点时,它将尝试可传递地解决其在可观察模块(在本例中为我们的四个应用程序模块和所有平台模块)范围内的所有依赖关系。

如果可以构建有效的模块图,则它将最终查找--main-class=com.infoq.monitor.Monitor指定的main方法并启动我们的应用程序。 否则,它将以异常通知我们违反条件,例如缺少模块或循环依赖关系而失败。

隐含可读性

一个模块对另一个模块的依赖性可以采取两种形式。

首先,有些依赖是内部消耗的,而外界对此一无所知。 以Guava为例,其中依赖于模块的代码根本不在乎内部是否使用不可变列表。

这是最常见的情况,并且如上所述被可读性所覆盖,在这种情况下,一个模块仅在声明其对它的依赖关系时才能访问另一个模块的API。 因此,如果模块依赖于Guava,则其他模块对此事实一无所知,如果没有声明自己对它的明确依赖,就无法访​​问Guava。

但是还有另一种用例,其中依赖关系没有完全封装,而是存在于模块之间的边界上。 在这种情况下,一个模块依赖于另一个模块,并在其自己的公共API中公开依赖模块的类型。 在Guava的示例中,模块的公开方法可能期望或返回不可变列表。

因此,要调用依赖模块的代码可能必须使用依赖模块中的类型。 但是,如果它也不读取第二个模块,它就无法做到这一点。 因此,为了使从属模块可用,客户端模块也必须全部明确依赖于该第二模块。 识别并手动解决这种隐藏的依赖关系将是一项繁琐且容易出错的任务。

这就是隐含可读性的来源:

[我们]扩展了模块声明,以便一个模块可以将可读性授予该模块依赖的其他模块,以及任何依赖于该模块的模块。这种隐含的可读性通过在require子句中包含public修饰符来表示。

在使用不可变列表的模块公共API的示例中,该模块通过公开要求将Guava的可读性授予所有其他模块。 这样,其API即可立即使用。

示例:隐含可读性

让我们转到我们的UI模块,该模块在其API中公开一个使用来自模块javafx.base的类型的模型。 现在,我们可以更改模块描述符,以使其公开需要该模块:

module com.infoq.monitor.ui {// expose javafx.base to modules depending on this onerequires public javafx.base;requires javafx.controls;requires javafx.graphics;exports com.infoq.monitor.ui.launch;exports com.infoq.monitor.ui.show;}

我们的主模块现在可以隐式读取javafx.base,而不再必须显式依赖它,因为它的依赖项com.infoq.monitor.ui会导出它:

module com.infoq.monitor {requires com.infoq.monitor.stats;requires com.infoq.monitor.db;requires com.infoq.monitor.ui;// we don’t need javafx.base anymore to update the UI modelrequires java.logging;// no packages to export}

尽管这改变了com.infoq.monitor读取com.infoq.monitor.stats的原因,但是它并没有改变它确实读取它的事实。 因此,这既不会更改我们的模块图,也不会更改编译和启动应用程序所需的命令。

超越模块边界

再次引用设计概述:

通常,如果一个模块导出包含其签名指向第二个模块中的一个包的类型的包,则第一个模块的声明应包括对第二个模块的公共依赖。这将确保依赖于第一个模块的其他模块将能够自动读取第二个模块,从而访问该模块导出的包中的所有类型。

但是,我们应该走多远? 例如,查看java.sql模块。 它公开了接口Driver ,其中包含一个返回aLogger的公共方法getParentLogger()。 因此,该模块公开需要java.logging。 因此,使用JavaSQL功能的任何模块都可以隐式访问日志记录API。

考虑到这一点,让我们再看一下数据库模块:

module com.infoq.monitor.db {requires java.logging;requires java.sql;exports com.infoq.monitor.db.write;}

从技术上讲,不需要要求java.logging的声明,这似乎是多余的。 那我们应该放弃它吗?

为了回答这个问题,我们必须看看com.infoq.monitor.db是如何使用java.logging。 我们的模块可能只需要读取它,因此我们可以调用Driver.getParentLogger(),对logger执行某些操作(例如,记录消息)并完成它。 在这种情况下,我们的代码与java.logging的交互java.logging发生在它与来自java.sql.Driver交互的附近java.sql.在上面,我们称此为com.infoq.monitor.dbjava.sql之间的边界。

另外,我们可能在整个com.infoq.monitor.db使用日志记录。 然后,来自java.logging类型出现在许多独立于Driver地方,并且不再被认为仅限于com.infoq.monitor.dbjava.sql的边界。

随着拼图技术的发展,社区仍然有时间讨论这些主题并就推荐的做法达成共识。 我的观点是,如果一个模块不仅在另一个模块的边界上使用,还应该明确要求它。 这种方法阐明了系统的结构,并在将来对重构的模块声明进行了验证。 因此,只要我们的数据库模块独立于SQL模块使用日志记录,我就会保留它。

聚合模块

隐含的可读性为所谓的聚合器模块开辟了道路,该聚合器模块自身不包含任何代码,但是聚合了许多其他API,以便于使用。 Jigsaw JDK已经采用了这种方法,该模型将紧凑的概要文件建模为模块,这些模块简单地公开了包是概要文件一部分的模块。

在我们的微服务监控器的情况下,我们可以设想单个API模块,该模块聚集用于统计,用户界面和数据的模块,以便主模块仅具有单个依赖性:

module com.infoq.monitor.api {requires public com.infoq.monitor.stats;requires public com.infoq.monitor.db;requires public com.infoq.monitor.ui;// implied readability is not transitive// so we have to explicitly list `javafx.base`requires public javafx.base}module com.infoq.monitor {requires com.infoq.monitor.api;requires java.logging;// no packages to export}

原则上这是有用的,但是在这个简单的示例中并没有提供特别的优势。

服务

到目前为止,我们已经介绍了在编译时确定并声明的依赖项。 如果依赖项采用服务的形式,则可以实现松散的耦合,其中一个或多个模块提供在单个类型之后抽象的功能,而其他模块则使用该类型的实例。 消费者可以使用模块系统来发现提供者。 这实现了服务定位器模式 ,其中模块系统本身充当定位器。

服务是提供内聚功能的一组接口和(通常是抽象的)类。 必须从一种类型(可能是一种接口)访问所有涉及的类型,以便可以使用一种类型来加载服务。

服务提供者模块包含一项或多项服务的实现。 每一个都provides X with Y一个provides X with Y; 子句在其模块描述符中,其中X是服务接口的完全限定名称,而Y是实现类的完全限定名称。 Y需要有一个公共的,无参数的构造函数,以便模块系统可以实例化它。

服务使用者模块必须读取服务模块,并包含一个uses X; 描述符中的子句。 然后,可以在运行时调用ServiceLoader . load(Class<X>)ServiceLoader . load(Class<X>)并获取服务接口的加载程序。 加载程序是可迭代的,并且包含实现X的所有类的实例,并由服务提供商模块提供。

如此处所述使用服务时,耦合不仅会在编译时松开(因为使用者没有声明对提供程序的依赖)。 它在运行时也很松散,因为模块系统不会在使用者和提供者之间创建任何读取边。

另一个有趣的方面是,服务的可用提供者集合由模块路径(即通常在启动时)定义。 确切地说,那些提供服务实现的可观察模块将在运行时通过服务加载器提供。 因此,可以通过编辑其模块路径的内容并重新启动系统来影响系统的行为。

示例:创建和使用服务

在我们的示例中,我们有一个模块com.infoq.monitor.stats,我们说它与网络中运行的服务联系并创建统计信息。 对于单个模块,这听起来需要大量工作,但是我们可以将其拆分。

监视单个服务是一项统一的任务,因此我们为此创建一个API并将其放入新模块com.infoq.monitor.watch。 该服务接口称为com.infoq.monitor.watch.Watcher

现在,我们可以自由地创建一个或多个带有具体微服务实现的模块。 让我们将这些模块com.infoq.monitor.watch.logincom.infoq.monitor.watch.shipping等。 它们的模块描述符如下:

module com.infoq.monitor.watch.login {// we need the module defining the service we are providing;// we imply readability so this module is usable on its ownrequires public com.infoq.monitor.watch;provides com.infoq.monitor.watch.Watcherwith com.infoq.monitor.watch.login.LoginWatcher;}

请注意,它们仅提供Watcher实现,而不导出任何包。

既然与微服务联系的所有代码都来自com.infoq.monitor.stats,我们现在必须确保它仍然可以访问该功能。 其新module-info.java

module com.infoq.monitor.stats {requires java.logging;requires com.infoq.monitor.watch;// we have to declare which service we are depending onuses com.infoq.monitor.watch.Watcher;exports com.infoq.monitor.stats.get;}

我们现在可以在其代码中的某个位置执行此操作:

List<Watcher> watchers = new ArrayList<>();ServiceLoader.load(Watcher.class).forEach(watchers::add);

这将生成一个列表,其中包含每个Watcher实现的一个实例,该实例由模块路径上的模块提供。 从这里开始,代码将与之前类似,即联系服务并创建统计信息。

将我们的模块图限制为com.infoq.monitor.stats及以下(因为其他所有内容均不变),我们的新版本如下所示:

注意所有箭头如何指向新模块。 依赖反转原理的一个典型例子。

编译,打包和启动就像以前一样(除了现在我们有更多的模块)。

移民

到目前为止,我们已经讨论了将包含所有依赖项的完整应用程序转换为模块的场景。 但是,当拼图首次从其闪亮的新包装中取出时,这种情况并不常见。 大多数项目将依赖于尚未适应模块系统使用的库,并且这些库将无法控制。

拼图团队已经直接解决了这个问题,并提供了逐步过渡到模块化的途径。 为此,他们介绍了两种我们尚未讨论过的模块。

模块种类II

我们已经了解平台模块和应用程序模块。 他们完全了解模块系统,其定义特征是模块描述符。 因为它们给它们起了一个名字,所以它们被称为命名模块。

对于工件,还有其他两种类型的模块,它们不知道模块系统。

在进行Jigsaw之前,从类路径加载的所有类型最终都处在它们可以自由访问的同一空间中。 这种非结构化的空间继续存在:每个类加载器都有一个唯一的未命名模块,向其分配从类路径加载的所有类型。

未命名的模块将读取所有其他模块并导出所有软件包。 因为模块化的应用程序不应依赖于类路径的随机内容,所以命名模块不能要求未命名模块,因此无法读取它们(不求助于反射)。

但是,没有模块描述符的工件也可以放置在模块路径上。 在这种情况下,模块系统将为其创建一个完整的模块,称为自动模块。

自动模块的名称是从工件的文件名派生的,它可以读取所有其他模块并导出其所有包。 因为模块系统可以在启动时轻松检查模块路径上是否存在任何特定的自动模块,所以命名模块可以依赖于它们并因此读取它们。

因此,在单个应用程序类加载器的常见情况下,应用程序的可观察模块范围可以包括:

在运行时包含的命名平台模块 一个具有模块描述符的模块路径上每个工件的一个命名应用程序模块 一个自动模块,用于模块路径上没有模块描述符的每个工件 一个唯一的未命名模块,由类路径上的所有工件组成,无论它们是否具有模块描述符(对于多个应用程序类加载器,将有多个未命名模块)

迁移策略

这些模块为逐步迁移到模块系统开辟了道路。 (请注意,模块关系并不是唯一的障碍 。)

如上所述,整个应用程序,包括其所有依赖项,都可以简单地放在类路径上。 万一某些问题阻止迁移,这是至关重要的逃生门。

现在我们可以看到这种方法为什么起作用:类路径上的所有工件都被放入未命名的模块中,在该模块中所有类型都可以自由地相互访问。 要使用Java的公共API,他们必须访问平台模块,因为未命名的模块会读取所有其他可观察的模块,所以他们必须这样做。

自下而上的迁移从无依赖工件开始,可以立即对其进行模块化。 在此基础上,其他项目可以移至拼图。

客户可以将模块化JAR放在模块路径上,并在迁移项目时按名称引用它们。 即使没有,并且它们的代码仍然来自类路径,它也可以访问迁移的工件,因为未命名的模块可以读取所有其他模块。 或者客户可以决定将模块化JAR放在类路径上。

这种方法最适合很少维护的依赖项的图书馆项目。 但是,随着依赖关系数量的增加,项目可能不愿意等待所有模块模块化。 对于大型Java应用程序尤其如此,大型Java应用程序可能更愿意选择另一种方法。

自上而下的迁移始于为项目的所有工件创建模块描述符。 他们需要名称,并且必须指定他们所依赖的其他内部构件以及要导出的软件包。

这个过程自然会面临外部依赖。 如果存在可识别拼图的版本并且可以使用,那就太好了。 如果不是这样,那么就应该使用自动模块:项目的工件需要具有Jigsaw从工件文件名派生的名称的模块,并将工件放置在模块路径上。

这样做足以满足直接依赖关系,以便新的应用程序模块可以访问它们。 依赖性可能会带来传递性依赖性。 但是,由于直接的依赖关系变成了自动模块,可以读取所有其他模块,包括未命名的模块,因此可以将其依赖关系放在类路径中。

对于大型项目,这种手动方法变得不可用,并且构建工具必须有所帮助。 Gradle和Maven已经开始研究拼图相关的功能。

有关迁移的更多详细信息,可以在Oracle拼图团队的两位成员Alex Buckley和Alan Bateman的JavaOne演讲“高级模块化开发”中找到。

示例:迁移依赖关系

假设我们的数据库模块使用Guava,并且在目录libs.有工件guava-19.0.jarlibs.我们不能简单地将其放在类路径中,因为我们的应用程序已经正确地模块化了,并且上面我们讨论了命名模块不能读取未命名模块。 因此,我们需要一个自动模块。

Java从名为guava-19.0.jar的文件中得出模块名称guava。 有了这些知识,我们可以更新数据库模块的描述符:

module com.infoq.monitor.db {requires java.logging;requires java.sql;requires guava;exports com.infoq.monitor.db.write;}

要对其进行编译,我们需要将libs添加到编译器的模块路径中:

javac \-mp libs \-d classes/com.infoq.monitor.db \${list of source files}

包装不需要任何更改。 如果我们像以前那样启动应用程序,JVM将抱怨找不到模块guava。 要解决此问题,我们需要将libs添加到模块路径:

java -mp mods:libs -m com.infoq.monitor

(请注意,在Windows上,路径之间的分隔符是“;”而不是“:”。)

下一步

我们探索了Jigsaw原型,并看到了它所提供的核心功能。 除了等待Java 9之外,我们还能做什么?

更深入

总是可以了解更多信息,并且有一些我们没有讨论过的高级主题:

出色的“模块系统状态”展示了如何将模块与反射配合使用,其中包括在运行时添加读取边缘和新的层概念,以及与类加载器的交互 。

新的工具jlink可用于创建仅包含特定平台模块集的运行时映像。 强烈建议您在《 拼图快速入门指南》中对其进行介绍。

拼图团队在JavaOne 和Devoxx BE 上的演讲也涵盖了这些主题。我在这里总结了前者。

观察

拼图的所有内容都可以从该项目的OpenJDK站点中发现。 有关Jigsaw项目的最新信息的主要来源是Jigsaw-Dev邮件列表 。 我还将继续讨论该主题 我的博客 。

准备

正如已经暗示的那样,迁移到拼图游戏可能会有些困难 。 为了准备我们的项目,我们应该检查它们是否依赖于Java 9中不可用或已删除的任何内容。

可以使用jdeps来分析对内部API的依赖,这是一个关键的障碍,jdeps是Java依赖性分析工具( 带有一些内部软件包的介绍, Windows和Unix的官方文档),已在JDK 8中提供。至少还有三个jdeps -Maven的插件: 阿帕奇 菲利普·马绍尔 ( Philippe Marschall)和 我自己 。 后者使项目能够逐渐删除其对内部API的依赖关系,同时打破重复构建。

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

我们还应该确定项目所依赖的关键依赖性,并与这些团队一起检查他们如何为Java 9做准备。

采用

可以使用Jigsaw早期访问版本 ,并可以用来临时编译和运行现有项目。 不幸的是,构建系统支持仍不完善,但仍在努力。

通过在Jigsaw-Dev邮件列表中发布,可以将以这种方式收集的信息和问题返回给项目。 要引用众多涉及的JEP中的一个(几乎)最后的话:

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

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

翻译自: /articles/Latest-Project-Jigsaw-Usage-Tutorial/?topicPageSponsorship=c1246725-b0a7-43a6-9ef9-68102c8d48e1

scratch拼图编程

如果觉得《scratch拼图编程_使用模块化和项目拼图进行编程。 使用最新的抢先体验版的教程》对你有帮助,请点赞、收藏,并留下你的观点哦!

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。