前端开发入门到精通的在线学习网站

网站首页 > 资源文章 正文

阿里P8大牛带你学习自动模块:模块路径上的普通JAR

qiguaw 2024-09-01 01:08:24 资源文章 31 ℃ 0 评论

一 自动模块

任何模块化工作的长期目标都是将普通JAR升级为模块化JAR,并将它们从类路径移动到模块路径。

一种方法是,等到所有依赖都以模块的形式出现后再模块化自己的项目——这是一种自下而上的方法。不过,这可能需要很长时间,因此模块系统也允许自上而下的模块化方法。

后文将详细解释这两种方法,但是要采用自上而下的方法,首先需要一种新的技术手段。考虑一下:如果依赖以普通JAR的形式出现,你将如何声明模块?

正如下面所述,如果将它们放在类路径上,它们最终会出现在无名模块中,而你的模块无法访问该模块。但是,前文曾提到过,普通JAR也可以放在模块路径上,模块系统将自动为它们创建模块。

注意 围绕自动模块的机制通常适用于编译时和运行时。前文曾说过,如果总是提到这两个词的话,不仅增加的信息量很少,而且会使文章更难阅读。

针对模块路径上每个没有模块描述符的JAR,模块系统都将创建一个自动模块。它与任何其他具名模块一样,有3个核心属性。

1) 名称——可以在JAR的manifest文件中使用Automatic-ModuleName头来确定自动模块的名称。如果未填写,模块系统将基于文件名生成一个名称。

2) 依赖——自动模块可以读取其他所有进入模块图的模块,包括无名模块(你很快就会看到,这一点很重要)。

3) 导出——自动模块导出它的所有包并开放它们以方便反射(开放式包和模块的详细信息,)。

此外,可执行JAR会生成可执行模块,这些模块的主类将被标记。META-INF/services中提供的服务对ServiceLoader而言是可用的。对服务的介绍。

再次假设ServiceMonitor还没有模块化,但此时你仍然可以将其工件放在模块路径上。如果目录jar-mp

monitor.jar、monitor.observer.jarmonitor.statistics.jar,而目录jars-cp包含所有其他应用程序和依赖JAR,你可以用如下命令启动ServiceMonitor

可以在图8-6中看到最终的模块图。有些细节可能还不清楚,(比如,为什么在命令行只引用monitor的情况下,所有3个自动模块都出现在图中?)别担心,下一节将进行解释。

monitor.jarmonitor.observer.jarmonitor.statistics.jar这几个普通JAR放在模块路径上,JPMS为它们创建了3个自动模块。

类路径上的内容像以前一样被视为无名模块。请注意自动模块如何相互读取,以及它们如何读取无名模块,并且在图中创建许多循环

自动模块是功能完备的具名模块,这意味着:

1) 可以在其他模块的声明中通过名称引用它们,例如通过requires指令;

2) 强封装使它们不能使用平台模块的内部功能(与无名模块不同);

3) 它们接受包分裂检查。

另一方面,它们确实有一些独特之处。后文将开始使用自动模块,在此之前本文先讨论一下它。

1 自动模块名称:小细节,大影响

将普通JAR转换为模块的主要目的是在模块声明中依赖它们。为此,它们需要一个名称,但是缺少模块描述符,名称从何而来?

01. 首先是manifest条目,然后是文件名

确定普通JAR模块名称的一种方法是基于其manifest文件,即JARMETA-INF目录下的MANIFEST.MF文件。manifest文件包含头值对(header-value pairs)形式的各种信息,其中最重要一个头是MainClass,它通过命名包含main函数的类定义了一个部分模块化应用程序的入口点。这使得通过java -jar app.jar来启动应用程序成为可能。

如果模块路径上的JAR文件不包含模块描述符,模块系统将通过以下两步确定自动模块的名称。

(1) 在manifest文件中查找Automatic-Module-Name头,如果找到它,就使用对应的值作为模块的名称。

(2) 如果没有在manifest文件中找到对应头,那么模块系统将根据文件名推断模块名称。

manifest文件中推断模块的名称是一种值得推荐的方式,因为这样比较稳定。

从文件名推断模块名称的具体规则有点复杂,但细节并不太重要。

要点如下:

1) JAR文件名通常以版本字符串结尾(比如-2.0.5),版本是可以识别和忽略的;

2) 除字母和数字外,每个字符都被转换成一个点。

这个过程可能导致不好的结果,令产生的模块名称无效。字节码操作工具Byte Buddy就是一个例子:它在Maven Central中的名称为byte-buddy-${version}.jar,这导致自动模块名为byte.buddy。很遗憾,这是非法的,因为byte是一个Java关键字。

为了避免对模块系统为给定的JAR选择了哪个名称进行猜测,你可以使用jar工具查看。

如果JAR缺少模块描述符,前部分输出如下。

${module-name}是实际名称的占位符,你需要查找实际名称。很遗憾,这并不能告诉你名称是从manifest条目还是文件名中获取的。要找到答案,有如下几个选择:

1) 使用命令jar --file ${jarfile} --extract METAINF/MANIFEST.MF获取manifest文件,然后手动查看;

2)在Linux系统上,命令unzip -p ${jarfile} METAINF/MANIFEST.MF打印manifest文件内容到控制台,然后保存在文件中;

3)重命名文件,然后再次运行jar --describe-module。这里以Guava 20.0为例。

作为一个自动模块,Guava 20.0被称为guava。这是通用的还是基于模块名的?通过unzip工具,查看manifest文件的内容。

可以看到,Automatic-Module-Name没有经过设置。将文件重命名为com.google.guava- 20.0.jar,生成模块名称com.google.guava

如果使用一个不太过时的版本,比如Guava-23.6,会得到如下输出。

从所选名称和文件名不相同的事实可以看出,Google选择com.google.common作为Guava的模块名称。用unzip来查看一下。

好了,Automatic-Module-Name得到了设置。

02. 何时设置Automatic-Module-Name

如果你正在维护一个公开发布的项目,这就意味着它的工件可以通过Maven Central或其他公共仓库获得,那么你应该仔细考虑何时在manifest文件中设置Automatic-Module-Name

正如上文将解释的那样,这可以让作为自动化模块使用的项目更加可靠,但同时也带来了一个承诺,那就是将来要以清晰模块代替当前的JAR。你实际上是在说:“模块就是这样,只是还没来得及发布。”

定义一个自动模块名称会让用户开始把你的工件视作模块。这一事实有几个重要的含义。

1)未来模块的名称必须与现在声明的名称完全相同(否则,由于缺少模块,不可靠配置会影响你的用户)。

2)工件结构必须保持不变,因此不能将受支持的类或包从一个JAR移动到另一个JAR(即使没有模块系统,本书也不建议这样做。

对于类路径而言,哪个JAR包含某个类并不重要,因为这不会影响对它的使用。然而在使用模块系统时,类的所属模块很重要,因为可访问性要求用户找到正确的模块)。

3)该项目在Java 9及以上版本中运行得相当好。如果它需要命令行选项或其他变通方式,这些都已经被很好地文档化了(否则,你不能确定代码中是否隐藏了其他问题,而这些问题可能导致承诺失去意义)。

当然,软件开发“有时是不可预测的”,所以这些都不能保证万无一失。但是你应该相信自己可以坚持这些承诺。如果没有足够的精力在Java 9及以上版本中进行测试,或者发现将导致模块化结果不可预测的问题,那么请诚实地面对这种情况,暂且不要设置Automatic-Module-Name

如果你设置了它,并且无论如何都必须进行这样的更改,那么进行一次主要版本升级就可以了。图8-7展示了设置Automatic-Module-Name的示例。

如果你计划在模块化项目之前,在包之间移动类或在JAR之间移动包,请暂缓设置Automatic-Module-Name,直到该工作完成。

在这里,项目的JAR(左)在使用自动模块名发布之前进行了重构(中),因此当它们进行模块化(右)时,结构不会再发生改变要设置Automatic-Module-Name,你的项目不需要针对Java 9及以上版本。

JAR中可能包含为JVM老版本编译的字节码,但是定义模块名称对于模块系统的用户仍然有帮助,模块描述符也是如此。

2 自动模块的模块解析

理解模块系统如何在模块解析期间构建模块图是理解和预测模块系统行为的一个关键因素。对于显式模块而言,这很简单(requires指令);但是对于无名模块,它更加复杂,因为普通JAR不能表达依赖关系。

自动模块也是由普通JAR创建的,因此它们也没有显式的依赖关系,这就引出了一个问题:它们在解析期间的行为是什么样的。

后文将马上回答这个问题,但是正如后文所述,这将导致一个新的问题:你应该将自动模块的依赖放在哪里,类路径或模块路径上吗?阅读完本节,你就会知道答案。

01. 解析自动模块的依赖

第一个要回答的问题是,在模块解析期间,如果JPMS遇到一个自动模块会发生什么。自动模块是为了解决部分模块化依赖的模块化问题而创建的,因此在开发人员积极把项目模块化的情况下,可以使用它们。

在这种情况下,如果每个平台模块都引入自动模块(就像无名模块那样)将很糟糕,所以一般人们不会这样做(需要说明的是,他们也不显式引入任何应用程序模块)。

尽管如此,但通常JAR会互相依赖;如果模块系统只解析了显式依赖的自动模块,那么所有其他的自动模块都必须用--add-modules添加到图中。

对于有数百个依赖的大型项目而言,将这些依赖项放在模块路径上的情形是难以想象的。为了防止手动添加模块这种繁重而琐碎的操作,JPMS一旦遇到第一个自动模块,就会引入所有的自动模块。

一旦解析了一个自动模块,所有其他模块也都被解析。你可以将所有普通JAR添加为自动模块(至少依赖或添加一个),也可以一个也不添加(相反)。

这解释了为什么图8-6中虽然只添加了不能表达依赖关系的monitor模块,却因为将其设置为根模块而有3monitor.*模块。

请注意,自动模块对其他自动模块而言是可读的,这意味着任何模块只要能读取一个,就能读取全部。在确定自动模块的依赖关系时,请记住这一点——反复试错可以减少所需的requires指令。

ServiceMonitor应用程序中,monitor.rest模块依赖Spark Web框架,在本例中它也依赖于Guava。这两个依赖都是普通的JAR,所以monitor.rest需要将它们作为自动模块。

问题是,尽管spark.corecom.google.common中可能缺少一个requires指令,一切却可以正常运行。当模块系统解析第一个自动模块时,将解析其他所有自动模块;任何模块只要读取了其中一个,都可以读取其他所有自动模块。

因此,即使没有requires com.google.common指令,guava.jar也会与spark.core.jar一起作为自动模块出现,因为monitor.rest能读取spark.core,所以它也能读取guava。请确保依赖关系正确(比如使用JDeps)。

模块图中的循环

“自动模块读取其他所有模块”中隐藏了一个重要细节:这种方法会在模块图中创建循环。显然,至少有一个模块依赖于自动模块,(否则它为何会出现在模块图中呢?)并且读取它;同样,自动模块也读取该模块。

虽然这不会造成实质上的不良后果,但此处需要澄清,这并不违背禁止静态依赖循环的规则,因为由自动模块引起的循环不是静态声明导致的,而是由模块系统动态引入的。

如果自动模块只能读取其他具名模块,那么任务就完成了。一旦将普通JAR放在模块路径上,那么它的所有直接依赖也必须放到模块路径上,然后这些直接依赖的依赖也是如此,以此类推,直到所有传递依赖都被视为模块(显式的或自动的)。

不过,将所有普通JAR转换成自动模块也有缺点,所以最好将它们放在类路径上,并加载为无名模块。模块系统允许自动模块读取无名模块,这意味着它们的依赖关系可以在类路径上或模块路径上。

02. 选择传递依赖的路径

对于自动模块的依赖,通常有两个选项(请记住,也可以使用JDeps列出它们):类路径或模块路径。遗憾的是,并非在所有环境下都可以自由选择,在某些情况下,你需要做的不仅仅是决定采用哪种方式。

根据其他模块是否依赖这些模块,以及它们是平台模块、普通JAR还是模块JAR,表8-2列出了将这些依赖引入模块图的选项。如下几幅图反映了具体情况。

1)图8-8展示了在默认情况下,仅被自动模块所依赖的平台模块是不会得到解析的。


如果一个项目(本例中是your.app)使用了自动化模块(org.jooq),你将无法确定模块图是否与期望一致。

因为自动模块不能表达依赖,所以它们所需要的平台模块可能不会出现在模块图中(本例中这发生在了java.sql上),而是需要通过--add-modules来添加。

2)图8-9涵盖了自动模块所需要的普通JAR的几种不同情况。

monitor.rest(模块化JAR)对spark.core(普通JAR)的依赖开始,后者需要被放置在模块路径中。但是它的依赖slf4j(另一个普通JAR)呢?

此处可以看到,由此产生的模块图依赖于slf4j是否被另一个模块化JAR所依赖(上下两行对比),或者它被放置在哪个路径中(中间一列与右边一列对比)。

看上去模块路径占了绝对优势,但是再看一下图8-10

3)图8-10展示了当一个传递依赖由普通JAR转换为模块化JAR时模块图的演进。

在与图8-9右下角相同的场景下,如果放置在模块路径中的某个自动模块的传递依赖(slf4j)被模块化后,会发生什么?

它将不再被默认解析,而是需要通过--addmodules手动添加以解析表8-2 如何将自动模块的依赖添加到模块图中

仔细观察平台模块后,我们发现自动模块无法表达对它们的依赖。

结果是,模块图不一定会包含它们。如果没有包含它们,那么自动模块在运行时很可能会由于缺少相关类而失败。

这个问题唯一的解决方法是,项目的维护者将所需要的模块发布到公开文档中,这样用户就可以确保所需要的模块存在。用户既可以明确声明依赖(比如,在依赖于自动模块的模块当中声明),也可以使用--add-modules选项来达到这个目的。

在检查完对平台模块的依赖后,来看一下应用程序模块。如果某个自动模块的依赖被清晰模块所需要,那么它们需要被放置到模块路径中,以方便模块系统对其解析,而且不需要任何其他步骤。

如果没有清晰模块需要它们,那么JAR既可以被放置在类路径中(它们会被转化为无名模块,因此一直可被访问),也可以被放置在模块路径中(它们会被其他的机制放入模块图中)。

1)普通JAR由自动模块解析按照“全有全无”的方式引入。

2)平台模块和清晰应用程序模块在默认情况下不会得到解析,需要利用其他模块对它们进行依赖或是通过--add-modules进行手动添加。

考虑到多数甚至所有依赖在某个时间将从普通JAR转换成模块化JAR,这两种现象非常引人注意:它们暗示着模块路径中的传递依赖在作为普通JAR时工作正常;一旦被模块化,它们就会从模块图中消失。

现在关注第二点,并且考虑部分模块化依赖需要访问的模块。如果你或其他模块都不需要,它们就不会出现在模块图中,这导致依赖无法访问它们。

在这种情况下,你可以在模块描述符中指定对它们的依赖(别忘了加一个注释说明为什么要这样做),也可以在编译时和启动时用命令行参数添加它们。后文将根据具体场景简要讨论相应的利弊。

另外一个障碍是自动模块通过公有API公开的类型。假设某个项目(一个模块化JAR)依赖于一个类库(一个普通JAR),这个类库有一个返回Guava(同样是一个普通JAR)中ImmutableList对象的方法。

如果将这个项目连同这个类库放置于模块路径中,并将Guava放置于类路径中,会得到图8-11中展示的模块图:这个项目(清晰模块)读取这个类库(自动模块),后者又读取无名模块(包含Guava)。

此时如果代码调用返回ImmutableList对象的方法,那么针对这个类型的可访问性检查就不会按照你的期望完成,因为你的模块不会读取这个无名模块。

如果一个自动模块(本例中是org.lib)中的某个方法返回了无名模块中的类型(ImmutableList),那么具名模块(your.app)将无法访问它,因为它们不会读取无名模块。

如果该方法声明返回无法访问的类型(ImmutableList),这将导致应用程序崩溃,而声明一个超类型(这里很可能是List)则可以正常工作。

这并不是全新的概念。如果ImmutableList是该类库中的一个非公有类型,那么由于缺乏可见性,你也无法调用这个方法。

与本例中一样,它与所声明的返回类型相关。如果该方法声明返回一个List类型,并且选择ImmutableList作为具体返回类型,那么一切正常。这与API声明的类型相关,而非其返回的类型。

因此,如果一个自动模块公开了来自于另一个JAR的类型,那么这个JAR也需要被放入模块路径中。否则,它的类型会包含在无名模块中,无法被清晰模块访问。这会导致IllegalAccessError错误,原因是缺少可读性边。

如果具名模块需要访问无名模块的情况无法避免,那么你只剩下一个选项——照这个需求的字面意义去做。

之前介绍的命令行选项--add-reads在指定目标值为ALL-UNNAMED时,可以添加一个从具名模块到无名模块的可读性边。这将使你的模块化代码与不可预测的类路径中的内容耦合到一起,所以这是万不得已的选项。

使用--add-reads后,前面提到的例子(Guava在类路径中并且自动模块返回一个ImmutableList)终于可以工作了。

如果所接收的ImmutableList实例(并且接下来无法通过可访问性检查)的清晰模块名为app,那么对编译器和运行时添加--add-readsapp=ALL-UNNAMED选项,可以保证应用程序正常工作。

介绍了这么多,那么在各种情况下应该选择何种路径呢?应该无条件选择自动模块,还是应该倾向于在类路径中保留尽可能多的依赖?下文会对这个问题进行讨论。

3 无条件选择自动模块

有了将普通JAR放置到模块路径中将它们转变为自动模块的方法,人们是否仍然需要类路径?难道不能将所有JAR放置到模块路径中,(根据是否包含描述符)将它们转变为清晰模块或自动模块吗?从技术上说,是的,可以这么做。尽管如此,本书并不推荐这种做法,下面来解释原因。

01. 普通JAR无法构造良好的模块

总的来说,普通JAR无法构造良好的模块,原因如下:

1)它们可能访问JDK内部API

2)它们可能在自身和JEE模块间造成包分裂;

3)它们无法表达自身的依赖。

如果它们转变为自动模块,模块系统就会将相应的规则强加于这些自动模块,这样你就要花大量时间来解决由此带来的诸多问题。

而这些问题中最典型一个就是,一旦某个普通JAR被升级为模块化JAR,它就不再被默认解析(参见表8-2和图8-10),所以针对项目依赖树上每个这样的升级,你将不得不手动添加依赖。

自动模块的唯一好处是,它们可以被清晰模块所需要,但是如果你不需要这一特性,那么将所有普通JAR转变为自动模块所带来的回报与麻烦相比将不值一提。

另一方面,如果将它们保留在类路径中,那么这些JAR会被转化为无名模块,这样一来:

1)在至少一个Java发行版本中,非法访问在默认情况下得到了允许;

2)JAR之间的分裂不会造成影响,但JAR和平台模块间的分裂仍会导致问题;

3) 如果它们包含应用程序入口,则可以读取所有Java SE平台模块;

4) 当普通JAR被升级为模块化JAR时,不用做任何事情。

这让开发工作变得更简单。

要点 尽管把任何事情都作为模块处理很让人兴奋,但本书仍然建议你尽量少(项目可以正常工作即可)把普通JAR放在模块路径中,其余的仍应放置在类路径中。

从另一方面讲,一个自动模块的模块化依赖需要被放置在模块路径中。因为它们是作为模块化JAR出现的,所以不需要模块系统像对待无名模块那样宽大地对待它们。如果作为模块加载,它们会从可靠配置和强封装中得到好处。

02. 自动模块作为连接类路径的桥梁

针对更少使用自动模块,有一个哲学观点:这将它们转变为连接模块化世界和混乱的类路径世界的桥梁。诸多模块处于桥的这一侧,它们的直接依赖为自动模块,而间接依赖在桥的另一侧。

每当有一个依赖被转变为清晰模块时,它就从桥上移动到模块化的那一侧,并将它的直接依赖作为自动模块拉到桥上。这就是前文提到过的自上而下的方式,后文在讨论模块化策略时将进一步对其进行观察。

4 依赖自动模块

自动模块唯一的目标是让代码可以依赖于普通JAR,这样无须等待所有依赖都被模块化就能创建清晰模块。但这里有一条重要的忠告:如果JARmanifest文件中没有包含Automatic-Module-Name字段,那么依赖会很脆弱。

正如上面所解释的,没有这个字段,自动模块的名称就是由文件名推断出来的。但是,根据设置,不同的项目中同一个JAR会使用不同的名称。

此外,大多数项目会使用一个基于Maven的本地仓库。在仓库中,JAR文件会按照${artifactID}-${version}的方式命名,而模块系统很可能将其中的${artifactID}作为自动模块的名称。

这就会造成问题,因为工件ID通常不遵循前文讲到定义的反向域名命名规则:一旦项目被模块化,模块名很有可能会改变。

由于Google的Guava被广泛使用,本书仍然会将它作为一个典型的例子。如之前所见,对于guava-20.0.jar,模块系统推导出guava作为自动模块名称。这是Maven本地仓库中的文件名称,但是在其他项目中可能会有不同的设置。

假设命名规则为${groupID}-${artifactID}-${version},这样,JAR文件将被命名为com.google.guava-guava-20.0.jar,而自动模块名称为com.google.guava.guava

另外,一个模块化的Guava会被命名为com.google.common,所以没有一个自动模块的名称是正确的。

总的来说,同一个JAR在不同项目(依赖于项目设置)中,或者在不同时间(模块化前后)可能会有不同的模块名。这有可能造成很大的问题。

思考一下你最喜欢的项目。想象一下,某个依赖以自动模块的形式引用了它的一个依赖,而这个自动模块的名称没有遵循项目设置(如图8-12所示)。

也许这个依赖以${groupID}-${artifactID}-${version}命名文件,而你所使用的Maven的命名规则为${artifactID}-${version}。现在这个依赖需要自动模块${groupID}.${artifactID},但是模块系统会在你的项目中用${artifactID}进行推断。

这会让构建失败——虽然有一些办法能够将其修复),但没有一个是令人愉快的。

依赖org.lib通过构建过程中得到的自动模块名称(com.google.guava.guava)依赖于Guava。但很不幸,在系统中,这个工件叫作guava.jar,所以模块名为guava。如果不进一步采取措施,模块系统会抱怨依赖丢失。

不仅如此,事情还会变得更糟糕!还是在同一个项目中,如果添加另一个依赖,而这个依赖需要同一个自动模块,但是使用了不同的名称(如图8-13所示),这就是之前提到的模块的死亡之眼:同一个JAR无法满足针对不同名称的模块的需求,并且多个相同内容的JAR由于针对包分裂的规则而无法工作。请不惜一切代价来避免这种情况!

另一个依赖,即com.framework,也依赖于Guava,但是使用了不同的名称(guava)。现在,同一个JAR需要以两个不同的具名块的形式出现——这是不可能的。

看起来,关键性的错误在于通过一个基于文件名的模块名称来依赖一个普通JAR。但实际情况并不是这样——如果开发者能完全掌控此类自动模块的模块描述符,那这种方法在应用程序中或者其他场景中是没问题的。

“压垮骆驼的最后一根稻草”是将带有这种依赖的模块发布到一个公共仓库。只有在这种情况下,用户才有可能将模块明确依赖到无法控制的细节上,进而不得不采取额外的措施,甚至导致无法解决的冲突。

结论是,如果模块依赖于manifest文件中不包含Automatic-ModuleName字段的普通JAR,请永远不要将这样的模块发布到可公开访问的仓库,因为只有带有这个字段,自动模块的名称才足够稳定、值得依赖。

是的,这也许意味着你不能为类库或者框架发布模块化的版本,而只能等待依赖添加这个字段。这很不幸,但是草率地发布模块化版本会对你的用户产生很大伤害。

提示 关于迁移和模块化,本书目前已经介绍了能够影响现有代码的所有挑战和机制。后文将继续探索对它们的最佳实践。之后,后面会讲授模块系统更高级的特性。

小结

1 在增量模块化的过程中,人们往往会使用类路径和模块路径。理解以下两点非常重要:类路径中的任何JAR(普通JAR或模块化JAR)都会被转换成无名模块;

模块路径中的任何JAR都会被转换成具名模块,即自动模块(对于普通JAR来说)或者清晰模块(对于模块化JAR来说)。这使JAR的用户(而非它的创建者)能够决定它是否要变成一个具名模块。

2 无名模块是一种兼容性特性,能够让模块系统与类路径一起工作。

1) 它没有名称,会抓取类路径中的内容、读取所有其他模块、导出和开放所有包。

2) 因为没有名称,清晰模块无法在模块描述符中对其进行引用。一个后果就是,它们无法读取无名模块,并因此无法使用类路径中定义的类型。

3) 如果无名模块是一个初始模块,就会有一系列特定规则保证正确的模块集合得到解析。总体上说,这些都是非JEE模块及其依赖。

这使得类路径中的代码无须进一步配置即可读取所有的Java SE API,以实现兼容性的最大化。

2 自动模块是一种迁移特性,能够使模块依赖于普通JAR

1) 模块系统会为模块路径中的每个JAR创建一个自动模块。它的名称通过JAR manifest文件中的Automatic-Module-Name字段定义,如果没有这个字段,则从JAR的文件名中获取。它读取包括无名模块在内的所有其他模块,并且导出和开放所有包。

2) 由于它是一个普通的具名模块,因此可以在模块声明中被引用,比如指明对它的依赖。这使正在被模块化的项目可以依赖于尚未被模块化的项目。

3) 一个自动模块的依赖可以放置在类路径中或者模块路径中。尽管用哪个路径要取决于项目的具体情况,但是默认方式是将模块化依赖放置在模块路径中,将普通依赖放置在类路径中,这也是较为合理的方式。

4) 随着第一个自动模块得到解析,其他自动模块也将逐一被解析。进而,任何模块只要读取一个自动模块,就会根据隐式可读性读取所有自动模块。在测试对自动模块的依赖时,这个情况要考虑在内。

Tags:

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表