Let's consider the following situation. There are three layers.
BootLayer (moduleA)
|
|_______Child1(moduleB)
|
|__________ Child2(moduleC)
Child1 is the child of the BootLayer, Child2 is the child of the Child1.
Child1
and Child2
was created by the same code:
ClassLoader parentClassLoader = ClassLoader.getSystemClassLoader();
ModuleLayer layer = parentLayer.defineModulesWithOneLoader(cf, parentClassLoader);
As you see the parent class loader of both child layers is SystemClassLoader. However, moduleB
can use classes of moduleA
, but moduleC
can use classes of moduleA
and moduleB
.
I am a beginner at classloading, but I've read about parent-first delegation model
. However, if for both child layers SystemClassLoader is used, then why they see classes from other layers? Could anyone explain?
The answer was given by Alan Bateman in jigsaw-dev mailing list and is posted here.
Module layers are an advanced topic. ClassLoaders are also an advanced topic. When working with module layers and using the defineModulesWithXXX
methods to create the module layers, then you mostly don't need to be too concerned with class loaders. They are still used to load classes but they are mostly in the background (and not in your face).
You also don't need to be too concerned with the "parent class loader" that you specify to the defineOneWithOneLoader
method. It's not used when loading classes from modules, it's only when for cases where the code in moduleB
or moduleC
tries to load a class that is not in a module, maybe Class.forName("Foo")
where Foo
is on the class path. So probably best to ignore the parent class loader when starting out.
The API docs explain how the delegation works with modules but maybe it's not clear enough for what is needed here. In your example, support L1
is the class loader for moduleB
in child layer 1, and L2
is the class loader for moduleC
in child layer 2. Further suppose the module declarations are:
module moduleC {
requires moduleB;
}
module moduleB {
exports b;
}
The configuration for Child1
is very simple: one moduleB
that reads java.base
The configuration for Child2
is also very simple: one moduleC
that reads moduleB
and java.base
.
When Child1
is created it will create L1
and map moduleB
to L1
. When code in moduleB
tries to resolve a reference to a class in its own module then it will be loaded by L1
(no delegation). When moduleB
references a class in java.base
then L1
will delegate to the boot loader
.
When Child2
is created it will create L2
and map moduleC
to L2
. When code in moduleC
tries to resolve a reference to a class in its own module then it will be loaded by L2
(no delegation). When moduleC
references a b.*
class then it will be delegate to L1
to resolve the reference. When moduleC
references a class in java.base then L2
will delegate to the boot loader.
If you draw this out then you should see that the class loader delegation is "direct delegation" and exactly mirrors the edges in the readability graph (Configuration object).
Hopefully this is enough to get your started. It really needs diagrams and graphs to explain some of these details probably. As I said, you can mostly ignore class loader details when working with module layers.