Project Jigsaw: JDK Modularization Tips
Terminology
Classpath mode
At compile-time, a Java compiler does not directly compile a module declaration.
Observability (JLS 7.3) is determined solely by the host system, and consistently for all compilation units. The host system typically considers classes on the classpath and/or sourcepath as the entire observable universe (plus classes named on the command line).
This is how Java works today.
Module mode
At compile-time, a Java compiler directly compiles a module declaration. Observability is determined jointly by the host system and the module system, and on a per-module basis. The host system typically considers classes on the modulepath and/or modulesourcepath as part of the observable universe, in addition to classes observable through the module system (and classes named on the command line).
At runtime, the JVM makes classes visible to each other based on module definitions.
This is how the Jigsaw prototype works when asked to run a module from a module library rather than a class from the class path.
JDK Modularization Tips
ClassLoader != null
In classpath mode, all classes in rt.jar are loaded by the boot
class loader, and for any such class the
getClassLoader()
method returns null, for example
Object.class.getClassLoader() == null
.
In module mode, the classes that were previously contained in
rt.jar are contained in modules. As of the current Jigsaw
implementation the class loader of all classes contained within
some modules is no longer null, for example
com.sun.net.httpserver.HttpServer.class.getClassLoader() !=
null
(this class is in the jdk.httpserver module).
Specifically, all classes contained in modules tightly coupled with
the jdk.base module and all classes contained in the jdk.base
module will have a null class loader, all other classes will have a
non-null class loader. In time we expect that all modules that
comprise the JDK will be loaded by module class loaders so
getClassLoader
will never return null. To get to that
point requires addressing all places in the code that assume that
the class loader is null.
Class.forName
Class.forName(String className)
will throw
ClassNotFoundException
if the Class object for the
given string name cannot be found, which may be because the class,
that is visible in classpath mode, is no longer visible in module
mode. For example, if module B depends on (requires) module A then
only the classes that module A exports are visible to classes in
module B. Furthermore,
Class.forName("sun.misc.BASE64Decoder")
will throw a
ClassNotFoundException since the package sun.misc
is
never exported by the jdk.base module for general visibility.
Class.forName(String className)
will use the class
loader of the caller.
Class.forName(String className, boolean initialize,
Classloader loader)
will throw
ClassNotFoundException
in module mode whereas in
classpath mode a Class object is returned. If the loader parameter
is null then the class loader of the caller is used.
It is common behavior throughout the JDK and in many
libraries/applications to utilize a system property to declare a
fully qualified class name and obtain the Class object given that
name. With module mode then ClassNotFoundException
may
be thrown. Such cases may be candidates for modules services with
ServiceLoader
. Often this pattern is used in
conjunction with ServiceLoader
. In such cases it needs
to be clarified if the specified behavior is compatible or requires
changing. For, example if the implementation first looks at the
system property and fails to load the class does that result in an
exception or an attempt to use ServiceLoader
?
ClassLoader delegation
In module mode there is no class loader delegation using the traditional class loader hierarchy. Each module class loader will delegate at most once to the class loader of the module that defines the requested class.
Class loaders can be easily propagated, for example with the
thread context class loader. With module mode the class loader of a
module might be propagated and utilized for cases that result in
ClassNotFoundException
being thrown. For example, if C
requires B and B requires A, and A only permits that B may require
A, then at runtime, classes in C cannot access, using the class
loader of A (obtained for example by propagation of thread local
context), classes that are visible only to B.
META-INF/services
Code that explicitly parses META-INF/services
files
to load service implementation classes will be problematic in
module mode. The same visibility rules that apply to classes also
apply to resources. Such code should, where possible, be modified
to use ServiceLoader
.