Appearance
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.