Java Platform Module System
Goals of java platform module system
- Reliable configuration, use module path to replace class path
- Strong encapsulation, control visibility of public classes in a module
All source code is available at github.
1 Defining modules
A module can contain java class, interface, xml and json file etc.
module-info.java should be at the root of module's source file hierarchy.
1.1 Module declaration
Define a module com.foo.bar
module com.foo.bar { }
Module com.foo.bar depends on module org.baz.qux at compile time and run time
module com.foo.bar {
requires org.baz.qux;
}
Public classes in package com.foo.bar.alpha and com.foo.bar.beta are visible to other modules
module com.foo.bar {
requires org.baz.qux;
exports com.foo.bar.alpha;
exports com.foo.bar.beta;
}
1.2 Module artifacts
A modular jar is a regular jar with module-info.class
JMOD - java platform class files are packed in jmod format
[yunpxu@yunpxu-mac Home]$ jmod list /Library/Java/JavaVirtualMachines/jdk-11.0.1.jdk/Contents/Home/jmods/java.base.jmod
...
classes/java/lang/Boolean.class
classes/java/lang/BootstrapMethodError.class
classes/java/lang/Byte$ByteCache.class
classes/java/lang/Byte.class
classes/java/lang/Character$CharacterCache.class
classes/java/lang/Character$Subset.class
classes/java/lang/Character$UnicodeBlock.class
classes/java/lang/Character$UnicodeScript.class
classes/java/lang/Character.class
...
classes/java/lang/Class.class
...
1.3 Module descriptor
An IDE or a build-time packaging tool can insert attributes such as a module’s version, title, description, and license. This information can be read at compile time and run time via the module system’s reflection facilities.
1.4 Platform module
Module java.base, base module doesn't depend on any other module, every other module depends on base module implicitly.
Java SE platform specification module names start with 'java', JDK specific module names start with 'jdk'.
2 Using Modules
2.1 Module path
Module path vs class path, system locate the whole modules in the module path rather than individual classes.
2.2 Resolution
Resolution is the process of computing how each module depends on each other.
Module com.foo.app depends on module com.foo.bar and platform module java.sql
module com.foo.app {
requires com.foo.bar;
requires java.sql;
}
Platform module java.sql
module java.sql {
requires java.logging;
requires java.xml;
exports java.sql;
exports javax.sql;
exports javax.transaction.xa;
}
Module graph for com.foo.app
Dark blue lines represent explicit dependence(requires), the light blue lines represent implicit dependence over base module.
2.2 Readability
The module system ensures every dependence is fulfilled by one and only one other module.
- There is no dependence relationship between module a and b, then we can define package com.foo in both module a and b.
- If module a depends on module b, then we can't define package com.foo in both module a and b.
2.3 Accessibility
Module com.foo.app can access
- Packages exported by module com.foo.bar and java.sql
- Packages exported by module java.base
Module com.foo.app can't access
- Packages exported by module org.baz.qux and java.xml and java.logging
2.4 Implied readability
With requires transitive, module com.foo.app can also access module java.xml and java.logging
module java.sql {
requires transitive java.logging;
requires transitive java.xml;
exports java.sql;
exports javax.sql;
exports javax.transaction.xa;
}
Implied readability.png
3 Compatibility & migration
3.1 The unnamed module
Jar files on class path are considered as the unnamed module.
- The unnamed module reads every other modules
- The unnamed module exports all of its packages
- A named module can't depend on the unnamed module
- If a package is defined in both a named module and the unnamed module then the package in the unnamed module is ignored.
3.2 Bottom-up migration
We'll build our jars without module-info.
[yunpxu@yunpxu-mac JavaModule]$ tree out/production/
out/production/
├── app
│ ├── com
│ │ └── foo
│ │ └── app
│ │ └── App.class
│ └── module-info.class
├── bar
│ ├── com
│ │ └── foo
│ │ └── bar
│ │ └── Bar.class
│ └── module-info.class
└── qux
├── module-info.class
└── org
└── baz
└── qux
└── Qux.class
12 directories, 6 files
[yunpxu@yunpxu-mac JavaModule]$ cd out/artifacts/
[yunpxu@yunpxu-mac artifacts]$ jar cvf org-baz-qux.jar -C ../production/qux org/baz/qux/
added manifest
adding: org/baz/qux/(in = 0) (out= 0)(stored 0%)
adding: org/baz/qux/Qux.class(in = 457) (out= 309)(deflated 32%)
[yunpxu@yunpxu-mac artifacts]$ jar cvf com-foo-bar.jar -C ../production/bar com/foo/bar/
added manifest
adding: com/foo/bar/(in = 0) (out= 0)(stored 0%)
adding: com/foo/bar/Bar.class(in = 916) (out= 562)(deflated 38%)
[yunpxu@yunpxu-mac artifacts]$ jar cvfe com-foo-app.jar com.foo.app.App -C ../production/app com/foo/app/
added manifest
adding: com/foo/app/(in = 0) (out= 0)(stored 0%)
adding: com/foo/app/App.class(in = 422) (out= 278)(deflated 34%)
[yunpxu@yunpxu-mac artifacts]$ java -cp 'com-foo-app.jar:com-foo-bar.jar:org-baz-qux.jar' com.foo.app.App
Bar
Qux
[yunpxu@yunpxu-mac artifacts]$ tree
.
├── com-foo-app.jar
├── com-foo-bar.jar
└── org-baz-qux.jar
0 directories, 3 files
The initial module graph would look like this
Initial module graph.pngWe can use jdeps to inspect class dependencies, org-baz-qux.jar depends on java.base only.
[yunpxu@yunpxu-mac artifacts]$ jdeps org-baz-qux.jar
org-baz-qux.jar -> java.base
org.baz.qux -> java.io java.base
org.baz.qux -> java.lang java.base
So we can improve the module graph by declaring module org.baz.qux
migrate-1.pngcom-foo-bar.jar depends on java.base, java.sql, java.xml and org.baz.qux which is identified as the unnamed module.
[yunpxu@yunpxu-mac artifacts]$ jdeps com-foo-bar.jar
com-foo-bar.jar -> java.base
com-foo-bar.jar -> java.sql
com-foo-bar.jar -> java.xml
com-foo-bar.jar -> not found
com.foo.bar -> java.io java.base
com.foo.bar -> java.lang java.base
com.foo.bar -> java.sql java.sql
com.foo.bar -> javax.xml.parsers java.xml
com.foo.bar -> org.baz.qux not found
com-foo-app.jar depends on java.base and com.foo.bar which is identified as the unnamed module.
[yunpxu@yunpxu-mac artifacts]$ jdeps com-foo-app.jar
com-foo-app.jar -> java.base
com-foo-app.jar -> not found
com.foo.app -> com.foo.bar not found
com.foo.app -> java.lang java.base
We can get the migrated module graph, since we know com.foo.app depends on com.foo.bar and com.foo.bar depends on org.baz.qux
migrate-2.png3.3 Automatic modules
Jar files on module path but without module declaration are considered automatic modules.
- Automatic module's name is derived from jar file name
- Automatic module can be read by other modules
- Automatic module can read every other module
- All packages in automatic module are exposed
3.4 Bridges to the class path
Two jar files on the class path contain classes in the same package.
- Try to remove one of the jar file, and put the other one in module path(automatic module)
- If these two jars are needed, then leave them in class path
4 Services
Service interface
module com.service {
exports com.service
}
Service implementation
module com.service.impl {
requires com.service;
provides com.service.MyService with
com.com.service.impl.MyServiceImpl
}
Service client
module com.client{
requires com.service;
uses com.service.MyService;
}
Iterable<MyService> services =
ServiceLoader.load(MyService.class);
5 Advanced topics
5.1 Reflection
An instance of java.lang.Module class represents a single module at run time.
An instance of java.lang.module.ModuleDescriptor class represents module descriptors.
Module java_sql = Driver.class.getModule();
System.out.println(java_sql);
System.out.println(java_sql.getDescriptor());
//output
module java.sql
module { name: java.sql@11.0.1, [transitive java.transaction.xa, mandated java.base, transitive java.xml, transitive java.logging], uses: [java.sql.Driver], exports: [javax.sql, java.sql] }
//module-info.java
module java.sql {
requires transitive java.logging;
requires transitive java.transaction.xa;
requires transitive java.xml;
exports java.sql;
exports javax.sql;
uses java.sql.Driver;
}
5.2 Reflective readability
The reflection API simply assume that any code that reflects upon some type is in a module that can read the module that defines that type.
String providerName
= System.getProperty("javax.xml.stream.XMLInputFactory");
if (providerName != null) {
Class providerClass = Class.forName(providerName, false,
Thread.getContextClassLoader());
XMLInputFactory.class.getModule()
.addReads(providerClass.getModule());
Object ob = providerClass.newInstance();
return (XMLInputFactory)ob;
}
// Otherwise use ServiceLoader
5.3 Class loaders
- Bootstrap and extension class loader load classes from platform modules
- Application class loader load classes from module path
5.4 Unnamed modules
- ClassLoader.getUnnamedModule()
5.5 Layers
Layer is created from
- A graph of modules in a Configuration
- A function that maps each module to a ClassLoader
[yunpxu@yunpxu-mac JavaModule]$ tree out/production/
out/production/
├── JavaModule
│ ├── AppTest$1.class
│ └── AppTest.class
├── app
│ ├── com
│ │ └── foo
│ │ └── app
│ │ └── App.class
│ └── module-info.class
├── bar
│ ├── com
│ │ └── foo
│ │ └── bar
│ │ └── Bar.class
│ └── module-info.class
└── qux
├── module-info.class
└── org
└── baz
└── qux
└── Qux.class
13 directories, 8 files
//boot layer
ModuleLayer bootLayer = ModuleLayer.boot();
//boot configuration
Configuration bootCfg = bootLayer.configuration();
//path of the module
ModuleFinder finder = ModuleFinder.of(Paths.get("/Users/yunpxu/IdeaProjects/JavaModule/out/production"));
//configuration for module com.foo.app
Configuration appCfg = bootCfg.resolve(finder, ModuleFinder.of(), Set.of("com.foo.app"));
//print dependence for module com.foo.app
appCfg.modules().stream().forEach(module1 -> {
System.out.format("%s -> %s%n", module1.name(), module1.reads().stream().map(ResolvedModule::name).collect(Collectors.joining(", ")));
});
//AppClassLoader->PlatformClassLoader->null
ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
//create a new layer for module com.foo.app
ModuleLayer appLayer = bootLayer.defineModulesWithOneLoader(appCfg, systemClassLoader);
ClassLoader appClassLoader = appLayer.findLoader("com.foo.app");
ClassLoader barClassLoader = appLayer.findLoader("com.foo.bar");
ClassLoader quxClassLoader = appLayer.findLoader("org.baz.qux");
System.out.println("defineModulesWithOneLoader");
System.out.format("com.foo.app %s %s%n", appClassLoader, appClassLoader.getParent());
System.out.format("com.foo.bar %s %s%n", barClassLoader, barClassLoader.getParent());
System.out.format("org.baz.qux %s %s%n", quxClassLoader, quxClassLoader.getParent());
appLayer = bootLayer.defineModulesWithManyLoaders(appCfg, systemClassLoader);
appClassLoader = appLayer.findLoader("com.foo.app");
barClassLoader = appLayer.findLoader("com.foo.bar");
quxClassLoader = appLayer.findLoader("org.baz.qux");
System.out.println("defineModulesWithManyLoaders");
System.out.format("com.foo.app %s %s%n", appClassLoader, appClassLoader.getParent());
System.out.format("com.foo.bar %s %s%n", barClassLoader, barClassLoader.getParent());
System.out.format("org.baz.qux %s %s%n", quxClassLoader, quxClassLoader.getParent());
Function<String, ClassLoader> classLoaderFunc = (m) -> {
if (m.contains("foo")) {
return systemClassLoader;
} else {
return new ClassLoader() {
};//dummy class loader
}
};
appLayer = bootLayer.defineModules(appCfg, classLoaderFunc);
appClassLoader = appLayer.findLoader("com.foo.app");
barClassLoader = appLayer.findLoader("com.foo.bar");
quxClassLoader = appLayer.findLoader("org.baz.qux");
System.out.println("defineModulesWithCustomLoaders");
System.out.format("com.foo.app %s %s%n", appClassLoader, appClassLoader.getParent());
System.out.format("com.foo.bar %s %s%n", barClassLoader, barClassLoader.getParent());
System.out.format("org.baz.qux %s %s%n", quxClassLoader, quxClassLoader.getParent());
Module | Dependency |
---|---|
com.foo.app | com.foo.bar, java.base |
com.foo.bar | java.sql, java.transaction.xa, java.base, java.xml, java.logging, org.baz.qux |
org.baz.qux | java.base |
defineModulesWithOneLoader
Module | ClassLoader | Parent ClassLoader |
---|---|---|
com.foo.app | jdk.internal.loader.Loader@68be2bc2 | jdk.internal.loader.ClassLoaders$AppClassLoader@512ddf17 |
com.foo.bar | jdk.internal.loader.Loader@68be2bc2 | jdk.internal.loader.ClassLoaders$AppClassLoader@512ddf17 |
org.baz.qux | jdk.internal.loader.Loader@68be2bc2 | jdk.internal.loader.ClassLoaders$AppClassLoader@512ddf17 |
defineModulesWithManyLoaders
Module | ClassLoader | Parent ClassLoader |
---|---|---|
com.foo.app | jdk.internal.loader.Loader@c038203 | jdk.internal.loader.ClassLoaders$AppClassLoader@512ddf17 |
com.foo.bar | jdk.internal.loader.Loader@8bd1b6a | jdk.internal.loader.ClassLoaders$AppClassLoader@512ddf17 |
org.baz.qux | jdk.internal.loader.Loader@18be83e4 | jdk.internal.loader.ClassLoaders$AppClassLoader@512ddf17 |
defineModulesWithCustomLoaders
Module | ClassLoader | Parent ClassLoader |
---|---|---|
com.foo.app | jdk.internal.loader.ClassLoaders$AppClassLoader@512ddf17 | jdk.internal.loader.ClassLoaders$PlatformClassLoader@4501b7af |
com.foo.bar | jdk.internal.loader.ClassLoaders$AppClassLoader@512ddf17 | jdk.internal.loader.ClassLoaders$PlatformClassLoader@4501b7af |
org.baz.qux | AppTest$1@d8355a8 | jdk.internal.loader.ClassLoaders$AppClassLoader@512ddf17 |
5.6 Qualified exports
Exports sun.reflect to all modules.
module java.base {
...
exports sun.reflect;
}
With exports sun.reflect to, sun.reflect is exported to specified modules only.
module java.base {
...
exports sun.reflect to
java.corba,
java.logging,
java.sql,
java.sql.rowset,
jdk.scripting.nashorn;
}
6 Reference
The State of the Module System
Idea getting-started-with-java-9-module-system