Skip to content
On this page

Module

Starting from Java 9, JDK introduced modules.

What is a module? This starts with versions before Java 9.

We know that the .class file is the smallest executable file seen by the JVM, and a large program needs to write many Classes and generate a bunch of .class files, which is very inconvenient to manage. Therefore, the jar file is the container of class file.

Before Java 9, a large Java program would generate its own jar file and reference dependent third-party jar files. The Java standard library that comes with the JVM is actually stored in the form of a jar file. This file is called rt.jar , a total of more than 60M.

If you develop your own program, in addition to your own app.jar , you also need a bunch of third-party jar packages to run a Java program. Generally speaking, the command line is written like this:

sh
java -cp app.jar:a.jar:b.jar:c.jar com.sample.Main

Notice

Do not write the standard library rt.jar that comes with the JVM into the classpath. Writing it will interfere with the normal operation of the JVM.

If a jar that needs to be used at runtime is omitted, ClassNotFoundException is very likely to be thrown during runtime.

Therefore, jar is just a container for storing classes, and it does not care about the dependencies between classes.

The modules introduced since Java 9 are mainly to solve the problem of "dependency". If a.jar must depend on another b.jar to run, then we should add some description to a.jar so that the program can automatically locate b.jar when compiling and running. This kind of built-in "dependency" The class container is the module.

In order to show Java's determination to modularize, starting from Java 9, the original Java standard library has been split from a single huge rt.jar into dozens of modules. These modules are identified with .jmod extension and can be found in $JAVA_HOME/jmods Find them in the $JAVA_HOME/jmods directory:

  • java.base.jmod
  • java.compiler.jmod
  • java.datatransfer.jmod
  • java.desktop.jmod
  • ...

Each of these .jmod files is a module, and the module name is the file name. For example: the file corresponding to the module java.base is java.base.jmod .

The dependencies between modules have been written to the module-info.class file within the module. All modules directly or indirectly depend on java.base module. Only java.base module does not depend on any module. It can be regarded as the "root module", just like all classes inherit directly or indirectly from Object .

Encapsulating a bunch of classes into a jar is just a packaging process, while encapsulating a bunch of classes into a module requires not only packaging, but also dependencies, and can also include binary code (usually JNI extensions). In addition, the module supports multiple versions, that is, different versions can be provided for different JVMs in the same module.

Writing modules

So, how should we write modules? Let’s take a specific example. First of all, creating a module is exactly the same as creating a Java project. Taking the oop-module project as an example, its directory structure is as follows:

oop-module
├── bin
├── build.sh
└── src
    ├── com
    │   └── itranswarp
    │       └── sample
    │           ├── Greeting.java
    │           └── Main.java
    └── module-info.java

Among them, the bin directory stores the compiled class files, and src directory stores the source code, which is stored in the directory structure of the package name. There is only one more module-info.java file in the src directory, which is the module description file. In this module, it looks like this:

sh
module hello.world {
	requires java.base; // No need to write, any module will automatically import java.base
	requires java.xml;
}

Among them, module is the keyword, and the following hello.world is the name of the module. Its naming convention is consistent with the package. The curly braces requires xxx; indicates the names of other modules that this module needs to reference. In addition to java.base being automatically introduced, here we introduce a java.xml module.

When we declare dependencies using modules, we can use the imported modules. For example, the Main.java code is as follows:

java
package com.itranswarp.sample;

// The java.xml module must be introduced before the classes in it can be used:
import javax.xml.XMLConstants;

public class Main {
	public static void main(String[] args) {
		Greeting g = new Greeting();
		System.out.println(g.hello(XMLConstants.XML_NS_PREFIX));
	}
}

If requires java.xml; is removed from module-info.java , the compilation will report an error. It can be seen that the important role of modules is to declare dependencies.

Next, we use the command line tools provided by JDK to compile and create modules.

First, we switch the working directory to oop-module , compile all .java files in the current directory, and store them in the bin directory. The command is as follows:

sh
$ javac -d bin src/module-info.java src/com/itranswarp/sample/*.java

If the compilation is successful, the project structure is now as follows:

oop-module
├── bin
│   ├── com
│   │   └── itranswarp
│   │       └── sample
│   │           ├── Greeting.class
│   │           └── Main.class
│   └── module-info.class
└── src
    ├── com
    │   └── itranswarp
    │       └── sample
    │           ├── Greeting.java
    │           └── Main.java
    └── module-info.java

Notice that module-info.java in src directory is compiled into module-info.class in the bin directory.

Next, we need to package all the class files in the bin directory into a jar. When packaging, pay attention to passing in --main-class parameter so that the jar package can locate the class where main method is located:

sh
$ jar --create --file hello.jar --main-class com.itranswarp.sample.Main -C bin .

Now we have the jar package hello.jar in the current directory. It is no different from an ordinary jar package. You can directly use the command java -jar hello.jar to run it. But our goal is to create a module, so continue to use the jmod command that comes with the JDK to convert a jar package into a module:

sh
$ jmod create --class-path hello.jar hello.jmod

So, we got the module file hello.jmod in the current directory. This is the legendary module that was finally packaged!

Run module

To run a jar, we use the java -jar xxx.jar command. To run a module, we only need to specify the module name. Try:

sh
$ java --module-path hello.jmod --module hello.world

The result is an error:

sh
Error occurred during initialization of boot layer
java.lang.module.FindException: JMOD format not supported at execution time: hello.jmod

The reason is that .jmod cannot be put into --module-path . It will be no problem if you change it to .jar :

sh
$ java --module-path hello.jar --module hello.world
Hello, xml!

So what is the use of hello.jmod we worked so hard to create? The answer is we can use it to package JRE.

Package JRE

As mentioned earlier, in order to support modularization, Java 9 first took the lead in splitting its huge rt.jar into dozens of .jmod modules. The reason is that when running Java programs, the JDK modules we actually use , not that much. Unnecessary modules can be deleted completely.

In the past, when a Java application was released, to run it, one had to download a complete JRE and then run the jar package. The complete JRE is very large, more than 100M. How to slim down JRE?

Now, JRE's own standard library has been split into modules. You only need to bring the modules used by the program, and other modules can be cut out. How to tailor JRE? It does not mean deleting some modules from the JRE installed on the system, but "copying" a JRE, but only bringing the modules used. For this purpose, JDK provides the jlink command to do this. The command is as follows:

sh
$ jlink --module-path hello.jmod --add-modules java.base,java.xml,hello.world --output jre/

We specified our own module hello.jmod in --module-path parameter, and then specified the three modules we used java.base , java.xml and hello.world , in the --add-modules parameter. Use , separate. Finally, specify the output directory in the --output parameter.

Now, in the current directory, we can find the jre directory, which is a complete JRE with our own hello.jmod module. Try running this JRE directly:

sh
$ jre/bin/java --module hello.world
Hello, xml!

To distribute our own Java applications, we only need to package this jre directory and send it to the other party, and the other party can directly run the above command. There is no need to download and install the JDK, nor do we need to know how to configure our own modules, which is extremely convenient. distribution and deployment.

Access rights

As we mentioned before, Java's class access rights are divided into public, protected, private and default package access rights. After the module is introduced, these access rights rules need to be slightly adjusted.

To be precise, these access rights of class are only valid within a module and between modules. For example, if module a wants to access a certain class of module b, the necessary condition is that module b explicitly exports an accessible package.

For example: the module hello.world we wrote uses a class javax.xml.XMLConstants of the module java.xml . The reason why we can use this class directly is because it is declared in module-info.java of the module java.xml Several exports:

java
module java.xml {
    exports java.xml;
    exports javax.xml.catalog;
    exports javax.xml.datatype;
    ...
}

External code is allowed to access only the exported packages it declares. In other words, if external code wants to access hello.world com.itranswarp.sample.Greeting class, we have to export it:

java
module hello.world {
    exports com.itranswarp.sample;

    requires java.base;
	requires java.xml;
}

Therefore, modules further isolate access to your code.

Summary

The purpose of the modules introduced in Java 9 is to manage dependencies;

Use modules to package JRE on demand;

Access to classes is further restricted using modules.

Module has loaded