Chapter 21. Java Module System

Java 9 introduces the Project Jigsaw, which both adds modularization to the platform and modularizes the JDK itself. The goal of Jigsaw is twofold: to enable reliable configuration and add strong encapsulation to Java. With modularization, it is now possible to restrict which packages are public and also ensure that runtime dependencies are present when an application is launched.

The Java Platform Module System (JPMS) is implemented as a separate layer within the JVM. This distinguishes it from other module systems, such as OSGi, which are implemented using Classloaders. JPMS enables modularization of the JDK itself.

Project Jigsaw

Project Jigsaw is made up of a JSR (Java Specification Request) and multiple JEPs (JDK Enhancement Proposals). The specifications that make up Jigsaw are as follows:

  • JSR 376 Java Platform Module System

  • JEP 200: Modular JDK

  • JEP 201: Modular Source Code

  • JEP 220: Modular Runtime Images

  • JEP 260: Encapsulate Most Internal APIs

  • JEP 261: Module System

  • JEP 282: jlink: Java Linker

The main project page for Jigsaw has links to each of these specifications.

Java Modules

Java modules are a JAR file containing a module-info.java file in the default package. Since “module-info” is an invalid Java class name, it is ignored by Java 8 and earlier. It does get compiled to bytecode and is available via reflection. The module file declares the name of the module, dependencies of a module, and which packages are exported by this module. Services provided by this module can also be specified in this file.

The module system has the following rules:

  • Modules specify which packages are exported. Public types within these packages are available to other modules.

  • Packages not exported are not accessible and Java reflection cannot be used to access types at runtime.

  • Module names must be globally unique. Reverse domain names should be used as the module name.

  • Only one version of a module may be loaded. Multiple versions can be loaded using layers (see java.lang.ModuleLayer).

  • Module dependency graph cannot contain cycles.

  • All modules’ dependencies must be present on startup. Any missing dependencies result in an error.

  • Module path, analogous to the classpath, has been added to Java tools.

Java applications do not need to be modularized to run on Java 9. If the code is loaded using the classpath instead of the module path, the code will run as it did pre-Java 9. If code is loaded using the module path, then the module graph is resolved and dependencies are checked.

With a JAR file, how it is interpreted by the module system is dependent upon whether it is loaded on the module path or classpath and also whether it contains a module-info.java file. JPMS will create either an application module, unnamed module, or automatic module. Table 21-1 gives the breakdown of the behavior.

Table 21-1. JPMS loading behavior
--module-path -classpath

Modular JAR

Application module

Unnamed module

Nonmodular JAR

Automatic module

Unnamed module

Automatic Modules

JPMS will automatically create a module for JAR files added to the module path which are missing module-info.java. All packages in an automatic module are exported. The name of the automatic module is derived from the name of the JAR file. The rules for the module name are as follows:

  • “.jar” suffix is removed.

  • The module name will be extracted from the text preceding the hyphen of the first occurrence of the regular expressions -(\d(\.|$))+, and the version will be extracted after the hyphen if it can be parsed.

  • All nonalphanumeric characters will be replaced with a dot, all repeating dots are replaced by a single dot, and all leading/trailing dots are removed.

Table 21-2 shows some examples of module names.

Table 21-2. Module name examples
JAR name Module name Version

forex-calc.jar

forex.calc

None

forex-calc-0.1.0.jar

forex.calc

0.1.0

forex-0.1.0.jar

forex

0.1.0

Unnamed Modules

Classes loaded from the classpath, as opposed to the module path, are loaded as an unnamed module. Classes in the unnamed module are not visible to classes on the module path.

Accessibility

Modules add a new layer of encapsulation to Java. With modules, it is possible to restrict access to public types and selectively declare which modules can access a package. A public class is no longer globally public. A public class can be limited to just its module, or it can be exported and publically accessible to other modules.

Compiling Modules

The javac compiler command has been extended with additional parameters for handling modules. Multiple modules may be compiled simultaneously. For multiple modules, each module’s content should be placed in a directory with the same name of the module. An example of this is shown in Figure 21-1.

jpg4 2101
Figure 21-1. Multiple-module layout example

The example in Figure 21-1 would be compiled with the following command line on a Unix system:

javac -d out --module-source-path src $(find .
  -name "*.java")

Other module command-line options:

--add-modules <module>(,<module>)*

Root modules to resolve in addition to the initial modules, or all modules on the module path if <module> is ALL-MODULE-PATH

--limit-modules <module>(,<module>)*

Limit the universe of observable modules

--module <module-name>, -m <module-name>

Compile only the specified module, check timestamps

--module-path <path>, -p <path>

Specify where to find application modules

--module-source-path <module-source-path>

Specify where to find input source files for multiple mod⁠ules

--module-version <version>

Specify version of modules that are being compiled

--upgrade-module-path <path>

Override location of upgradeable modules

Modular JDK

As a part of Project Jigsaw (JEP 200), the JDK itself was heavily refactored and modularized. The module java.base is present by default, meaning you do not need to explicitly require it. However, if you are using JavaFX, JDBC, etc., you will need to include the relevant modules in addition to adding the relevant imports to your code. See Table 21-3 for a list of modules.

The Java compiler uses the java.se.ee module, whereas at runtime Java uses the java.se module. The functionality contained in java.se.ee is typically provided by a Java EE container.

Table 21-3. Module summary
Module Requires

java.activation

java.base, java.datatransfer, java.logging

java.base

java.compiler

java.base

java.cobra

java.base, java.desktop, java.logging, java.naming, java.rmi, java.transaction, jdk.unsupported
+ 4 transitive dependencies

java.datatransfer

java.base

java.desktop

java.base, java.datatransfer, java.prefs, java.xml

java.instrument

java.base

java.logging

java.base

java.management

java.base

java.management.rmi

java.base, java.management, java.naming, java.rmi
+ 2 transitive dependencies

java.naming

java.base, java.security.sasl
+ 1 transitive dependency

java.prefs

java.base, java.xml

java.rmi

java.base, java.logging

java.scripting

java.base

java.se

java.base, java.compiler, java.datatransfer, java.desktop, java.instrument, java.logging, java.management, java.management.rmi, java.naming, java.prefs, java.rmi, java.scripting, java.security.jgss, java.security.sasl, java.sql, java.sql.rowset, java.xml, java.xml.crypto

java.se.ee

java.activation, java.base, java.corba, java.se, java.transaction, java.xml.bind, java.xml.ws, java.xml.ws.annotation
+ 19 transitive dependencies

java.security.jgss

java.base, java.naming
+ 2 transitive dependencies

java.security.sasl

java.base, java.logging

java.smartcardio

java.base

java.sql

java.base, java.logging, java.xml

java.sql.rowset

java.base, java.logging, java.naming, java.sql
+ 2 transitive dependencies

java.transaction

java.base,java.rmi
+ 1 transitive dependency

java.xml

java.base

java.xml.bind

java.activation, java.base, java.compiler, java.desktop, java.logging, java.xml, jdk.unsupported
+ 2 transitive dependencies

java.xml.crypto

java.base, java.logging, java.xml

java.xml.ws

java.activation, java.base, java.desktop, java.logging, java.management, java.xml, java.xml.bind, java.xml.ws.annotation, jdk.httpserver, jdk.unsupported
+ 3 transitive dependencies

java.xml.ws.annotation

java.base

jdeps

To prepare for Project Jigsaw in Java 9, Oracle added the jdeps command-line tool in Java 8. This is a static dependency checker, which is meant to aid in preparation for Java 9. This tool has three primary uses:

  • Identify which JDK modules are required for a set of classes.

  • Trace transitive dependencies of a set of classes.

  • Identify dependencies on undocumented internal JDK classes.

The utility can generate the analysis on the console or dump it to a .dot file. Tools such as graphviz can use the .dot file to render the output graphically.

Identifying Dependencies

jdeps postgresql-42.1.1.jar

postgresql-42.1.1.jar -> /Library/Java/JavaVirtualMachines/jdk1.8.0_131.jdk/Contents/Home/jre/lib/jce.jar
postgresql-42.1.1.jar -> not found
postgresql-42.1.1.jar -> /Library/Java/JavaVirtualMachines/jdk1.8.0_131.jdk/Contents/Home/jre/lib/rt.jar
 org.postgresql (postgresql-42.1.1.jar)
 -> java.io
 -> java.lang
 -> java.net
 -> java.security
 -> java.sql
 -> java.util
 -> java.util.logging
 -> org.postgresql.copy postgresql-42.1.1.jar
 -> org.postgresql.fastpath postgresql-42.1.1.jar
 -> org.postgresql.jdbc postgresql-42.1.1.jar
 -> org.postgresql.largeobject postgresql-42.1.1.jar
 -> org.postgresql.replication  postgresql-42.1.1.jar
 -> org.postgresql.util postgresql-42.1.1.jar

Identifying Undocumented JDK Internal Dependencies

To identify dependencies on undocumented JDK classes, use –⁠jdkinternals. Undocumented JDK classes are those that begin with com.sun.* or sun.*. These were not meant to be used outside of the JDK and may be removed at any point. With Java 9, many of these APIs have been refactored or removed.

The following jdeps command invocation returns the dependencies for the MyEncoder jar:

jdeps -jdkinternals MyEncoder.jar

MyEncoder.jar -> /Library/Java/JavaVirtualMachines/jdk1.8.0_131.jdk/Contents/Home/jre/lib/rt.jar
 org.ctjava.util.TransmitUtil (MyEncoder.jar)
 -> sun.misc.BASE64Encoder JDK internal API (rt.jar)

Warning: JDK internal APIs are unsupported and private to JDK implementation that are
subject to be removed or changed incompatibly and could break your application.
Please modify your code to eliminate dependency on any JDK internal APIs.
For the most recent update on JDK internal API replacements, please check:
https://wiki.openjdk.java.net/display/JDK8/Java+Dependency+Analysis+Tool

JDK Internal API Suggested Replacement
---------------- ---------------------
sun.misc.BASE64Encoder Use java.util.Base64 @since 1.8

In this example, the class TransmitUtil depends upon an undocumented JDK class which isn’t supported in Java 9.

Defining a Module

To define a module, a module-info.java file must be created in the default package. The content of the file is as follows:

<open> module <module-name> {
	[export <java package> [to <module name>]
 	[requires [transitive] <module-name>]
 	[opens <module name> [to <module name]]
	[provides <interface> with <implementation>]
	[uses <interface>]
}

The module name must be unique and should use the reverse domain name pattern. The following example defines module with the name org.ctjava.admin, which doesn’t export any packages or depend upon any modules:

module org.ctjava.admin {
}

Exporting a Package

All public types in a package may be exported by adding an export statement to a module definition:

module org.ctjava.admin {
  exports org.ctjava.admin.api
}

In this example, all public classes in org.ctjava.admin.api are exported and available to other modules that depend upon org.ctjava.admin.

Packages can be selectively exported to a specific module, for example:

module org.ctjava.admin {
  exports org.ctjava.admin.ui to javafx.graphics;
}

In this example, org.ctjava.admin.ui is selectively exported to javafx.graphics. This gives classes in javafx.graphics access to classes within org.ctjava.admin.ui without this packaging having to declare a dependency in its module file. This is typically done when another package uses reflection. Without the export, classes in javafx.graphics would not be able to perform reflection on classes in org.ctjava.admin.ui.

Declaring Dependencies

To declare a dependency upon another package, add the requires statement to the module definition. The following example has three dependencies, which must be present when the module is loaded at runtime:

module org.ctjava.admin {
    requires javafx.controls;
    requires org.ctjava.services;
    requires org.ctjava.message.api;
}

Transitive Dependencies

If a module is exporting a package which uses classes from another module and which will be required by downstream dependencies, then the dependency must include the transitive keyword.

Consider the following module definition:

module org.ctjava.services {
    requires transitive org.ctjava.model;
    exports org.ctjava.services;
}

This definition will export the org.ctjava.services package, which contains the following class:

public interface MeetingService {
    void scheduleMeeting(Meeting meeting,
            Date scheduledDate);
    void updateMeeting(Meeting meeting);
    
    }
}

This class uses the type Meeting from the org.ctjava.model package. If org.ctjava.services didn’t include the transitive keyword on the requires for org.ctjava.model, then users who depend upon org.ctjava.services would not have access to the Meeting class and hence could not invoke/use the MeetingService class.

Defining Service Providers

Modules can be defined that export a service which can be dynamically added to the module path on startup. The Service Provider API was first added in Java 6 and has been modified for Java 9. With Service Providers, you have the following:

  • A module containing interfaces for a service

  • One or more modules containing the implementation of the service

  • A module that uses the service

Figure 21-1 shows an example service implementation. The modules are as follows:

org.ctjava.message.api::Contains an interface MessageService.java org.ctjava.email::Contains an implementation of MessageService.java org.ctjava.admin::Uses implementations of org.ctjava.message.api added to the module path.

jpg4 2102
Figure 21-2. Service Provider API example

Defining Service API

The module org.ctjava.message.api, contains MessageService.java defining the contract for the service:

module org.ctjava.message.api {
    exports org.ctjava.message.api;
}

The package org.ctjava.message.api contains an interface defining the service:

package org.ctjava.message.api;

public interface MessageService {
    void sendMessage(String memeber, String message);
}

Implementing Service API

Providing an implementation of a service is straightforward. The module file declares a dependency on service API and specifies that it provides an implementation of the interface. The new requires and provides keywords are needed in the relative statements to declare the dependency as shown here:

module org.ctjava.email {
    requires org.ctjava.message.api;
    provides org.ctjava.message.api.MessageService
            with org.ctjava.email.EmailMessageService;
}

The implementation of the service is straightforward:

package org.ctjava.email;

import org.ctjava.message.api.MessageService;

public class EmailMessageService implements MessageService {
    @Override
    public void sendMessage(String memeber, String message) {
        // send message
    }
}

Using Service Providers

To use a service, a dependency upon the API is declared, along with a uses statement specifying the interface of the service, as shown here:

module org.ctjava.admin {
    requires org.ctjava.message.api;
    uses org.ctjava.message.api.MessageService;
}

To use the service, use java.util.ServiceLoader to get a reference:

Iterable<MessageService> mservice = ServiceLoader.load(MessageService.class);
for(MessageService ms : mservice) {
  ms.sendMessage(member, "Hello World!");
}

jlink

The jlink tool assembles and optimizes a set of modules and their dependencies into a custom runtime image. This utility is defined in JEP 282. Only required modules are included in the image. For example, a desktop application using JavaFX is only 95 MB versus 454 MB for the full JDK.

The command jlink has the following parameters:

jlink --module-path <modulepath> +
        --add-modules <modules> +
        --limit-modules <modules> +
        --output <path>

Here’s an example:

jlink --module-path $JAVA_HOME/jmods:dist --add-modules org.ctjava.TimerUtil --output test

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset