Advertisement

Java 9 Recipes pp 605-614 | Cite as

Java Modularity

  • Josh Juneau
Chapter

Abstract

One of the most important new features of Java 9 is the modular system, which came to fruition via Project Jigsaw. Project Jigsaw may also be referred to as JSR 376: The Java Platform Module System. The purpose of the project was to construct a system that provided reliable configuration which would replace the classpath system. It also focused on providing strong encapsulation between different modules. The module system is composed of all modules that constitute the Java Platform, as the platform was reconstructed from the ground up and modularized as part of this project. Application developers and library creators can also create modules…whether they be single modules that perform a specific task, or a number of modules that together create an application.

Keywords

Module System Service Consumer Loose Coupling Module Descriptor Public Class 
These keywords were added by machine and not by the authors. This process is experimental and the keywords may be updated as the learning algorithm improves.

One of the most important new features of Java 9 is the modular system, which came to fruition via Project Jigsaw. Project Jigsaw may also be referred to as JSR 376: The Java Platform Module System. The purpose of the project was to construct a system that provided reliable configuration which would replace the classpath system. It also focused on providing strong encapsulation between different modules. The module system is composed of all modules that constitute the Java Platform, as the platform was reconstructed from the ground up and modularized as part of this project. Application developers and library creators can also create modules…whether they be single modules that perform a specific task, or a number of modules that together create an application.

In this chapter, the basic fundamentals for development and management of modules will be touched upon. Although Java Modularity is a very large topic, this chapter is terse, providing enough information to get started with module development quickly. I recommend reading more in-depth books and documentation for those interested in learning more details about Java Modularity.

22-1. Constructing a Module

Problem

You wish to create a simple module that will print a message to the command line or via a logger.

Solution

Develop a module so that it can be executed via the java executable. Begin by creating a new directory somewhere on your file system…in this case name it “recipe22-1.” Create a new file named module-info.java, which is the module descriptor. In this file, list the module name as follows:

module org.firstModule {}

Next, create a folder named org within the recipe22-1 directory that was created previously. Next, create a folder named firstModule within the org folder. Now, create the bulk of the module by adding a new file named Main.java inside of the org.firstModule folder. Place the following code within the Main.java file:

package org.firstModule;
public class Main {
    public static void main(String[] args) {
        System.out.println("This is my first module");
    }
}

How It Works

The easiest modules can be built with two files, those being the module descriptor and a single Java class file that contains the business logic. The solution to this example follows this pattern to create a very basic module that performs a single task of printing a phrase to the command line. The module is packaged inside of a directory that is entitled the same as the module name. In the example, this directory is named org.firstModule, as it follows the standard module naming convention. In reality, a module can be named anything, so long as it does not conflict with other module names. However, it is recommended to utilize the inverse-domain-name pattern of packages. This causes the module name to become prefixed with its containing package names.

In this solution, the module descriptor contains the module name, followed by opening and closing braces. In a more complex module, the names of other module dependencies can be placed within the braces, along with the names of packages that this module exports for others to use. The module descriptor should be located at the root of the module directory. Inclusion of this file indicates to the JVM that this is a module. This directory can be made into a JAR file as I will discuss later in the chapter, and this creates a Modular JAR.

The other file that must be created to develop a simple module is the Java class file containing the business logic. This file should be placed inside of the org/firstModule directory, and the package should indicate org.firstModule. In this solution, the Main method will be invoked when the module is executed. Note that any dependencies that the module would require must be listed within the module descriptor. In this simple module, there are no dependencies. After setting up this directory structure and placing these two files into their respective locations, the module development is complete.

22-2. Compiling and Executing a Module

Problem

You’ve developed a basic module. Now you would like to compile the module and execute it.

Solution

Make use of the javac utility to compile the module, specifying the d flag to list the folder into which the compiled code will be placed. After the d option, each of the source files to be compiled must be listed, including the module-info.java descriptor. Separate each of the file paths with a space. The following command compiles the sources that were developed in Recipe 22-1 and places the result into a directory named mods/org.firstModule.

javac d src/mods/org.firstModule src/org.firstModule/module-info.java src/org.firstModule/org/firstModule/Main.java

Now that the code has been compiled, it is time to execute the module. This can be done with the standard java executable. However, the --module-path option, which is new in Java 9, must be used to indicate the path of the module sources. The -m option is used to specify the Main class of the module.

java --module-path mods -m org.firstModule/org.firstModule.Main  

The output from executing the module should be as follows:

This is my first module

If there were more than one module that was going to be compiled, they could be compiled separately using a similar technique to the one described previously, or they could be compiled all at once. The syntax for compiling two modules that contain a dependency is as follows:

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

How It Works

As you know, before a Java application can be executed, it must be compiled. Modules are the same way in that they must be compiled before they can be used. The standard javac utility has been enhanced so that it can accommodate the compilation of modules by simply listing out the fully qualified paths to the module-info.java file and each subsequent .java file contained within the module. The d option is used to specify the destination for the compiled sources. In the solution, the javac utility is invoked and the destination is set the location src/mods/org.firstModule. Each of the .java files that constitute the module are listed afterward, separated by a space. If a particular module included many .java source files, then simply specifying an asterisk (*) wildcard in the path after each package, rather than the individual file names, would suffice to compile each .java file contained within the specified package(s).

javac -d mods/src/org.firstModule src/org.firstModule/module-info.java src/org.firstModule/org/firstModule/*

The same java executable that is used to execute most Java applications can be used to execute a module. With the help of some new options, the java executable is able to execute a module with all of the required dependencies. The --module-path option specifies the path to where the compiled module resides. If there are a number of modules that comprise an application, specify the path to the module that contains the application entry point. The -m option is used to specify the path application entry point class, as well as its fully qualified name. In the solution, the main class resides within a directory named org.firstModule, and within a package named org.firstModule.

22-3. Creating a Module Dependency

Problem

You wish to develop a module that depends upon and utilizes another module.

Solution

Develop at least two modules, where one of the modules depends upon the other. Then specify the dependency within the module descriptor. The module that was developed in the previous recipes will be used in this solution as well, but it will be altered a bit to make use of another module named org.secondModule. This second module will accept a number and then calculate a room rate.

To start, create the module org.secondModule by creating a new directory within the src directory. Next, create a .java file named module-info.java and place it into that location. The contents of the module descriptor should look as follows:

module org.secondModule {
    exports org.secondModule;
}

The module will be making sources contained within the org.secondModule package available to other modules that require it. The sources for the module should be placed into a class named Calculator.java, and this file should be placed into the src/org.secondModule/org/secondModule directory. Copy the following code into Calculator.java:

package org.secondModule;
import java.math.BigDecimal;
public class Calculator {
    public static BigDecimal calculateRate(BigDecimal days, BigDecimal rate) {
        return days.multiply(rate);
    }
}

The code that was originally used for org.firstModule (Recipes 22-1, and 22.2) should be modified to make use of org.secondModule as follows:

package org.firstModule;
import org.secondModule.Calculator;
import java.math.BigDecimal;
public class Main {
    public static void main(String[] args) {
        System.out.println("This is my first module.");
        System.out.println("The hotel stay will cost " + Calculator.calculateRate(
             BigDecimal.TEN, new BigDecimal(22.95)
        ));
    }
}

The module descriptor for org.firstModule must be modified to require the dependency:

module org.firstModule {
    requires org.secondModule;
}

To compile the modules, specify the javac command, using a wildcard to compile all code within the src directory:

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

Lastly, to execute org.firstModule along with its dependency, use the same syntax that was used previously to execute the module. The module system takes care of gathering the required dependencies.

How It Works

A module can contain zero or many dependencies. The readability of a module depends upon what has been exported in the module descriptor of that module. Likewise, a module must require another module in order to read from it. The module system practices strong encapsulation. A module always is readable to itself, but other modules can only make use of those packages that are exported from the module. Furthermore, only public methods and so on are available for use by other modules.

To make one module dependent upon another, a required declaration must be placed in the module descriptor, specifying the name of the module on which it is dependent. In the solution, org.firstModule is dependent upon org.secondModule since the module descriptor declares it. This means that org.firstModule is able to utilize any public features residing within the org.secondModule package of the org.secondModule module. If there were more packages contained within org.secondModule, then they would not be available to org.firstModule since they have not been exported within the module descriptor for org.secondModule.

Utilization of the module descriptor for Java 9 modules trumps the classpath, as it is a much more robust means of declaring dependencies. However, if a Java 9 module were packaged as a JAR (see Recipe 22-4), it can be used on older versions of Java by placing the JAR into the classpath, and the module descriptor will be ignored.

Modules can be compiled separately using the javac command as demonstrated in Recipe 22-2, or they can be compiled using the wildcard notation, as also seen in Recipe 22-2 and in this recipe solution. Execution of the module is the same, whether it depends upon zero or more other modules.

22-4. Packaging a Module

Problem

Your module has been developed and you wish to package it to make it portable.

Solution

Utilize the enhanced jar utility to package modules and also to make executable modules. To package the module that was developed in Recipe 22-2, navigate to the directory which contains the mods and src directories. From within that directory, execute the following commands via the command line:

mkdir lib
jar --create --file=lib/org.firstModule@1.0.jar --module-version=1.0 --main-class=org.firstModule.Main -C mods/org.firstModule .

This utility will package the module into a JAR file within the lib directory. The JAR file can then be executed with the java executable as follows:

java -p lib -m org.firstModule

How It Works

The jar utility has been enhanced for Java 9 to include a number of new options, including a few that make module packaging easier. Table 22-1 lists the options of the jar utility.
Table 22-1.

jar Utility Options

Option

Description

-c, --create

Create an archive

-I, --generate-index=FILE

Generate index information for specified jar files

-t, --list

List an archive’s table of contents

-u, --update

Update an existing jar file

-x, --extract

Extract one or more files from a jar file

-C DIR

Change to the directory that is specified and include file

-f, --file=FILE

Name of the jar file

-v, --verbose

Generate verbose output

-e, --main-class=NAME

The main class or entry point for a module that will be packaged into the jar

-m, --manifest=FILE

Include specified manifest file information with the jar

-M, --no-manifest

Omit manifest

--module-version=VERSION

Module version

--hash-modules=PATTERN

Compute and record hashes of modules matched by the specified pattern

-P, --module-path

Location of module dependency for generation of hash

-0, --no-compress

Specifies that no ZIP compression shall be used

Looking at the table, there are a couple of options that are important for working with modules. Specifically, as seen in the example, the --module-version option allows a version to be specified. The other module-specific option is --module-path, which specifies the location of module dependence for generating a hash.

New options aside, creation of a JAR file using modules is not too much different than standard JAR file generation. Perhaps the most difficult part is ensuring that you are in the correct directory when initiating the command. As seen in the solution, simply specify the main class that will be executed when the JAR is invoked by using the --main-class or -e option. After that, perform a -C directory change inside of the module root, and then end the command with a “.” to indicate the current directory.

Once a JAR file is created, the module will become portable, which means that it can be used on other systems.

22-5. Listing Dependencies or Determining JDK-Internal API Use

Problem

You would like to determine whether an existing application relies upon any of the inaccessible internal JDK APIs with Java 9.

Solution

Use the jdeps tool to list module dependencies from the command line. To see the list of dependencies for a given module, specify the --list-deps option as follows:

jdeps --list-deps <<your-jar.jar>>

Invoking this command will initiate output that includes each of the packages that the specified JAR file depends upon. For example, choosing a random JAR file from the GlassFish application server modules directory would produce something similar to the following:

jdeps --list-deps acc-config.jar
   java.base
   java.xml.bind
   unnamed module: acc-config.jar

There are also applications that may make use of JDK-Internal APIs, which are now inaccessible to standard applications starting with Java 9. The jdeps tool can list such dependencies, making it possible to determine whether an application will run on Java 9 without issue. To utilize this functionality, specify the -jdkinternals option as follows:

jdeps –jdkinternals <<your-jar.jar>>

Invoking the jdeps utility to review a JAR that contains dependencies upon JDK-Internal APIs will produce output such as the following:

jdeps -jdkinternals security.jar
security.jar -> java.base
   com.sun.enterprise.common.iiop.security.GSSUPName  -> sun.security.util.ObjectIdentifier                    JDK internal API (java.base)
   com.sun.enterprise.common.iiop.security.GSSUtilsContract -> sun.security.util.ObjectIdentifier                    JDK internal API (java.base)
   com.sun.enterprise.security.auth.login.LoginContextDriver -> sun.security.x509.X500Name                            JDK internal API (java.base)
   com.sun.enterprise.security.auth.login.LoginContextDriver$4 -> sun.security.x509.X500Name                            JDK internal API (java.base)
   com.sun.enterprise.security.auth.realm.certificate.CertificateRealm -> sun.security.x509.X500Name                            JDK internal API (java.base)
   com.sun.enterprise.security.auth.realm.ldap.LDAPRealm -> sun.security.x509.X500Name                            JDK internal API (java.base)
   com.sun.enterprise.security.ssl.JarSigner          -> sun.security.pkcs.ContentInfo                         JDK internal API (java.base)
   com.sun.enterprise.security.ssl.JarSigner          -> sun.security.pkcs.PKCS7                               JDK internal API (java.base)
   com.sun.enterprise.security.ssl.JarSigner          -> sun.security.pkcs.SignerInfo                          JDK internal API (java.base)
   com.sun.enterprise.security.ssl.JarSigner          -> sun.security.x509.AlgorithmId                         JDK internal API (java.base)
   com.sun.enterprise.security.ssl.JarSigner          -> sun.security.x509.X500Name                            JDK internal API (java.base)
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 dependence 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.security.x509.X500Name               Use javax.security.auth.x500.X500Principal @since 1.4

How It Works

The jdeps (Java Dependency Analysis) tool was introduced in Java 8, and it is a command-line tool that is useful for listing static dependencies of JAR files.

Java 9 encapsulates many of the internal JDK APIs, making them inaccessible to standard applications. Prior to Java 9, there were circumstances that required applications to make use of such internal APIs. Those applications will not run as expected on Java 9, so it is imperative such dependencies are found and resolved before attempting to run older code on Java 9. The jdeps tool can be very useful for finding whether a JAR depends upon these internal APIs by listing out the dependencies if they exist. If you wish to list the output in the .dot file format, specify the -dotoutput option along with -jdkinternals, as follows:

jdeps -dotoutput /java_dev/security-dependencies.dot  -jdkinternals security.jar

The jdeps tool can also be helpful for determining JAR dependencies, in general. The tool contains a --list-deps option to do just that. Simply put, the --list-deps option lists each of the modules a specified JAR depends upon.

22-6. Providing Loose Coupling Between Modules

Problem

You would like to provide loose coupling between modules, such that one module may call upon another module as a service.

Solution

Make use of the service architecture that has been built into the Java 9 modularity system. A service consumer can specify loose coupling by specifying a “uses” clause in the module descriptor to indicate that the module makes use of a particular service. The following example could be used for a module that may have the task of providing a web service discovery API. In the example, the org.java9recipes.serviceDiscovery module both requires and exports modules. It also then specifies that it uses the org.java9recipes.spi.ServiceRegistry service.

module org.java9recipes.serviceDiscovery {
    requires public.java.logging;
    exports org.java9recipes.serviceDiscovery;
    uses org.java9recipes.spi.ServiceRegistry;
}

Similarly, a service provider must specify that it is providing an implementation of a particular service. One can do so by including a “provide” clause within the module descriptor. In this example, the following module descriptor indicates that the service provider module provides the org.java9recipes.spi.ServiceRegistry with the implementation of org.dataregistry.DatabaseRegistry.

module org.dataregistry {
    requires org.java9recipes.serviceDiscovery;
    provides org.java9recipes.spi.ServiceRegistry
        with org.dataregistry.DatbaseRegistry;
}

The corresponding modules can now be compiled and used, and they will enforce loose coupling.

How It Works

The concept of module services allows for loose coupling to be had between two or more modules. A module that makes use of a provided service is known as a service consumer, whereas a module that provides a service is known as a service provider. Service consumers do not use any of a service provider’s implementation classes, rather, they utilize interfaces. For the loose coupling to work, the module system must be able to easily identify any uses of previously resolved modules, and on the contrary, search for service providers through a set of observable modules. To make the identification of the use of services easy, we specify the “uses” clause in a module descriptor to indicate that a module will make use of a provided service. On the flip side, a service provider can easily be found by the module system as we specify the “provides” clause within the module descriptor of a service provider.

Utilizing the module service API, it is very easy for the compiler and runtime to see which modules make use of services, and also which modules provide. This enforces even stronger decoupling, as the compiler along with linking tools can ensure that providers are appropriately compiled and linked to such services.

22-7. Linking Modules

Problem

You wish to link a set of modules in an effort to create a modular runtime image.

Solution

Make use of the jlink tool to link said set of modules, along with their transitive dependencies. In the following excerpt, a runtime image is created from the module that was created in Recipe 22-1.

jlink --module-path $JAVA_HOME/jmods:mods --add-modules org.firstModule --output firstmoduleapp

How It Works

Sometimes it is handy to generate a runtime image of modules to make for easier transportation. The jlink tool provides this functionality, amongst others. In the solution, a runtime image named firstmoduleapp is created from the module named org.firstModule. The --module-path option first indicates the path to the JVM jmods directory, followed by any directories that contain modules to be incorporated in the runtime image. The --add-modules option is used to specify the names of each module that should be included in the image.

The jlink tool contains a bevy of options, as indicated in Table 22-2.
Table 22-2.

jlink Options

Option

Description

--add-modules

Named modules to be resolved.

-c, --compress=<0|1|2>

Enables compression or resources.

--disable-plugin <name>

Disables named plugin.

--endian <little|big>

Specifies byte order of generated image.

--ignore-signing-information

Suppress fatal error when linked image will contain modular JARs which are signed. Signature-related files of signed modular jars will not be included.

--limit-modules

Limit the amount of observable modules.

--list-plugins

List available plugins.

-p,--module-path

Module path.

--no-header-files

Exclude header files from path.

--no-man-pages

Exclude man pages from path.

--output <path>

Output location.

--plugin-module-path

Custom plugin module path.

--save-opts <filename>

Save jlink options in specified file.

-G,--strip-debug

Strip debug information.

--version

Version information.

@<filename>

Read options from specified file.

Summary

This chapter provided a brief summary of the Java 9 Module System. In this chapter, you learned how to define a module, compile, and execute it. You also learned how to package a module and how to create modular dependencies. You learned about a couple of useful tools for working with modules for the purposes of listing dependencies, uses of JDK Internal APIs, and linking modules. Lastly, this chapter demonstrated how to create loose coupling via the use of module services.

Copyright information

© Josh Juneau 2017

Authors and Affiliations

  • Josh Juneau
    • 1
  1. 1.HinckleyUSA

Personalised recommendations