classpath
Operating System (OS) variable
does not actually allow Java to compete with, for instance C++ (.h
files
on the compilation side and .obj
/.lib
files
on the runtime side).
java --version (⤳ java 18.0.1.1 2022-04-22 Etc.)
java --list-modules (⤳ java.base@18.0.1.1 Etc.)
module-info.java
.
In a project-based logic (Ant, Maven, Gradle…), the single mode is characterized by
a unique module-info.java
file at the root of the project structure
while the multiple mode supposes that the Java project is “big enough” to have multiple (internal) modules.
resources
directory under the root of the project structure.
echo $env:java_home
(⤳ C:\Program Files\Java\jdk-14.0.1
)%JAVA_HOME%\bin
to execution pathecho $env:path
javac --version
(⤳ javac 14.0.1
)(Get-Command javac).Path
(⤳ C:\Program Files\Java\jdk-14.0.1\bin\javac.exe
)--module-path
option
to access pre-compiled modules in JAR (Java ARchive) format.
--module-source-path
option is necessary for “pointing”
both the root of the project structure and all module-info.java
files.
+---module_library
| My_compile_time_annotation-1.0.jar
| My_run_time_annotation-1.0.jar
\---src
\---main
\---java
| module-info.java
+---com
| \---franckbarbier
| +---package_A
| | A.java
| +---package_B
| | B.java
| \---package_main
| Main.java
\---resources
Franck.jpg
PowerShell:
javac -d PRODUCTION_SOFTWARE --module-path module_library `
src/main/java/com/franckbarbier/package_A/A.java `
src/main/java/com/franckbarbier/package_B/B.java `
src/main/java/com/franckbarbier/package_main/Main.java `
src/main/java/module-info.java
module My_module {
exports com.franckbarbier.package_A; // Note that 'com.franckbarbier.package_B' is *NOT* pushed to the outside...
requires java.logging; // JVM required component...
// requires java.sql; // JVM *NOT* required component...
requires static My_compile_time_annotation_module; // Optional at run-time...
requires My_run_time_annotation_module;
}
package com.franckbarbier.package_A;
@com.franckbarbier.My_compile_time_annotation.Author(identity = "Franck Barbier", image_URL = "resources/Franck.jpg")
@com.franckbarbier.My_run_time_annotation.Security(level = com.franckbarbier.My_run_time_annotation.Security.Security_level.MEDIUM)
public final class A {
// 'protected' is for test since 'requires' exposes 'protected' features:
protected final static String _Feature = "Feature in class 'A'";
public final static String Property = "Property in class 'A'";
}
package com.franckbarbier.package_B;
public final class B {
public final static String Property = "Property in class 'B'";
}
package com.franckbarbier.package_main;
public class Main {
public static void main(String[] args) {
System.out.print(com.franckbarbier.package_A.A.Property + " - " +
com.franckbarbier.package_B.B.Property);
// 'requires java.logging;' in 'module-info.java':
java.util.logging.Logger.getLogger(java.util.logging.Logger.GLOBAL_LOGGER_NAME).finest(com.franckbarbier.package_A.A.Property + " - " + com.franckbarbier.package_B.B.Property);
}
}
module My_compile_time_annotation_module {
exports com.franckbarbier.My_compile_time_annotation;
}
package com.franckbarbier.My_compile_time_annotation;
@java.lang.annotation.Documented // For JavaDoc...
// Annotation with retention policy 'CLASS' is retained till compiling the code, and discarded during run-time:
@java.lang.annotation.Retention(value = java.lang.annotation.RetentionPolicy.CLASS)
@java.lang.annotation.Target(value = java.lang.annotation.ElementType.TYPE)
public @interface Author {
String identity() default "Franck Barbier";
String image_URL() default "Franck.jpg";
String json() default "{}";
}
├── Main_module
│ ├── com
│ │ └── franckbarbier
│ │ └── package_main
│ │ └── Main.java
│ ├── module-info.java
│ └── pom.xml
├── Module_A
│ ├── com
│ │ └── franckbarbier
│ │ └── package_1
│ │ └── A.java
│ ├── module-info.java
│ └── pom.xml
├── Module_B
│ ├── com
│ │ └── franckbarbier
│ │ └── package_2
│ │ └── B.java
│ ├── module-info.java
│ └── pom.xml
└── Multiple_module_mode
└── pom.xml
…
<artifactId>Multiple_module_mode</artifactId>
<groupId>com.FranckBarbier</groupId>
<packaging>pom</packaging>
<version>1.0</version>
<modules>
<module>./Module_B</module>
<module>./Module_A</module>
<module>./Main_module</module>
</modules>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.release>14</maven.compiler.release>
<!-- obsolete because of 'maven.compiler.release': <maven.compiler.source>14</maven.compiler.source> -->
<!-- obsolete because of 'maven.compiler.release': <maven.compiler.target>14</maven.compiler.target> -->
</properties>
…
module Module_B {
exports com.franckbarbier.package_2 to Module_A;
}
module Module_A {
exports com.franckbarbier.package_1;
requires Module_B;
}
module Main_module {
requires Module_A;
}
Simply speaking, if a module exposes functionality that returns stuff from another module
then the former (e.g.,
java.desktop
)
requires the latter (e.g.,
java.xml
); former's “reusers” (e.g.,
my_module
)
reuse the latter as well (transitivity). Transitivity is one-level only!
module java.desktop { // From the JVM core...
requires transitive java.datatransfer;
requires transitive java.xml;
}
“Implied readability”
(see also here…)
is a common warning. Archetype: if module B
requires module A
, B
should use A
's stuff in a black-box way. The alternative white-box way is when, for instance,
B
exposes a type, say T
, while this type is defined in A
.
module A { exports P1; }
module B { requires A; }
package P2; // In 'B' module...
public class C {
private P1.T _t;
// Warning: 'class P1.T in module A is not indirectly exported using requires transitive'
public void f(P1.T t) { _t = t; }
}
The compiler then asks for the non-implicit exposition of A
's stuff: module B { requires transitive A; }
.
+---Main_module
| | module-info.java
| \---com
| \---franckbarbier
| \---package_main
| Main.java
+---Module_A
| | module-info.java
| \---com
| \---franckbarbier
| \---package_1
| A.java
+---Module_B
| | module-info.java
| \---com
| \---franckbarbier
| \---package_2
| B.java
\---Module_library
javax.json-1.1.4.jar
# Third-party library *NOT* downloaded by means of Maven...
jar --file=C:\Users\Bab\Documents\Nextcloud\From_Java_9\Multiple_module_mode.Java\Module_library\javax.json-1.1.4.jar --describe-module
Display:
No module descriptor found. Derived automatic module.
java.json@1.1.4 automatic
requires java.base mandated
contains javax.json
contains javax.json.spi
contains javax.json.stream
contains org.glassfish.json
contains org.glassfish.json.api
module Module_B {
exports com.franckbarbier.package_2 to Module_A;
// Look inside file to get module-based status: 'jar --file=.\Multiple_module_mode.Java\Module_library\javax.json-1.1.4.jar --describe-module'
requires transitive java.json; // 'transitive' to let access in 'Module_A'...
// Warning: 'requires transitive' directive for an automatic module...
}
package com.franckbarbier.package_2;
public final class B {
public final static String Property = "'Property' value in class 'B'";
public final static javax.json.JsonObject Property_ = javax.json.Json.createObjectBuilder().add("Property_", "'Property_' value in class 'B'").build();
}
module Module_A {
exports com.franckbarbier.package_1;
requires Module_B;
}
package com.franckbarbier.package_1;
public final class A {
public final static String Property = com.franckbarbier.package_2.B.Property;
// Transitive access:
public final static String Property_ = com.franckbarbier.package_2.B.Property_.getJsonString("Property_").getString();
}
module Main_module {
requires Module_A;
}
package com.franckbarbier.package_main;
public class Main {
public static void main(String[] args) {
System.out.println(com.franckbarbier.package_1.A.Property);
System.out.println(com.franckbarbier.package_1.A.Property_);
}
}
PowerShell:
javac -d PRODUCTION_SOFTWARE --module-path Module_library `
--module-source-path . `
Module_A/com/franckbarbier/package_1/A.java Module_A/module-info.java `
Module_B/com/franckbarbier/package_2/B.java Module_B/module-info.java `
Main_module/com/franckbarbier/package_main/Main.java Main_module/module-info.java
PowerShell:
& ($env:JAVA_HOME + "\bin\java") -p Module_library`;PRODUCTION_SOFTWARE `
-m Main_module/com.franckbarbier.package_main.Main
Display:
'Property' value in class 'B'
'Property_' value in class 'B'
Command:
# Third-party library downloaded by means of Maven...
jar --file=C:\Users\Bab\.m2\repository\javax\json\javax.json-api\1.1.4\javax.json-api-1.1.4.jar
--describe-module
Display:
java.json jar:file:///C:/Users/Bab/.m2/repository/javax/json/javax.json-api/1.1.4/javax.json-api-1.1.4.jar/!module-info.class
exports javax.json
exports javax.json.spi
exports javax.json.stream
requires java.base mandated
uses javax.json.spi.JsonProvider
Migrate (using Maven or not) this legacy program to Java 9.
Note that the derby-10.14.2.0.jar
file (JavaDB driver)
as external library has no module declaration; it is not Java 9-compliant.
package com.franckbarbier.embedded_javadb;
public final class JDBC {
private java.sql.Connection _connection;
public JDBC() throws java.sql.SQLException {
// MAVEN dependency on 'derby-10.14.2.0.jar':
_connection = java.sql.DriverManager.getConnection("jdbc:derby:memory:JDBC_test;create=true");
java.sql.DatabaseMetaData dmd = _connection.getMetaData();
if (dmd.getSQLStateType() == java.sql.DatabaseMetaData.sqlStateSQL99)
System.out.print(dmd.getDatabaseProductName() + " " + dmd.getDatabaseProductVersion() + " is SQL99-compliant\n");
else System.out.print(dmd.getDatabaseProductName() + " " + dmd.getDatabaseProductVersion() + " isn't SQL99-compliant\n");
_connection.close();
}
…
derby-10.15.2.0.jar
, Java 9-compliant?jar --file=lib/derby-10.15.2.0.jar --describe-module | grep "module-info.class"
org.apache.derby.engine jar:file:///Users/franckbarbier/Desktop/JDBC_JavaDB_Embedded.Java/lib/derby-10.15.2.0.jar/!module-info.class
jar --file=lib/derby-10.15.2.0.jar --describe-module | grep "export" | grep -v "qualified"
exports org.apache.derby.agg
exports org.apache.derby.authentication
exports org.apache.derby.catalog
exports org.apache.derby.diag
exports org.apache.derby.iapi.db
exports org.apache.derby.iapi.services.io
exports org.apache.derby.iapi.services.loader
// Etc.
module JavaDB {}
<artifactId>Derby</artifactId>
<groupId>com.FranckBarbier</groupId>
<packaging>jar</packaging>
<version>1.0</version>
<dependencies>
<dependency>
<groupId>org.apache.derby</groupId>
<artifactId>derby</artifactId>
<version>10.14.2.0</version>
</dependency>
</dependencies>
module JDBC.JavaDB.Embedded {
exports com.franckbarbier.jdbc_javadb;
requires transitive java.sql; // 'transitive' because 'public JDBC_Embedded() throws java.sql.SQLException'
requires JavaDB;
}
<artifactId>JDBC_JavaDB_Embedded</artifactId>
<groupId>com.FranckBarbier</groupId>
<packaging>jar</packaging>
<version>1.0</version>
<dependencies>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>Derby</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
module My_service_module {
exports com.franckbarbier.My_service_API;
}
package com.franckbarbier.My_service_API;
public interface Destroyable {
public enum Status {
DEAD, LIVING, LIVING_DEAD
}
default Status destroy() {
return Status.DEAD;
}
}
Command:
jar --file=.\My_service_module-1.0.jar --describe-module
Display:
My_service_module@1.0 jar:file:///C:/Users/Bab/Documents/Nextcloud/From_Java_9/My_service_provider_module.Java/lib/./My_service_module-1.0.jar/!module-info.class
exports com.franckbarbier.My_service_API
requires java.base mandated
provides… with
)module My_service_provider_module {
requires My_service_module; // Service realization has "common" dependency upon API...
provides com.franckbarbier.My_service_API.Destroyable with com.franckbarbier.My_service_implementation.Destroyable_implementation;
}
package com.franckbarbier.My_service_implementation;
public class Destroyable_implementation implements com.franckbarbier.My_service_API.Destroyable {
private com.franckbarbier.My_service_API.Destroyable.Status _status = com.franckbarbier.My_service_API.Destroyable.Status.DEAD;
public com.franckbarbier.My_service_API.Destroyable.Status destroy() {
switch (_status) {
case DEAD:
return com.franckbarbier.My_service_API.Destroyable.Status.LIVING_DEAD;
case LIVING:
return com.franckbarbier.My_service_API.Destroyable.Status.DEAD;
case LIVING_DEAD:
return com.franckbarbier.My_service_API.Destroyable.Status.LIVING_DEAD;
default:
return _status; // Stupid but mandatory because the compiler wants 'return' in "all cases"...
}
}
}
+---lib
| My_service_module-1.0.jar
|
\---src
\---main
\---java
| module-info.java
|
\---com
\---franckbarbier
\---My_service_implementation
Destroyable_implementation.java
uses
)
(see also here…)
module My_service_consumer_module {
requires My_service_module; // Dependency upon API...
uses com.franckbarbier.My_service_API.Destroyable;
// Not that requiring any possible implementation of the service *IS MISSING*...
}
package com.franckbarbier.My_service_consumption;
public class Main {
public static void main(String[] args) {
// Lookup service implementation at runtime:
for (com.franckbarbier.My_service_API.Destroyable d : java.util.ServiceLoader.load(com.franckbarbier.My_service_API.Destroyable.class)) {
System.out.println(d.getClass().getSimpleName() + ": " + d.destroy());
}
}
}
+---lib
| My_service_module-1.0.jar
| My_service_provider_module-1.0.jar
|
\---src
\---main
\---java
| module-info.java
|
\---com
\---franckbarbier
\---My_service_consumption
Main.java
PowerShell:
& ($env:JAVA_HOME + "\bin\java") -p lib`;PRODUCTION_SOFTWARE `
-m My_service_consumer_module/com.franckbarbier.My_service_consumption.Main
Display:
Destroyable_implementation: LIVING_DEAD
opens
)
module Open_My_type_module {
exports com.franckbarbier.Open_My_type_package;
opens com.franckbarbier.Open_My_type_package; // Used by reflection...
}
// All module's packages may be opened:
// open module Open_My_type_module {
// exports com.franckbarbier.Open_My_type_package;
// }
module Access_My_type_module {
requires Open_My_type_module;
}
package com.franckbarbier.Open_My_type_package;
public class My_type {
private final static String _Secret =
"Not so much secret because of 'Opens'...";
}
package com.franckbarbier.Access_My_type_package;
public class Main {
public static void main(String[] args) {
try {
java.lang.reflect.Field field = com.franckbarbier.Open_My_type_package.My_type.class.getDeclaredField("_Secret");
try { /* Check whether 'opens' *activated*: */ field.setAccessible(true); }
catch (java.lang.reflect.InaccessibleObjectException ioe) { // 'opens' *inactivated*:
System.err.println(ioe.getClass().getSimpleName() + ": " + ioe.getMessage()); Runtime.getRuntime().exit(2);
}
// 'opens' *activated*:
Object o = null;
o = field.get(o);
System.out.println("Execution result: " + field.toGenericString() + ": " + o.toString());
} catch (IllegalAccessException | IllegalArgumentException | SecurityException e) { System.err.println(e.getClass().getSimpleName() + ": " + e.getMessage());
} catch (NoSuchFieldException nsfe) { System.err.println(nsfe.getClass().getSimpleName() + ": " + nsfe.getMessage()); Runtime.getRuntime().exit(1); }
}
}
├── lib
│ └── Open_My_type_module-1.0.jar
└── src
└── main
└── java
├── com
│ └── franckbarbier
│ └── Access_My_type_package
│ └── Main.java
└── module-info.java
Bash shell:
jar --file=./lib/Open_My_type_module-1.0.jar --describe-module
printf "Starting configuration..."
tree
javac -d PRODUCTION_SOFTWARE --module-path lib \
src/main/java/com/franckbarbier/Access_My_type_package/Main.java \
src/main/java/module-info.java
printf "\nCompilation ends...\n"
cd PRODUCTION_SOFTWARE
jar --create --verbose --file ./lib/Access_My_type_module-1.0.jar \
--main-class com.franckbarbier.Access_My_type_package.Main *
printf "Packaging' ends...\n\n"
cd ..
rm -r PRODUCTION_SOFTWARE
jar --file=./lib/Access_My_type_module-1.0.jar --describe-module
java --module-path lib --module Access_My_type_module
opens
… to
-
(see also here…)
module Programmable_thermostat {
// 'transitive' allows the access to 'javafx.application.Application'
// and 'javafx.stage.Stage' coming from 'javafx.graphics' module:
requires transitive javafx.controls; // 'javafx.base' and 'javafx.graphics' are transitively included...
/**
* JavaFX is based on reflection, so:
*/
opens com.franckbarbier._Programmable_thermostat_GUI to javafx.graphics;
// 'opens' to FXML if it is used...
requires PauWareTwo;
// This applies for actions called by reflection by PauWareTwo:
opens com.franckbarbier._Programmable_thermostat_MODEL._Programmable_thermostat to PauWareTwo;
opens com.franckbarbier._Programmable_thermostat_MODEL._Relays to PauWareTwo;
opens com.franckbarbier._Temperature to PauWareTwo;
requires PauWare2Web;
}
jdeps
(see also here…)
Command:
# '--module-path lib' is mandatory to find 'Open_My_type_module-1.0.jar':
jdeps --module-path lib ./lib/Access_My_type_module-1.0.jar
Display:
Access_My_type_module
[file:///Users/franckbarbier/Nextcloud/From_Java_9/Access_My_type_module.Java/lib/Access_My_type_module-1.0.jar]
requires Open_My_type_module
requires mandated java.base (@14.0.1)
Access_My_type_module -> Open_My_type_module
Access_My_type_module -> java.base
com.franckbarbier.Access_My_type_package -> com.franckbarbier.Open_My_type_package Open_My_type_module
com.franckbarbier.Access_My_type_package -> java.io java.base
com.franckbarbier.Access_My_type_package -> java.lang java.base
com.franckbarbier.Access_My_type_package -> java.lang.invoke java.base
com.franckbarbier.Access_My_type_package -> java.lang.reflect java.base
jdeps
visualizationjdeps
may generate visual data based on the dot
format
(--dot-output
option). .dot
files may then
be consumed by Graphviz. This may occur online here…
jdeps --ignore-missing-deps --recursive -summary \
--module-path ./web --dot-output visualization \
./web/Programmable_thermostat_JavaFX.PauWare2Web-1.0.jar
# Check result:
cat ./visualization/summary.dot
jdeps
visualization cont'dmodule-info.java
with jdeps
jdeps
also aims at analyzing legacy JAR (Java ARchive) files in order
to produce module-info.java
for Java 9 migration.
Commands:
jdeps --recursive --generate-module-info . ODBC-1.0.jar
jdeps --recursive --generate-open-module . ODBC-1.0.jar
Display (2nd command):
cat ./ODBC/module-info.java
open module ODBC {
requires transitive java.sql;
}
jlink
**
(see also here…)# By default, 'jlink' is not straightforwardly accessible (macOS):
/Library/Java/JavaVirtualMachines/jdk-14.0.1.jdk/Contents/Home/bin/jlink --version
Packaging:
# 'Access_My_type_module-1.0.jar' and 'Open_My_type_module-1.0.jar' are in the 'lib' dir.:
/Library/Java/JavaVirtualMachines/jdk-14.0.1.jdk/Contents/Home/bin/jlink -p lib \
--add-modules Access_My_type_module \
--output distribution \
--strip-debug \
--launcher go=Access_My_type_module
Execution:
./distribution/bin/go
*The --bind-services
option
allows the inclusion of services instead of loading them at run-time by means of --module-path
.
**jlink
cannot deal with automatic modules.
jmod
(archive) format
(see also here…)
jmod
is an archive format
that is used by the Java 9 core modules.
It brings out the possibility of having varied file types in the archive.
jmod
, opposite to the “old” JAR (Java ARchive) format,
is not executable. Instead,
jmod
archives aim at being assembled using jlink
.
Bash shell:
mkdir ./jmod
/Library/Java/JavaVirtualMachines/jdk-15.0.2.jdk/Contents/Home/bin/jmod create \
--class-path ./My_stack.PauWare2-1.0.jar --main-class com.franckbarbier.My_stack.My_stack \
./jmod/My_stack.jmod
# Copy dependencies to the directory where 'My_stack.jmod' is located
cp ./PauWare2-1.0.jar ./jmod/PauWare2-1.0.jar
# Create runtime image by assembling jar and jmod files:
/Library/Java/JavaVirtualMachines/jdk-15.0.2.jdk/Contents/Home/bin/jlink --module-path ./jmod \
--add-modules My_stack --output ./jmod/distribution --strip-debug --launcher My_stack=My_stack/com.franckbarbier.My_stack.My_stack
# Execution:
./jmod/distribution/bin/My_stack;rm -rf ./jmod
Ahead-Of-Time
(AOT) compilation
(see also here…)
jaotc
allows
the production of native runtime images (opposite to the
Just-In-Time
compiler). jaotc
generates native code for execution.
Beyond, GraalVM
(here) is the technology that aims at
enhancing Java infrastructure in the future.
Bash shell:
echo "Ahead-Of-Time (AOT) compiler ver.: " `/Library/Java/JavaVirtualMachines/jdk-15.0.2.jdk/Contents/Home/bin/jaotc --version`
# 'PauWare2-1.0.jar' has to found be in the class path:
/Library/Java/JavaVirtualMachines/jdk-15.0.2.jdk/Contents/Home/bin/jaotc --verbose \
--output My_stack.so --jar ./My_stack.PauWare2-1.0.jar -J-cp -J./PauWare2-1.0.jar
# Execution requires metadata (namely, main class) that is available in jar files:
java -cp ./My_stack.PauWare2-1.0.jar:./PauWare2-1.0.jar -verbose \
-XX:+UnlockExperimentalVMOptions -XX:AOTLibrary=My_stack.so -XX:+PrintAOT \
com.franckbarbier.My_stack.My_stack
--release
option
(see also here…)
Legacy code:
public class Leap_year {
public static String Leap_year(final String date) throws Exception {
java.util.Calendar calendar = java.util.Calendar.getInstance();
calendar.setTime(new java.text.SimpleDateFormat("yyyy-MM-dd").parse(date));
return " (before Java 8) " + (new java.util.GregorianCalendar()).isLeapYear(calendar.get(java.util.Calendar.YEAR));
}
}
Modern code:
public class Leap_year { // In 'Leap_year' Java 9 module...
public static String Leap_year(final String date) throws Exception {
return " (from Java 8) " + java.time.LocalDate.parse(date).isLeapYear();
}
}
Compilation:
javac --release 7 -d ./7 ./seven/com/franckbarbier/leap_year/*.java
javac --release 9 -d ./9 ./nine/module-info.java ./nine/com/franckbarbier/leap_year/*.java
Packaging (single-release mode) & execution:
jar --create --file ./Leap_year.jar --main-class=com.franckbarbier.leap_year.Main -C 7 .
# Execution (before Java 8):
java -jar ./Leap_year.jar
Packaging (multiple-release mode) & execution:
jar --create --file ./Leap_year.jar --main-class=com.franckbarbier.leap_year.Main -C 7 . \
--release 9 -C 9 .
# Execution (from Java 8):
java -jar ./Leap_year.jar
# Or:
java --module-path . --module Leap_year/com.franckbarbier.leap_year.Main
MANIFEST.MF
:
Multi-Release: true
--class-path
versus
--module-path
(see also here…)While Java 9 still uses the -cp
,
-classpath
or --class-path
equivalent options,
compilation and execution in Java 9 benefit from
the --module-path
option to access dependencies in general.
Execution with two alternatives:
java -cp ./My_stack.PauWare2-1.0.jar:./PauWare2-1.0.jar com.franckbarbier.My_stack.My_stack
# Or:
java --module-path . --module My_stack/com.franckbarbier.My_stack.My_stack
The rule is that
explicit modules (those having a module-info.java
descriptor) naturally go to the module path.
However, they cannot read unnamed modules (those that are expected on the class path).
The key practice is that implicitly named (a.k.a. automatic) modules are required by explicitly named modules
on the module path while automatic modules have the possibility of accessing unnamed modules on the class path.