In this tutorial, we will understand what Automatic Modules are and how they can help us to use a non-modular third party library jar in our modular application.
What are Automatic Modules?
Automatic Modules are named modules which are automatically created for a non-modular jar. It happens when the jar is placed on the 'module path' (as dependency) of a modular application.
In other words; non modular jar files become modular (automatic module) when used by a modular application.
Let's say we have a modular application 'app' (has the module-info.class) which wants to use a third party non-modular jar 'lib.jar' (does not have the module-info.class). If we run a main class of 'app' say com.app.Main.class using following command:
java --module-path appClasses;lib --module app/com.app.Main
then lib.jar (placed under lib folder) automatically becomes modular for 'app'. This module has the same name as the jar name (extension is dropped, hyphens '-', if any, are replaced by dots '.' and version part, if any, is also dropped). The module 'app' has to still explicitly import the jar module by its name using 'requires' clause in module-info.java.
In above command, appClasses is the folder where 'app' has its complied classes and lib is the folder where we have the third party lib.jar.
Automatic Modules are another migration tool which helps us to develop a modular application or modularize our existing applications without waiting for the third party libraries to become modularized.
What packages it 'exports'?
It 'exports' all its packages.
What modules it 'requires'?
It 'requires' all other modules on the module path.
Example
This example will create two application. The first application will represent a non-modular third party library and the second one will be a modular application which will be using the first library .
The non-modular library
easy-math/src/easy/math/BasicCalc.javapackage easy.math;
public class BasicCalc {
public static int sum(int a, int b) {
return a + b;
}
}
D:\automatic-module-example\easy-math>tree /F /A | \---src \---easy \---math BasicCalc.java
Compiling the class:
D:\automatic-module-example\easy-math>javac -d out src/easy/math/BasicCalc.java
Creating the jar:
D:\automatic-module-example>jar --create --file easy-math.jar -C easy-math/out .
The modular application
math.app/src/com/example/AppMain.javapackage com.example;
import easy.math.BasicCalc;
public class AppMain {
public static void main(String[] args) {
int sum = BasicCalc.sum(4, 7);
System.out.println("sum : " + sum);
}
}
math.app/src/module-info.javamodule math.app {
requires easy.math;
}
Note that above module declaration requiring the dependency jar module (automatic) by it's name which will transform from 'easy-math.jar' to 'easy.math'
D:\automatic-module-example\math.app>tree /F /A | \---src | module-info.java | \---com \---example AppMain.java
Moving easy-math.jar (from first project) to math.app application:
D:\automatic-module-example>mkdir math.app\lib
D:\automatic-module-example>move easy-math.jar math.app\lib\ 1 file(s) moved.
Now here's our directory structure
D:\automatic-module-example>tree /F /A | +---easy-math | | | +---out | | \---easy | | \---math | | BasicCalc.class | | | \---src | \---easy | \---math | BasicCalc.java | \---math.app | +---lib | easy-math.jar | \---src | module-info.java | \---com \---example AppMain.java
Compiling the application:
D:\automatic-module-example\math.app>javac --module-path lib -d out src/module-info.java src/com/example/AppMain.java
Running the application
D:\automatic-module-example\math.app>java --module-path out;lib --module math.app/com.example.AppMain sum : 11
Using jdeps
D:\automatic-module-example\math.app>jdeps --module-path lib;out -s --module math.app math.app -> easy.math math.app -> java.base
Using --describe-module
For the library jar:
D:\automatic-module-example\math.app>jar --file lib\easy-math.jar --describe-module No module descriptor found. Derived automatic module.
easy.math automatic requires java.base mandated contains easy.math
For the client application:
D:\automatic-module-example\math.app>java --module-path out;lib\easy-math.jar --describe-module math.app math.app file:///D:/automatic-module-example/math.app/out/ requires easy.math automatic requires java.base mandated contains com.example
Creating jar of the main application
Let's convert our main application to jar as well and try --describe-module on the jar:
D:\automatic-module-example\math.app>jar --create --file lib\math-app.jar -C out .
D:\automatic-module-example\math.app>jar --file lib\math-app.jar --describe-module math.app jar:file:///D:/automatic-module-example/math.app/lib/math-app.jar/!module-info.class requires easy.math requires java.base mandated contains com.example
Using Module API
Let's use new Module API to analyze the modules.
math.app/src/com/example/ModuleAnalysis.javapackage com.example;
import easy.math.BasicCalc;
public class ModuleAnalysis {
public static void main(String[] args) {
analyze(ModuleAnalysis.class);
System.out.println("------");
analyze(BasicCalc.class);
}
private static void analyze(Class<?> cls) {
System.out.println("class: " + cls);
Module module = cls.getModule();
System.out.println("Module: " + module);
System.out.println("Name: " + module.getName());
System.out.println("isNamed: " + module.isNamed());
System.out.println("Descriptor: " + module.getDescriptor());
System.out.println("isAutomatic: " + module.getDescriptor().isAutomatic());
}
}
We just need to compile above class without recompiling the rest:
D:\automatic-module-example\math.app>javac --module-path lib -d out src/com/example/ModuleAnalysis.java
D:\automatic-module-example\math.app>java --module-path out;lib --module math.app/com.example.ModuleAnalysis class: class com.example.ModuleAnalysis Module: module math.app Name: math.app isNamed: true Descriptor: module { name: math.app, [easy.math, mandated java.base (@9)] } isAutomatic: false ------ class: class easy.math.BasicCalc Module: module easy.math Name: easy.math isNamed: true Descriptor: module { name: easy.math, [mandated java.base] } isAutomatic: true
How automatic module name is derived?
From ModuleFinder doc:
A JAR file that does not have a module-info.class in its top-level directory defines an automatic module, as follows:
- If the JAR file has the attribute "Automatic-Module-Name" in its main manifest then its value is the module name. The module name is otherwise derived from the name of the JAR file.
- The version, and the module name when the attribute "Automatic-Module-Name" is not present, are derived from the file name of the JAR file as follows:
- The ".jar" suffix is removed.
- If the name matches the regular expression "-(\\d+(\\.|$))" then the module name will be derived from the subsequence preceding the hyphen of the first occurrence. The subsequence after the hyphen is parsed as a Version and ignored if it cannot be parsed as a Version.
- All non-alphanumeric characters ([^A-Za-z0-9]) in the module name are replaced with a dot ("."), all repeating dots are replaced with one dot, and all leading and trailing dots are removed.
- As an example, a JAR file named "foo-bar.jar" will derive a module name "foo.bar" and no version. A JAR file named "foo-bar-1.2.3-SNAPSHOT.jar" will derive a module name "foo.bar" and "1.2.3-SNAPSHOT" as the version.
Example ProjectDependencies and Technologies Used: |