ModuleLayer 和 ClassLoader 是什么关系?

What is the relation between ModuleLayer and ClassLoader?

让我们考虑以下情况。一共有三层。

BootLayer (moduleA)
     |
     |_______Child1(moduleB)
                |
                |__________ Child2(moduleC)

Child1是BootLayer的子层,Child2是Child1的子层。

Child1Child2 由相同的代码创建:

ClassLoader parentClassLoader = ClassLoader.getSystemClassLoader();
ModuleLayer layer = parentLayer.defineModulesWithOneLoader(cf, parentClassLoader);

如您所见,两个子层的父 class 加载器都是 SystemClassLoader。但是,moduleB 可以使用 moduleA 的 class,但是 moduleC 可以使用 moduleAmoduleB 的 class。

我是 class 加载的初学者,但我读过 parent-first delegation model。但是,如果两个子层都使用了 SystemClassLoader,那么为什么他们从其他层看到 classes?谁能解释一下?

首先,我假设Child2的父层是Child1。


让我们从 specification:

开始

Module membership is defined in terms of run-time packages (§5.3). A program determines the names of the packages in each module, and the class loaders that will create the classes and interfaces of the named packages; it then specifies the packages and class loaders to an invocation of the defineModules method of the class ModuleLayer. Invoking defineModules causes the Java Virtual Machine to create new run-time modules that are associated with the run-time packages of the class loaders.

这里重要的部分是:定义模块层时,必须知道每个模块的包名称。

A run-time module is implicitly part of exactly one layer, by the semantics of defineModules. However, a class loader may create classes in the run-time modules of different layers, because the same class loader may be specified to multiple invocations of defineModules. Access control is governed by a class's run-time module, not by the class loader which created the class or by the layer(s) which the class loader serves.

Class 加载程序委派无关紧要。

There are similarities and differences between class loaders and layers. On the one hand, a layer is similar to a class loader in that each may delegate to, respectively, one or more parent layers or class loaders that created, respectively, modules or classes at an earlier time. That is, the set of modules specified to a layer may depend on modules not specified to the layer, and instead specified previously to one or more parent layers. On the other hand, a layer may be used to create new modules only once, whereas a class loader may be used to create new classes or interfaces at any time via multiple invocations of the defineClass method.

(强调我的)


这应该足以回答您的问题:

  • 由于Child1的父层是boot层,Child1中的模块可以依赖boot层的模块
  • 由于Child2的父层是Child1,所以Child2层的模块可以依赖Child1和boot层的模块

Alan Bateman 在 jigsaw-dev 邮件列表中给出了答案,并张贴在这里。

模块层是一个高级主题。类加载器也是一个高级主题。当使用模块层并使用 defineModulesWithXXX 方法创建模块层时,您通常不需要太在意 class 加载程序。它们仍然用于加载 classes,但它们大多在后台(而不是在您的脸上)。

您也不必太在意指定给 defineOneWithOneLoader 方法的 "parent class loader"。从模块加载 class 时不使用它,仅当 moduleBmoduleC 中的代码尝试加载不在模块中的 class 时才使用,也许 Class.forName("Foo") 其中 Foo 在 class 路径上。所以最好在开始时忽略父 class 加载器。

API docs 解释了委派如何与模块一起工作,但对于此处需要的内容可能不够清楚。在您的示例中,支持 L1 是子层 1 中 moduleB 的 class 加载器,而 L2 是子层 1 中 moduleC 的 class 加载器子层 2。进一步假设模块声明是:

module moduleC {
     requires moduleB;
}

module moduleB {
     exports b;
}

Child1 的配置非常简单:一个 moduleB 读作 java.base

Child2的配置也很简单:一个moduleC读作moduleBjava.base.

创建 Child1 时,它将创建 L1 并将 moduleB 映射到 L1。当 moduleB 中的代码试图在其自己的模块中解析对 class 的引用时,它将被 L1 加载(无委托)。当 moduleB 引用 java.base 中的 class 时,L1 将委托给 boot loader.

创建 Child2 时,它将创建 L2 并将 moduleC 映射到 L2。当 moduleC 中的代码试图在其自己的模块中解析对 class 的引用时,它将被 L2 加载(无委托)。当 moduleC 引用 b.* class 时,它将委托给 L1 来解析引用。当 moduleC 引用 java.base 中的 class 时,L2 将委托给引导加载程序。

如果你把它画出来,那么你应该看到 class 加载程序委托是 "direct delegation" 并且完全反映了可读性图表(配置对象)中的边缘。

希望这足以让您入门。它可能真的需要图表来解释其中的一些细节。正如我所说,在使用模块层时,您几乎可以忽略 class 加载器细节。