JDK 9 introduces the Java Module System.
References:
- Project Jigsaw: Module System Quick-Start Guide @ http://openjdk.java.net/projects/jigsaw/quick-start.
- Project Jigsaw @ http://openjdk.java.net/projects/jigsaw/.
- JSR 376: Java Platform Module System @ http://openjdk.java.net/projects/jigsaw/spec/.
- JEP 200: The Modular JDK @ http://openjdk.java.net/jeps/200.
- JEP 201: Modular Source Code @ http://openjdk.java.net/jeps/201.
- JEP 220: Modular Run-Time Images @ http://openjdk.java.net/jeps/220.
- JEP 260: Encapsulate Most Internal APIs @ http://openjdk.java.net/jeps/260.
- JEP 261: Modular System @ http://openjdk.java.net/jeps/261.
- JEP 281: jlink: The Java Linker @ http://openjdk.java.net/jeps/281.
- JDK 9 Modules @ https://www.journaldev.com/13106/java-9-modules.
The most drastic change in JDK 9 is the Java Module System, which is carried out through the Project Jigsaw @ http://openjdk.java.net/projects/jigsaw/.
The current Java System suffers from these problems:
- The JDK is too big, with many legacy codes. This also impedes on the performance, test and maintenance.
- JAR files like
rt.jar
are too big to be used in small devices. - There is no strong encapsulation. The
public
access is too open. Some internal non-critical APIs likesun.*
,*.internal.*
, are accessible, as they arepublic
, which poses security issues.
I shall assume that you are familiar with Java package and CLASSPATH. Otherwise, read "Package and CLASSPATH".
Why module?
A module is a set of related packages and resources (code and data) that is designed for reuse. It adds a higher level of aggregation above the package. Each module has a module descriptor called module-info.java
, which is to be compiled into module-info.class
.
Before JDK 9, public
classes are accessible to ALL, resulted in many APIs got exposed unnecessarily. With modules, you can conceal packages for internal use only; or export packages to be used by other modules. You can further specify which classes within a package are to be shared with which modules, or you can set them to be accessible by any modules. In other words, you can fine-tune the public
access restriction of packages and classes to improve security with strong encapsulation and stability with reliable dependencies. "public
" on a class declaration no longer means accessible to everyone.
Module Dependency by Examples
Example 1: Hello Module - The module Statement
I shall assume that our project's base directory is "c:\myProject
", with a sub-directory "src
" for the source files and a sub-directory "mods
" for the compiled modules (instead of "classes
" for compiled classes). Create the directory structure.
Let's write a module called "com.hello
". By convention, the module's directory name is the same as the module's name. Create a sub-directory "com.hello
" under "src
".
Each module has a module descriptor file called "module-info.java
" under the top level directory of a module. Create the module descriptor "module-info.java
" under "src\com.hello
", as follows:
// src\com.hello\module-info.java module com.hello { }
This module com.hello
contains a package com.hellopack
, with a class Hello
(i.e., com.hellopack.Hello
). Recall that the dot in a package corresponds to a sub-directory, i,e, package com.hellopack
has a directory structure of "com\hellopack
". Create the directory structure "src\com.hello\com\hellopack
" for the package com.hellopack
under the module.
Write the main class com.hellopack.Hello
in "src\com.hello\com\hellopack\Hello.java
", as follows:
// src\com.hello\com\hellopack\Hello.java package com.hellopack; public class Hello { public static void main(String[] args) { System.out.println("hello, module"); } }
We shall keep our compiled module in a sub-directory called mods
under our project base directory (c:\myProject
). Our compiled module com.hello
shall be kept in directory mods\com.hello
.
Compile the module as follows, where -d
option specifies the directory of the generated class file, which will be created automatically.
> c:
> cd \myProject
> javac -d mods/com.hello src/com.hello/module-info.java src/com.hello/com/hellopack/Hello.java
// Note: You can use either forward slash (Unix and macOS) or
// backward slash (Windows) as path separator in the javac command
The mods\com.hello\module-info.class
and mods\com.hello\com\hellopack\Hello.class
shall be generated, as shown:
> tree /f /a
+---mods
| +---com.hello
| | module-info.class
| |
| \---com
| \---hellopack
| Hello.class
\---src
+---com.hello
| module-info.java
|
\---com
\---hellopack
Hello.java
You can run the main class as follows, where -p
(or --module-path
) specifies the module path, -m
specifies the main class. Both -p
and -m
are new options in JDK 9:
> c:
> cd \myProject
> java -p mods -m com.hello/com.hellopack.Hello
hello, module
Notes:
- JDK 9 introduces module-path for locating module, similar to classpath for locating classes prior to JDK 9.
- In this example, I named the module and package differently for academic purpose. In practice, the module and package could be given the same name.
Example 2: Module Dependency - Directives "exports <package>" and "requires <module>"
Let's create two modules: com.hi
and com.world
, such that com.hi
depends on (or reads) com.world
.
We need to start with the independent module com.world
. Create the module directory "com.world
" under "src
". Create the module descriptor module-info.java
under "src\com.world
", as follows:
// src\com.world\module-info.java module com.world { exports com.world; // This package can be used by ALL other modules }
The com.world
module contains a package called com.world
(having the same name as the module), which is exported to be used by ALL other modules.
The package com.world
contains a class called World
(i.e., com.world.World
), as follows:
// src\com.world\com\world\World.java package com.world; public class World { public static String getWorld() { return "world"; } }
Compile the module com.world
as follows:
> cd <project-base-path> > javac -d mods/com.world src/com.world/module-info.java src/com.world/com/world/World.java
Now, let us write the dependent module com.hi
, Create the module directory "com.hi
" under "src
". Create the module descriptor module-info.java
under "src\com.hi
", as follows:
// src\com.hi\module-info.java module com.hi { requires com.world; // Depends on this module // "requires" is analogous to "import" }
The module com.hi
contains a package com.hi
(having the same name as the module), with a class com.hi.Hello
. The Hello
class invokes method in the World
class (of the module com.world
):
// src\com.hi\com\hi\Hello.java package com.hi; import com.world.World; // from the "requires" module public class Hello { public static void main(String[] args) { System.out.println("hello, " + World.getWorld()); } }
Compile the module com.hi
as follows, where -p
(or --module-path
) specifies the module-path for locating the dependent module:
> cd <project-base-path> > javac -p mods -d mods/com.hi src/com.hi/module-info.java src/com.hi/com/hi/Hello.java
Run the Hello
class:
> java -p mods -m com.hi/com.hi.Hello
hello, world
The directory/file structure is as follows:
> tree /f /a
+---mods
| |
| +---com.hi
| | | module-info.class
| | |
| | \---com
| | \---hi
| | Hello.class
| |
| \---com.world
| | module-info.class
| |
| \---com
| \---world
| World.class
|
\---src
|
+---com.hi
| | module-info.java
| |
| \---com
| \---hi
| Hello.java
|
\---com.world
| module-info.java
|
\---com
\---world
World.java
Missing "requires <module>" Directive
Comment out the requires
directive in com.hi
's module descriptor src\com.hi\module-info.java
; and re-compile:
> javac -p mods -d mods/com.hi src/com.hi/module-info.java src/com.hi/com/hi/Hello.java
src\com.hi\com\hi\Hello.java:4: error: package com.world is not visible
import com.world.World;
^
(package com.world is declared in module com.world, but module com.hi does not read it)
Missing "exports <package>"Directive
Now comment out the exports
directive in com.world
's module descriptor src\com.world\module-info.java
; and re-compile:
> javac -d mods/com.world src/com.world/module-info.java src/com.world/com/world/World.java
> javac -p mods -d mods/com.hi src/com.hi/module-info.java src/com.hi/com/hi/Hello.java
src\com.hi\com\hi\Hello.java:4: error: package com.world is not visible
import com.world.World;
^
(package com.world is declared in module com.world, which does not export it)
Example 3: Packaging in Modular JAR Files
For deployment, we can package a module as a Modular JAR File, which is a regular JAR file with a module descriptor module-info.class
in its top-level directory.
We shall keep our modular JAR files in a sub-directory "mlib
". Create a sub-directory "mlib
" under "c:\myProject
".
Create modular JAR file for module com.world
as follows, where -c
(or --create
) for creating new JAR file, -f
(or --file
) specifies the name of the JAR file, -C
to change the directory:
> cd <project-base-path> > jar -c -f mlib/com.world.jar -C mods/com.world .
Create modular JAR file for module com.hi
as follows, where --main-class
specifies the main entry class of the module:
> jar -c -f mlib/com.hi.jar --main-class com.hi.Hello -C mods/com.hi .
You can run the modular JAR file directly as follows, where -p
(or --module-path
) specifies the module path, -m
(or --module
) specifies the module to be executed (both -p
and -m
are new options in JDK 9).
> java -p mlib -m com.hi
hello, world
CLASSPATH vs MODULEPATH
A CLASSPATH
is a sequence of classes' base paths and JAR files for the Java Compiler or JVM to locate the classes used in the application.
Similarly, in JDK 9, a MODULEPATH
is a sequence of modules' base paths and Modular JAR files for the Java Compiler or JVM to locate the modules used in the application.
Module's Directives
A module adds a higher level of aggregation above package. It is a set of related packages, as well as resources (such as images and XML files). By convention, a module is kept in a directory with the same name as the module name. Each module contains a module descriptor module-info
. The descriptor contains directives (such as requires
, exports
, uses
, provides...with
, opens
, to
and transitive
) to describe:
- the module's name
- the module's dependencies (via "
requires <module>
", "requires transitive <module>
") - the packages it explicitly makes available to other modules (via "
exports <package>
", "exports <package> to <modules>
") - the services it provides (via "
provides <service> with <implementationClass>
") - the services it consumes (via "
uses <service>
") - to what other modules it allows reflection (via "
opens <package>
")
// moduleName\module-info.java module moduleName { ...... // module directives }
requires <module>
A requires
directive specifies that this module depends on another module. When module A requires
module B, module A is said to read module B; and module B is read by module A.
requires transitive <module>
Other modules that read this module also read this dependency (known as implied readability). For example, if module A requires transitive
module B, and module C requires
module A. Module C can read module B without explicitly requires
module B.
exports <package>
Specify a package (of this module) whose public
types are accessible to ALL other modules.
exports <package> to <modules>
Further specify, in a comma-separated list, which module's code can access the exported package. For example, [TODO]
uses <service>
Specify a service used by this module - making this module a service consumer. A service is an instance of a class that implements the interface or extends the abstract
class specified in the uses
directive.
provides <service> with <implementationClass>
Specify that a module provides a service implementation - making this module a service provider.
opens <package>
Before JDK 9, reflection could be used to learn about all types in a package and all members of a type - even its private members. In JDK 9, the opens packageName
specifies that the package’s public types are accessible to code in ALL other modules at runtime only. Also, all the types in the specified package (and all of the types’ members) are accessible via reflection.
opens <package> to <modules>
To the specified modules (commas-separated list) only.
Service Consumer and Service Provider Modules By Example
Reference: JDK 9 Modules - Creating Services and Service Providers @ https://www.logicbig.com/tutorials/core-java-tutorial/modules/services.html.
Services allow for loose coupling between service consumer modules and service provider modules.
A service consumer module declares that it uses
one or more interfaces whose implementations will be provided at runtime by some service provider module. A service provider module declares what implementations of service interface it provides...with
.
The Service Consumer Module
Let's call our service consumer module com.hello.consumer
. It contains a package com.hello.consumer
, and consume a service defined in interface com.hello.consumer.HelloService
(to be provided by a service provider).
// Module "com.hello.consumer" Module Descriptor // src\com.hello.consumer\module-info.java module com.hello.consumer { exports com.hello.consumer; uses com.hello.consumer.HelloService; // service's interface }
// Service Interface // src\com.hello.consumer\com\hello\consumer\HelloService.java package com.hello.consumer; import java.util.Iterator; import java.util.ArrayList; import java.util.List; import java.util.ServiceLoader; public interface HelloService { // Return a List of implementations for this service. public static List<HelloService>getInstances() { ServiceLoader<HelloService> services = ServiceLoader.load(HelloService.class); Iterator <HelloService> iter = services.iterator(); if (!iter.hasNext()) throw new RuntimeException("No service providers found!"); List<HelloService> lst = new ArrayList<>(); iter.forEachRemaining(lst::add); return lst; } void sayHello(String msg); // (public abstract) to be provided by the implementation class }
// Compile the module from "src" into "mods" > cd <project-base-directory> > javac -d mods/com.hello.consumer src/com.hello.consumer/module-info.java src/com.hello.consumer/com/hello/consumer/HelloService.java // Create Modular JAR file in "mlib" > jar --create --file mlib/com.hello.consumer.jar -C mods/com.hello.consumer .
How does ServiceLoader find a class?
The ServiceLoader
can find and instantiate classes that implement a specific interface. When we call the static
method ServiceLoader.load(interfaceName)
, it returns a list of classes that implement this interface. The JDK 9's module declares the services that a ServiceLoader
can load, and a module can also declare what services it may need to load via the ServiceLoader
.
The Service Provider Module
Our service provider module called com.hello.provider
, provides an implementation for the service.
// Module "com.hello.provider" Module Descriptor // src\com.hello.provider\module-info.java module com.hello.provider { requires com.hello.consumer; provides com.hello.consumer.HelloService with com.hello.provider.HelloServiceImpl; // provides an implementation for the service. }
// Implementation class of the service
// src\com.hello.provider\com\hello\provider\HelloServiceImpl.java
package com.hello.provider;
import com.hello.consumer.HelloService;
public class HelloServiceImpl implements HelloService {
@Override
public void sayHello(String msg) {
System.out.println("hello, " + msg);
}
}
// Compile the module from "src" into "mods" > cd <project-base-directory> > javac -p mlib -d mods/com.hello.provider src/com.hello.provider/module-info.java src/com.hello.provider/com/hello/provider/HelloServiceImpl.java // Create Modular JAR file in "mlib" > jar --create --file mlib/com.hello.provider.jar -C mods/com.hello.provider .
Service Client Module
Now, write a client module that invokes the service.
// Module "com.hello.client" Module Descriptor
// src\com.hello.client\module-info.java
module com.hello.client {
requires com.hello.consumer;
}
// src\com.hello.client\com\hello\client\Main.java package com.hello.client; import com.hello.consumer.HelloService; import java.util.List; public class Main { public static void main(String[] args) { List<HelloService> services = HelloService.getInstances(); for (HelloService service : services) { System.out.println(service.getClass().getName()); // print the implementation classname service.sayHello("service"); } } }
// Compile the module > javac -p mlib -d mods/com.hello.client src/com.hello.client/module-info.java src/com.hello.client/com/hello/client/Main.java // Create JAR > jar --create --file mlib/com.hello.client.jar --main-class=com.hello.client.Main -C mods/com.hello.client . // Run > java -p mlib -m com.hello.client com.hello.provider.HelloServiceImpl hello, service
New Tools to Support Modules
jlink - The Java Linker (JEP 282)
The goal of JEP 282 is to "create a tool that can assemble and optimize a set of modules and their dependencies into a custom run-time image". jlink
is the linker tool and can be used to link a set of modules, along with their transitive dependence, to create a custom modular run-time image.
The following example creates a run-time image that contains the module com.hello.client
and its transitive dependences:
// For Windows > cd <project-base-path> > jlink --module-path "%JAVA_HOME%/jmods";mlib --add-modules com.hello.client --output helloapp // For Unix and macOS $ cd <project-base-path> $ jlink --module-path $JAVA_HOME/jmods:mlib --add-modules com.hello.client --output helloapp
Notes:
- The module path "
%JAVA_HOME%/jmod
" is the directory containingjava.base.jmod
and other standard and JDK modules. - The module path "
mlib
" is the directory containing our modulecom.hello.client
. - The module
com.hello.client
shall be packaged in modular JAR or JMOD format (in the module path).
The output "helloapp
" is a directory (36 MB), as follows:
> tree /a helloapp
HELLOAPP
+---bin
| \---server
+---conf
| \---security
| \---policy
| +---limited
| \---unlimited
+---include
| \---win32
+---legal
| \---java.base
\---lib
+---security
\---server
The modules are kept under "helloapp\lib\modules
" (22 MB)?!
To run the Main
class:
// Run the "Main" class > cd <project-base-path> > helloapp\bin\java -m com.hello.client/com.hello.client.Main Exception in thread "main" java.lang.RuntimeException: No service providers found! // Re-link with provider module > jlink --module-path "%JAVA_HOME%/jmods";mlib --add-modules com.hello.client,com.hello.provider --output helloapp // Run the "Main" class again > helloapp\bin\java -m com.hello.client/com.hello.client.Main
com.hello.provider.HelloServiceImpl
hello, service
JDK 9 Introduces a new optional phase, link time, which is in-between compile time and run time, during which a set of modules can be assembled and optimized into a custom runtime image.
jmod Tool and jmod Files
JDK 9 Introduces the JMOD format, which is a packaging format similar to JAR except it can include native code and configuration files. It also introduces a jmod
tool for creating jmod
files.
[TODO]
The Modular JDK
JDK 9 makes the following changes to support modular JDK.
The Modular JDK (JEP 200)
JDK has grown over the years, with massive amount of legacy codes. But the Java platform has primarily been a monolithic one-size-fits-all solution.
The goal is to divide the JDK into a set of modules that can be combined at compile time, build time and run time into a variety of configurations.
The modular JDK implements these design principles:
- Standard modules, whose specifications are governed by the JCP (Java Community Process), start with "
java.
".- Base Module
java.base
: defines the foundational APIs of the Java SE Platform, including core JDK packages such asjava.lang
,java.util
,java.io
,java.nio
,java.net
,javax.net
,java.text
,java.security
,javax.cryptio
.
The Base Module is an independent module and does NOT depends on any other modules. By default, all other modules depends on this module.
The Base Module is also the default module for all JDK modules and user-defined modules. - Module
java.desktop
: Defines the AWT and Swing user interface toolkits, plus APIs for accessibility, audio, imaging, printing, and JavaBeans, including packagesjava.awt
,java.awt.event
,java.awt.*
,java.beans
,javax.imageio
,javax.print
,javax.swing
,javax.swing.*
,javax.sound.*
. - Module
java.sql
: defines the JDBC API, including packagesjava.sql
,javax.sql
. - Others
- Base Module
- All other modules that are part of JDK, start with "
jdk.
". - JavaFX modules, starts with "
javafx.
".
A new option --list-modules
is provided in java
tool to list all the modules, for example,
> java --list-modules java.activation@10.0.1 java.base@10.0.1 ......
JEP 201: Modular Source Code (JEP 201)
The current source code JAR files are too big, especially rt.jar
. The goal of this JEP is to "reorganize the JDK source code into modules, enhance the build system to compile modules, and enforce module boundaries at build time".
Modular Run-Time Images (JEP 220)
The main goal of this JEP is to "restructure the JDK and JRE run-time images to accommodate modules and to improve performance, security, and maintainability".
The JDK 9 does not have a "jre
" sub-directory. But it has a "jmods
" sub-directory, which contains the Java modules (such as java.activation.jmod
, java.base.jmod
, etc.). In other words, In JDK 9, JRE is separated into a separate distribution jmods
.
JDK 9 removes rt.jar
or tools.jar
.
Encapsulate Most Internal APIs (JEP 260)
The main goal of this JEP is to "encapsulate most of the JDK's internal APIs by default so that they are inaccessible at compile time, and prepare for a future release in which they will be inaccessible at run time".
REFERENCES & RESOURCES