Meet Java Modules
As a warm up let's see how JDK itself was modularised.
List module files
New folder with JDK modules. jmod is currently internal jdk format for module file (jar on steroids)
ls -al /usr/lib/jvm/jdk-9/jmods/
total 134544
drwxr-xr-x 2 root root 4096 wrz 2 15:17 .
drwxr-xr-x 8 root root 4096 wrz 24 16:35 ..
-rw-r--r-- 1 uucp 143 60992 sie 3 06:24 java.activation.jmod
-rw-r--r-- 1 uucp 143 19179351 sie 3 06:24 java.base.jmod
-rw-r--r-- 1 uucp 143 111302 sie 3 06:24 java.compiler.jmod
-rw-r--r-- 1 uucp 143 2680154 sie 3 06:24 java.corba.jmod
-rw-r--r-- 1 uucp 143 51795 sie 3 06:24 java.datatransfer.jmod
-rw-r--r-- 1 uucp 143 15766124 sie 3 06:24 java.desktop.jmod
-rw-r--r-- 1 uucp 143 765862 sie 3 06:24 javafx.base.jmod
-rw-r--r-- 1 uucp 143 2511426 sie 3 06:24 javafx.controls.jmod
-rw-r--r-- 1 uucp 143 238532 sie 3 06:24 javafx.deploy.jmod
(...)
-rw-r--r-- 1 uucp 143 119985 sie 3 06:24 java.logging.jmod
-rw-r--r-- 1 uucp 143 880650 sie 3 06:24 java.management.jmod
-rw-r--r-- 1 uucp 143 90550 sie 3 06:24 java.management.rmi.jmod
-rw-r--r-- 1 uucp 143 443764 sie 3 06:24 java.naming.jmod
-rw-r--r-- 1 uucp 143 62499 sie 3 06:24 java.prefs.jmod
jmod
With java 9 a new option to describe module was added.
- try also: java -d jdk.unsupported
jmod describe /usr/lib/jvm/jdk-9/jmods/java.base.jmod
java.base@9
exports java.io
exports java.lang
exports java.lang.annotation
exports java.lang.invoke
exports java.lang.module
exports java.lang.ref
exports java.lang.reflect
exports java.math
...
list modules
java --list-modules | column
java -d java.sql
java.sql@9
exports java.sql
exports javax.sql
exports javax.transaction.xa
requires java.base mandated
requires java.logging transitive
requires java.xml transitive
uses java.sql.Driver
module system
Intro
We are going to use recent version of Intellij (2018 or newer) . In this workshop perform actions step by step.
Create New Project
File -> New -> Project -> Empty Project
Name : modulesjug
Create New Java Module
Name : com.jug.modules.intro.math
Press F4 and in project settings choose language level Java 9
Alt + Insert on src -> new module.info.java
Create New Package
Name : com.jug.modules.intro.math
Create New Class MathLib.java
package com.jug.modules.intro.math;
import java.util.Optional;
public class MathLib {
public static Optional<Integer> add(Integer i1, Integer i2){
return Optional.of(i1+i2);
}
}
- Create Main class in math package
package com.jug.modules.intro.math;
public class MathMain {
public static void main(String[] args) {
System.out.println("adding in math lib : "+MathLib.add(1,2));
}
}
create empty folder mods in the project folder on modules level
mkdir mods >ls com.jug.modules.intro.math mods
compile module
you can create bash file -> gedit compilemod.sh &
#!/bin/bash
chmod 700 compilemod.sh
javac -d mods/com.jug.modules.intro.math com.jug.modules.intro.math/src/module-info.java /
com.jug.modules.intro.math/src/com/jug/modules/intro/math/MathLib.java /
com.jug.modules.intro.math/src/com/jug/modules/intro/math/MathMain.java
tree mods
mods
└── com.jug.modules.intro.math
├── com
│ └── jug
│ └── modules
│ └── intro
│ └── math
│ ├── MathLib.class
│ └── MathMain.class
└── module-info.class
- Run Module
java --module-path mods -m com.jug.modules.intro.math/com.jug.modules.intro.math.MathMain
adding in math lib : Optional[3]
Module Encapsulation
- add internal package
package com.jug.modules.intro.math.internal
- Put there utility class OverflowDetector
public class OverflowDetector {
public static Optional<Integer> add(int a, int b){
try{
return Optional.of(java.lang.Math.addExact(a,b));
}catch(Exception e){
return Optional.empty();
}
}
}
- export main package
module com.jug.modules.intro.math {
exports com.jug.modules.intro.math;
}
use OverflowDetector
public class MathLib { public static Optional<Integer> add(Integer i1, Integer i2){ return OverflowDetector.add(i1,i2); } }
compile full module
javac -d mods/com.jug.modules.intro.math $(find com.jug.modules.intro.math/src/ -name *.java)
- show modules description
java --module-path mods --describe-module com.jug.modules.intro.math
file:///home/pawel/projects/workshops/modulesjug/mods/com.jug.modules.intro.math/
exports com.jug.modules.intro.math
- show cyclic dependency by requiring itself
Second Module - Displayer
create new module com.jug.modules.intro.displayer in the same parent folder as math module.
add module-info.java
- requires com.jug.modules.intro.math;
add Displayer class
package com.jug.modules.intro.displayer;
import com.jug.modules.intro.math.MathLib;
import java.util.Optional;
import java.util.Scanner;
import java.util.function.Consumer;
public class Displayer {
private static Consumer<Integer> printResult=(i) -> System.out.println("result is : "+i);
private static Runnable errorProcedure = () -> System.out.println("unable to add integeres, overflow");
public static void displayMath(){
Scanner scanner = new Scanner(System.in);
System.out.print("i1 : ");
Integer i1=scanner.nextInt();
System.out.print("i2 : ");
Integer i2=scanner.nextInt();
Optional<Integer> result = MathLib.add(i1, i2);
result.ifPresentOrElse(printResult,errorProcedure);
}
public static void main(String[] args) {
displayMath();
}
}
- try to compile
javac -d mods/com.jug.modules.intro.displayer $(find com.jug.modules.intro.displayer/src/ -name *.java)
com.jug.modules.intro.displayer/src/module-info.java:2: error: module not found: com.jug.modules.intro.math
requires com.jug.modules.intro.math;
- add module path
javac -d mods/com.jug.modules.intro.displayer --module-path mods $(find com.jug.modules.intro.displayer/src/ -name *.java)
- test displayer with input : MAX_INT = 2147483647
java --module-path mods -m com.jug.modules.intro.displayer/com.jug.modules.intro.displayer.Displayer
i1 : 2147483647
i2 : 2147483647
unable to add integeres, overflow
- try to access internal math package
import com.jug.modules.intro.math.MathLib.internal;
com.jug.modules.intro.displayer/src/com/jug/modules/intro/displayer/Displayer.java:4: error: cannot find symbol
import com.jug.modules.intro.math.MathLib.internal;
- remove math module and try to compile displayer
java --module-path mods -m com.jug.modules.intro.displayer/com.jug.modules.intro.displayer.Displayer
Error occurred during initialization of boot layer
java.lang.module.FindException: Module com.jug.modules.intro.math not found, required by com.jug.modules.intro.displayer
- compile math once again
javac -d mods/com.jug.modules.intro.math $(find com.jug.modules.intro.math/src/ -name *.java)
- java --show-module-resolution
java --show-module-resolution --limit-modules com.jug.modules.intro.math --module-path mods -m com.jug.modules.intro.displayer
root com.jug.modules.intro.displayer file:///home/pawel/projects/workshops/modulesjug/mods/com.jug.modules.intro.displayer/
com.jug.modules.intro.displayer requires com.jug.modules.intro.math file:///home/pawel/projects/workshops/modulesjug/mods/com.jug.modules.intro.math/
module com.jug.modules.intro.displayer does not have a MainClass attribute, use -m <module>/<main-class>
- decompile
Compiled from "module-info.java"
module com.jug.modules.intro.displayer {
requires com.jug.modules.intro.math;
requires java.base;
}
Packaging to jars
jar --create --file modsjars/modules.math.jar -C mods/com.jug.modules.intro.math/ .
- try to recompile displayer. Because we have math packed in jar so technically we have two modules with the same name
javac -d mods/com.jug.modules.intro.displayer --module-path mods $(find com.jug.modules.intro.displayer/src/ -name *.java)
error: duplicate module on application module path
module in com.jug.modules.intro.math
1 error
- move Jar to different folder
javac -d mods/com.jug.modules.intro.displayer --module-path modsjars $(find com.jug.modules.intro.displayer/src/ -name *.java)
jar --create --file modsjars/modules.displayer.jar --main-class com.jug.modules.intro.displayer.Displayer -C mods/com.jug.modules.intro.displayer/ .
java --module-path modsjars --module com.jug.modules.intro.displayer
i1 : 4
i2 : 7
result is : 11
// --show-module-resultion
// jar --list --file mods/modules.displayer.jar
// jar --describe-module --file mods/monitor.observer.jar
// -Xlog:module*=debug:stdout:tid
- Keep jar and folder to show ambigous modules
Automatic Modules
jar --file=/home/pawel/.m2/repository/commons-lang/commons-lang/2.5/commons-lang-2.5.jar --describe-module
No module descriptor found. Derived automatic module.
[email protected] automatic
requires java.base mandated
contains org.apache.commons.lang
contains org.apache.commons.lang.builder
contains org.apache.commons.lang.enums
contains org.apache.commons.lang.exception
contains org.apache.commons.lang.math
contains org.apache.commons.lang.mutable
contains org.apache.commons.lang.reflect
contains org.apache.commons.lang.text
contains org.apache.commons.lang.time
JLink
jlink --module-path mods:/usr/lib/jvm/jdk-9/jmods --add-modules com.jug.modules.intro.displayer --output hellojre
modulesjug$ hellojre/bin/java --list-modules
com.jug.modules.intro.displayer
com.jug.modules.intro.math
java.base@9
Run :
hellojre/bin/java -m com.jug.modules.intro.displayer/com.jug.modules.intro.displayer.Displayer
EXERCISE PART 1
- Create new module - com.exercise.consumer
- Set Java level in IDE to Java 9
- Create - empty for now - module-info.java
- Create new module - com.exercise.producer
- Set Java Level to Java 9
- Create module-info.java
- In producer module create package : com.exercise.producer
In com.exercise.producer create new class which produces List of Strings according to
class Producer{ public static List<String> produce(){....} } Producer.produce mustBe List("one","two","three") //test assertion in pseudo code
Add proper export to module.info
Add dependency to producer in consumer
- you may need to add dependency in IDE properties
Create packages the same as module name
Add Consumer class + imports
public class Consumer {
public static void main(String[] args) {
String result=Producer.produce().stream().collect(Collectors.joining("|"));
System.out.println(result);
}
}
When you run it the result should be
one|two|three
Create another module com.exercise.Printer
create module
create package
create module-info.java
in com.exercise.Printer add an interface
public interface Printer { void print(String s); }
create another package which will be hidden in the module. - com.exercise.printer.console
create printer implementation
package com.exercise.printer.console; import com.exercise.printer.Printer; public class ConsolePrinter implements Printer { @Override public void print(String s) { System.out.println("PRINTING: "+s); } }
create provider in the exported package com.exercise.printer
package com.exercise.printer; import com.exercise.printer.console.ConsolePrinter; public abstract class AbstractFactoryPrinterProviderBean { public static Printer providePrinter(){ return new ConsolePrinter(); } }
Use Printer in consumer module
update module-info
package com.exercise.consumer; import com.exercise.printer.AbstractFactoryPrinterProviderBean; import com.exercise.printer.Printer; import com.exercise.producer.Producer; import java.util.stream.Collectors; public class Consumer { public static void main(String[] args) { String result=Producer.produce().stream().collect(Collectors.joining("|")); Printer printer=AbstractFactoryPrinterProviderBean.providePrinter(); printer.print(result); } }
Result should be : PRINTING: one|two|three
It should be forbidden to access ConsolePrinter directly
//THIS IS FORBIDDEN!! import com.exercise.printer.console.ConsolePrinter; Printer printer=new ConsolePrinter();
Compile everything into mods folder
First displayer
javac --module-path mods -d mods/com.exercise.printer $(find com.exercise.printer/src/ -name *.java)
Then producer
Finally consumer
Run Consumer
java --module-path mods -m com.exercise.consumer/com.exercise.consumer.Consumer
Create Consumer Jar
Create Custom JRE for our Jar with JLink
Run Consumer on Customer JRE
Maven
First clone repo : https://github.com/PawelWlodarski/mvnmodules
parent pom
- configure Java9 in the main pom.xml
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.target>1.9</maven.compiler.target>
<maven.compiler.source>1.9</maven.compiler.source>
</properties>
- configure modules in the main pom.xml.
<modules>
<module>math</module>
<module>calculator</module>
<module>gui</module>
</modules>
- configure build plugin in the main pom.xml
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.7.0</version>
<configuration>
<source>9</source>
<target>9</target>
<showWarnings>true</showWarnings>
<showDeprecation>true</showDeprecation>
</configuration>
</plugin>
module pom
- gui modules pom.xml and check dependency on other module
<dependencies>
<dependency>
<groupId>lodz.jug</groupId>
<artifactId>calculator</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
- module-info.java in gui/src/main/java
module com.jug.modules.intro.gui {
requires com.jug.modules.intro.calculator;
}
Decoupling
Show EngineProvider. How it connects with services?
Compile Modules
mvn clean install
all jars will be saved in mods folder because of setting in the main pom.xml
<outputDirectory>../mods</outputDirectory>
Now try to run gui.
java --module-path mods --module com.jug.modules.intro.gui/com.jug.modules.intro.gui.GUI
Services
Services allows you to decouple provider of a given service implementation from its consumer
You can find three modules in the main pom.xml
<module>displayerAPI</module>
<module>displayerProvider1</module>
<module>displayerConsumer</module>
Those three modules use displayer API but consumer and provider are not aware of itself.
First let's look at the API.
package com.jug.modules.displayer.api;
public interface Displayer {
boolean supports(DisplayerPlatform p);
void display(String s);
}
It's an normal interface yet purpose of support method may be not clear at first. Generally Each implementation can support different features and because consumer will not be aware of implementation so it can not choose one specific. We will see more in consumer module.
Also nothing specific in module-info
module com.jug.modules.displayer.api{
exports com.jug.modules.displayer.api;
}
No we will see something new in producer module. Producer has to say that it is able to provides specific implementation of a service
module displayerProvider1 {
requires com.jug.modules.displayer.api;
provides com.jug.modules.displayer.api.Displayer
with com.jug.modules.displayer.console.ConsoleDisplayer;
}
No consumer has to declare usage of this service
module displayerConsumer {
requires com.jug.modules.displayer.api;
uses com.jug.modules.displayer.api.Displayer;
}
And finally we have to use service locator to choose proper implementation. Here is when support method may become useful.
ServiceLoader<Displayer> displayers = ServiceLoader.load(Displayer.class);
Stream<ServiceLoader.Provider<Displayer>> providerStream = displayers.stream();
Optional<Displayer> firstPotentialDisplayer = providerStream
.map(ServiceLoader.Provider::get)
.filter(d -> d.supports(DisplayerPlatform.LINUX))
.findFirst();
Displayer displayer = firstPotentialDisplayer.orElseThrow(() -> new RuntimeException("No Displayer her"));
displayer.display("Displayer Found Successfully : "+displayer.getClass());
Services Exercise (Together)
- Create new module in main pom.xml
- Add another implementation of Displayer
- Declare in module-info that you are providing implementation
- in consumer - just for - print all provided implementations
Dependencies Configuration
requires transitive
opens
opens <package-name>
opens <package-name> to <package_in_other_module>
- --add-opens
qualified exports
* Persistence Framework
exports javamodularity.easytext.gui to javafx.graphics;
unnamed module
- classpath emulation
- Code in the unnamed module (the class path) can access code in any named
modules using reflection
- --illegal-access flag is set by default for this module
Compile myUnnamed and runt he following
projects/workshops/mvnmodules$ jar --file mods/myUnnamed-1.0-SNAPSHOT.jar --describe-module
No module descriptor found. Derived automatic module.
[email protected] automatic
requires java.base mandated
contains com.jug.intro.unnamed
jdeps
deps math-1.0-SNAPSHOT.jar
com.jug.modules.intro.math
[file:///home/pawel/projects/workshops/mvnmodules/mods/math-1.0-SNAPSHOT.jar]
requires mandated java.base (@9)
com.jug.modules.intro.math -> java.base
com.jug.modules.intro.math -> com.jug.modules.intro.math.internal com.jug.modules.intro.math
com.jug.modules.intro.math -> java.io java.base
com.jug.modules.intro.math -> java.lang java.base
com.jug.modules.intro.math -> java.lang.invoke java.base
com.jug.modules.intro.math -> java.util java.base
com.jug.modules.intro.math.internal -> java.lang java.base
com.jug.modules.intro.math.internal -> java.util java.base
- with modules dependencies
jdeps --module-path mods mods/gui-1.0-SNAPSHOT.jar
com.jug.modules.intro.gui
[file:///home/pawel/projects/workshops/mvnmodules/mods/gui-1.0-SNAPSHOT.jar]
requires com.jug.modules.intro.calculator
requires mandated java.base (@9)
com.jug.modules.intro.gui -> com.jug.modules.intro.calculator
com.jug.modules.intro.gui -> java.base
com.jug.modules.intro.gui -> com.jug.modules.intro.calculator com.jug.modules.intro.calculator
list modules + custom modules
java --module-path out --list-modules
Qualified exports
exports moduleb.privateA to A; // Exported only to module A
exports moduleb.privateC to C; // Exported only to module C
java -d java.base
module java.base@9
...
exports jdk.internal.ref to java.desktop, javafx.media
exports jdk.internal.math to java.desktop
exports sun.net.ext to jdk.net
exports jdk.internal.loader to java.desktop, java.logging
Optionality
require static
Links