(the code for this is available at http://www.selikoff.net/ocp11-1
(do a more in-depth review of this chapter, if there’s enough time)
Introducing Modules
The Java Platform Module System (JPMS) was introduced in Java 9 to group code at a higher level. The main purpose of a module is to provide groups of related packages to offer a particular set of functionality to developers.
It’s like a JAR file except a developer chooses which packages are accessible outside the module.
Exploring a Module
A module is a group of one or more packages plus a special file called module-info.java.
(insert image of module)
Each block is a module. The arrows between them represent dependencies where one module relies on code in another.
(insert image of module)
Inside them, there’re three packages with two classes each. There’s also a module-info.java. This class is required to be inside all modules.
Benefits of Modules
Better Access Control
Aside from the existing access control (private, public…), they allow us to restrict access to just some packages, or implement complex access logic.
They act as a fifth level of access control. They can expose packages within the modular JAR to specific other packages (internal packages).
Clearer Dependency Management
It’s common for libraries to depend on other libraries. Usually you need to find this by reading documentation or project files.
In a fully modular environment, each project specifies its dependencies in a module-info.java
file. If a dependency is missing, Java complains when launching the program.
Custom Java Builds
The JDK is way to big. The module system allows developers to specify what modules they actually need. This makes possible to create a smaller runtime image that’s customized to what the app needs and nothing more. Users can run that image without having Java installed at all.
The tool jlink
is used to create this runtime image.
Improved Performance
Since Java now knows which modules are required, it only needs to look at those at class loading time. This improves startup time for big programs and requires less memory to run.
Unique Package Enforcement
It prevents the scenario of the same package being in two JARs. A package is only allowed to be supplied by one module.
Creating and Running a Modular Program
(insert image of module)
Creating the Files
Create the following classes and packages.
Task.java
package zoo.animal.feeding;
public class Task {
public static void main(String[]args) {
sout("All fed!");
}
}
module-info.java
module zoo.animal.feeding {
}
Key differences between module-info.java
and a regular Java class
- This file must be in the root directory of your module. Regular Java classes should be in packages.
- This file must use the keyword
module
instead ofclass
. - Its name follows the naming rules for package names. It often includes periods in its name.
The file may be empty, but it won’t create a .class
.
Directory structure
- mods
- feeding
- module-info.java
- zoo
- animal
- feeding
- Task.java
The directory called mods
(short for module) is for storing the module artifacts.
Compiling Our First Module
Before we can run modular code, we need to compile it.
javac --module-path mods
-d feeding
feeding/zoo/animal/feeding/*.java
feeding/module-info.java
-d
specifies the directory to place the class files in.
The syntax --module-path
and -p
are equivalent.
Running Our First Module
java --module-path feeding
--module zoo.animal.feeding/zoo.animal.feeding.Task
The short version for --module
is -m
.
Packaging Out First Module
A module isn’t much use if we can run it only in the folder it was cerated in.
jar -cvf mods/zoo.animal.feeding.jar -C feeding/ .
Versioning
It’s possible to version your module using --module-version
,
Updating Our Example For Multiple Modules
Updating the Feeding Module
We need to declare the intent of our modules calling each other inside module-info
.
The exports
keyword is used to indicate that a module intends for those packages to be used by Java code outside the module. Without it, a module is only available to be run from the command line on its own.
module zoo.animal.feeding {
exports zoo.animal.feeding;
}
Recompiling and repackaging the module will update the module-info
inside our zoo.animals.feeding.jar
file.
javac --module-path mods
-d feeding
feeding/zoo/animal/feeding/*.java
feeding/module-info.java
jar -cvf mods/zoo.animal.feeding.jar -C feeding/ .
Creating a Care Module
Let’s create the zoo.animal.care
module. We’re going to have two packages. The zoo.animal.care.medical
package will have the classes and methods that are intended for use by other modules. The zoo.animal.care.details
package is only going ot be used by this module. It won’t be exported from the module.
(insert image of module)
package zoo.animal.care.details;
import zoo.animal.feeding.*;
public class HippoBirthday {
private Task task;
}
package zoo.animal.care.medical;
public class Diet { }
This time the module-info.java
specifies three things.
module zoo.animal.care {
exports zoo.animal.care.medical;
requires zoo.animal.feeding;
}
- It specifies the name of the module.
- It lists the package we are exporting so it can be used by other modules.
- It specifies that a module is needed.
We now compile and package the module
javac -p mods
-d care
care/zoo/animal/care/details/*.java
care/zoo/animal/care/medical/*.java
care/module-info.java
Note that when compiling a module, order matters! module-info
goes last.
Now that we have compiled code, it’s time to create a modular JAR.
jar -cvf mods/zoo.animal.care.jar -C care/ .
Creating the Talks Module
So far we’ve used only one exports and requires in a module. Now we’ll handle exporting multiple packages or requiring multiple modules.
module zoo.animals.talks {
// allow other modules to reference all three packages
exports zoo.animal.talks.content;
exports zoo.animal.talks.media;
exports zoo.aniaml.talks.schedule;
// specifies two modules that this module depends on
requires zoo.animal.feeding;
requires zoo.animal.care;
}
Then we have the six classes:
package zoo.animal.talks.content;
public class ElephantScript {}
package zoo.animal.talks.content;
public class SeaLionScript {}
package zoo.animal.talks.media;
public class Announcement {
public static void main(String[] args) {
sout("we will be having talks");
}
}
package zoo.animal.talks.media;
public class Signage {}
package zoo.animal.talks.schedule;
public class Weekday {}
We compile and build the module
javac -p mods
-d talks
talks/zoo/animal/talks/content/*.java
talks/zoo/animal/talks/media/*.java
talks/zoo/animal/talks/schedule/*.java
talks/module-info.java
jar -cvf mods/zoo.animal.talks.jar -C talks/
Creating the Staff Module
This is the module-info
module zoo.staff {
requires zoo.animal.feeding;
requires zoo.animal.care;
requires zoo.animal.talks;
}
We have a single class in this module
package zoo.staff;
public class Jobs {}
Compile & build
javac -p mods
-d staff
staff/zoo/staff/*.java
staff/module-info.java
jar -cvf mods/zoo.staff.jar -C staff/
Diving into the module-info File
exports
and requires
are “keywords”, meaning that they’re only keywords inside a module-info.java
file. In other files, like classes or interfaces, they’re free to be used. This kind of special “keyword” are called directives.
exports
exports packageName
exports a package to other modules.
It’s also possible to export a package to a specific module.
module zoo.animal.talks {
exports zoo.animal.talks.content to zoo.animal.staff;
exports zoo.animal.talks.media;
exports zoo.animal.talks.schedule;
requires zoo.animal.feeding;
requires zoo.animal.care;
}
From zoo.animal.staff
module nothing has changed. However, no other modules would be allowed to access that package.
Exported Types
Exporting a package means, that all public classes, interfaces and enums are exported. Any public and protected fields and methods in those files are visible.
requires transitive
(check again: book 1. Page 528)
requires moduleName
specifies that the current module depends on moduleName
.
There’s also requires transitive moduleName
, which means any module that requires
this module will also depend on moduleName
.
module zoo.animal.care {
exports zoo.animal.care.medical;
requires transitive zoo.animal.feeding;
}
Effects of requires transitive
(check again: book 1. Page 530)
Duplicate requires Statements
(!) One place the exam might try to trick you is mixing requires
and requires transitive
together. (!)
module bad.module {
requires zoo.animal.talks;
requires transitive zoo.animal.talks;
}
Thsi doesn’t compile, because Java doesn’t allow you to repeat the same module in a requires
clause. It’s redundant. Keep in mind that requires transitive
is like requires
with some extra behaviour.
provides, uses, and opens
provides
- specifies that a class provides an implementation of a service. It’s kind of a service as a fany interface. To use it you supply the API and class name that implements it.
provides zoo.staff.ZooApi with zoo.staff.ZooImpl
uses
- specifies that a module is relying on a service. To code it you supply the API you want to call.
uses zoo.staff.ZooApi
opens
- It specifies explicitly that developers are allowed to use reflection.
opens zoo.animal.talks.schedule;
opens zoo.animal.taslks.media to zoo.staff;
The first example allows any module using this one to use reflection.
The second one, gives that privilege only to the zoo.staff
package.
Discovering Modules
Since Java 9, the classes built into the JDK were modularized as well.
The java Command
It has three module-related options.
Describing a Module
Suppose you’re given the zoo.animal.feeding
module JAR file and want to know about its module structure.
You could unjar it and open the module-info
. This would show you that the moduel exports one package and doesn’t require any modules.
module zoo.animal.feeding {
exports zoo.animal.feeding;
}
There’s an easier way. The java
command now has an option to describe a module.
This 2 commands are equivalent
java -p mods
-d zoo.animal.feeding
java -p mods
--describe-module zoo.animal.feeding
Each prints information about the module such as
zoo.animal.feeding file:///aboslutePath/mods/zoo.animal.feeding.jar
exports zoo.animal.feeding
requires java.base mandated
The first line is the module we asked about.
The second one, starts information about the module.
The third one, we see mandated
as keyword. The java.base
module is special. It’s automatically added as a dependency to all modules.
Listing Available Modules
List all modules that are available.
java --list-modules
the output are 70 lines that looked like this
java.base@11.0.2
java.compiler@11.0.2
java.datatransfer@11.0.2
This is a listing of all the modules that come with Java and their version numbers. Here 11.0.2
is the Java version.
This command may be used with custom code.
java -p mods --list-modules
Showing Module Resolution
In case listing modules didn’t give you enough output, you can also use --show-module-resolution
. You can think of it as a way of debugging modules. It spits out a lot of output when the program starts up.
java --show-module-resolution
-p feeding
-m zoo.animal.feeding/zoo.animal.feeding.Task
The jar Command
It can describe a module, just like the java
command.
Both of this commands are equivalent.
jar -f mods/zoo.animal.feeding.jar -d
jar --file mods/zoo.animal.feeding.jar --describe-module
The jar
version includes the module-info
in the filename.
You just need to know that both commands can describe a module.
The jdeps Command
It gives you information about dependencies within a module. Unlike describing a module, it looks at the code in addition to the module-info
file. This tells you what dependencies are actually used rather than simply declared.
Both of these commands give the same output.
jdeps -s mods/zoo.animal.feeding.jar
jdeps -summary mods/zoo.animal.feeding.jar
(review this again more in depth. book 1 page 536)
The jmod Command
JMOD files are recommended only when you have native libraries or something can’t go inside a JAR file.
jmod
is only for working with the JMOD files.
The following table are JMOD’s modes or syntax
operation | description |
---|---|
create | creates a JMOD file |
extract | extracts all files frmo the JMOD. Works like unzipping. |
describe | prints the module details such as requires |
list | lists all files in the JMOD file |
hash | shows a long string that goes with the file |
Reviewing Command-Line Options
This table shows the command lines you should expect to encounter on the exam.
Description | Syntax |
---|---|
Compile nonmodular code | javac -cp classpath -d directory classesToCompile |
javac –class-path classpath -d directory classesToCompile | |
javac -classpath classpath -d directory classesToCompile | |
Run nonmodular code | java -cp classpath package.className |
java –classpath classpath package.className | |
java –class-path classpath package.className | |
Compile a module | javac -p moduleFOlderName -d directory classesToCompileIncludingModuleInfo |
javac –module-path moduleFOlderName -d directory classesToCompileIncludingModuleInfo | |
Run a module | java -p moduleFolderName -m moduleName/package.className |
java –module-path moduleFolderName –module moduleName/package.className | |
Describe a module | java -p moduleFolderName -d moduleName |
java –module-path moduleFolderName –describe-module moduleName | |
jar –file jarName –describe-module | |
jar -f jarName -d | |
List available modules | java –module-path moduleFolderName –list-modules |
java -p moduleFolderName –list-modules | |
java –list-modules | |
View dependencies | jdeps -summary –module-path moduleFolderName jarName |
jdeps -s –module-path moduleFolderName jarName | |
Show module resolution | java –show-module-resolution -p moduleFolderName -d moduelName |
java –show-module-resolution –module-path moduleFolderName –describe-module moduleName |
javac modifiers
|Modifier|Description| |:—:|:—:| |-cp classpath|location of JARs in a nonmodular program| |-classpath classpath|| |–class-path classpath|| |-d dir|directory to place generated class files| |-p path|location of JARs in a modular program| |–module-path path||
java modifiers
|Modifier|Description| |:—:|:—:| |-p path|location of JARs in a modular program| |–module-path path|| |-m name|module name to run| |–module name|| |-d|describes the details of a module| |–describe-module|| |–list-modules|list observable modules without running a program| |–show-module-resolution|shows modules when running a program|
jar modifiers
|Modifier|Description| |:—:|:—:| |-c| craete a new JAR file| |–create|| |-v|prints details when working with JAR files| |–verbose|| |-f|JAR filename| |–file|| |-C|Directory containing giles to be used to create the JAR| |-d|Describes the details of a module| |–describe-module||
jdeps modifiers
|Modifier|Description| |:—:|:—:| |–module-path path|Location of JARs in a modular program| |-s|summarizes output| |-summary||
Summary
The Java Platform Module System organizes code at a higher level than packages. Each module contains one or more packages and a module-info file. Advantages of the JPMS include better access control, clearer dependency management, custom runtime images, improved performance, and unique package enforcement.
The process of compiling and running modules uses the –module-path, also known as -p. Running a module uses the –module option, also known as -m. The class to run is specified in the format moduleName/className.
The module-info file supports a number of keywords. The exports keyword specifies that a package should be accessible outside the module. It can optionally restrict that export to a specific package. The requires keyword is used when a module depends on code in another module. Additionally, requires transitive can be used when all modules that require one module should always require another. The provides and uses keywords are used when sharing and consuming an API. Finally, the opens keyword is used for allowing access via reflection.
Both the java and jar commands can be used to describe the contents of a module. The java command can additionally list available modules and show module resolution. The jdeps command prints information about packages used in addition to module-level information. Finally, the jmod command is used when dealing with files that don’t meet the requirements for a JAR.
Exam Essentials
Identify benefits of the Java Platform Module System. Be able to identify benefits of the JPMS from a list such as access control, dependency management, custom runtime images, performance, and unique package enforcement. Also be able to differentiate benefits of the JPMS from benefits of Java as a whole. For example, garbage collection is not a benefit of the JPMS.
Use command-line syntax with modules. Use the command-line options for javac, java, and jar. In particular, understand the module (-m) and module path (-p) options.
Create basic module-info files. Place the module-info.java file in the root directory of the module. Know how to code using exports to expose a package and how to export to a specific module. Also, know how to code using requires and requires transitive to declare a dependency on a package or to share that dependency with any modules using the current module.
Identify advanced module-info keywords. The provides keyword is used when exposing an API. The uses keyword is for consuming an API. The opens keyword is for allowing the use of reflection.
Display information about modules. The java command can describe a module, list available modules, or show the module resolution. The jar command can describe a module similar to how the java command does. The jdeps command prints details about a module and packages. The jmod command provides various modes for working with JMOD files rather than JAR files.