Java 9. Project Jigsaw. Modularity

Victor Melnik
Nov 8, 2016 · 10 min read

Module

module com.example.samplemodule {
requires com.example.sampleapp;
requires public java.httpclient;
exports com.example.samplemodule.model;
exports com.example.samplemodule.spi;
uses com.example.samplemodule.spi.DataProvider;
provides com.example.sampleapp.spi.SettingsProvider
with com.example.samplemodule.ModuleSettingsProvider;
}
module com.example.samplemodule {
requires com.example.sampleapp;
requires public java.httpclient;
exports com.example.samplemodule.model;
exports com.example.samplemodule.spi;
uses com.example.samplemodule.spi.DataProvider;
provides com.example.sampleapp.spi.SettingsProvider
with com.example.samplemodule.ModuleSettingsProvider;

Example 1. Direct dependence of two modules

// com/example/timeapp/Main.java
package com.example.timeapp;
import com.example.timelocal.TimeLocal;public final class Main {

public static void main(String[] args) {
System.out.format("Current time: %s%n", TimeLocal.now());
}
}
// module-info.java
module com.example.timeapp {
requires java.base;
requires com.example.timelocalmodule;
}
// com/example/timelocal/TimeLocal.java
package com.example.timelocal;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
public class TimeLocal {

public static String now() {
return DateTimeFormatter.ISO_LOCAL_DATE_TIME.format(LocalDateTime.now());
}
}
// module-info.java
module com.example.timelocalmodule {
exports com.example.timelocal;
}
Adding dependency
Adding dependency
Package scope in Netbeans IDE
@echo offset JAVA9_HOME=D:\Program Files\Java\jdk-9
set JAVA9="%JAVA9_HOME%/bin/java"
set JAVAC9="%JAVA9_HOME%/bin/javac"
mkdir mods\com.example.timeapp
mkdir mods\com.example.timelocalmodule
echo Compile timelocalmodule
%JAVAC9% -d mods/com.example.timelocalmodule ^
TimeLocalModule/src/module-info.java ^
TimeLocalModule/src/com/example/timelocal/TimeLocal.java
echo Compile timeapp
%JAVAC9% --module-path mods -d mods/com.example.timeapp ^
TimeApp/src/module-info.java ^
TimeApp/src/com/example/timeapp/Main.java
echo Run timeapp
%JAVA9% --module-path mods ^
-m com.example.timeapp/com.example.timeapp.Main
#!/bin/bashJAVA9_HOME=/usr/lib/jvm/java-9-oracle
JAVA9=$JAVA9_HOME/bin/java
JAVAC9=$JAVA9_HOME/bin/javac
mkdir -p mods/com.example.timeapp
mkdir -p mods/com.example.timelocalmodule
echo "Compile timelocalmodule"
$JAVAC9 -d mods/com.example.timelocalmodule \
TimeLocalModule/src/module-info.java \
TimeLocalModule/src/com/example/timelocal/TimeLocal.java
echo "Compile timeapp"
$JAVAC9 --module-path mods -d mods/com.example.timeapp \
TimeApp/src/module-info.java \
TimeApp/src/com/example/timeapp/Main.java
echo "Run timeapp"
$JAVA9 --module-path mods \
-m com.example.timeapp/com.example.timeapp.Main
Current time: 2016-10-20T18:36:36.6763098

Example 2. Services and ServiceLoader

module additional {
provides com.example.spi.Provider // base interface
with com.impl.ProviderImpl; // implemented in this module
}
module main {
uses com.example.spi.Provider;
}
ServiceLoader<Provider> sl = ServiceLoader.load(Provider.class);
for (Provider p : sl) {
// ..
}
// com/example/timeapp/spi/TimeProvider.java
package com.example.timeapp.spi;
public interface TimeProvider {

String now();
}
// module-info.java
module com.example.timeapp {
requires java.base;

exports com.example.timeapp.spi;
}
// com/example/timelocal/TimeLocalProvider.java
package com.example.timelocal;
import com.example.timeapp.spi.TimeProvider;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
public class TimeLocalProvider implements TimeProvider {

@Override
public String now() {
return DateTimeFormatter.ISO_LOCAL_DATE_TIME.format(LocalDateTime.now());
}
}
// module-info.java
module com.example.timelocalmodule {
requires com.example.timeapp;

provides com.example.timeapp.spi.TimeProvider
with com.example.timelocal.TimeLocalProvider;
}
// com/example/timeapp/Main.java
package com.example.timeapp;
import com.example.timeapp.spi.TimeProvider;
import java.util.ServiceLoader;
public final class Main {

public static void main(String[] args) {
ServiceLoader<TimeProvider> serviceLoader = ServiceLoader.load(TimeProvider.class);
serviceLoader.forEach(t -> {
System.out.format("Current time: %s%n", t.now());
System.out.println(t.getClass());
});
}
}
Exception in thread "main" java.util.ServiceConfigurationError: com.example.timeapp.spi.TimeProvider: use not declared in module com.example.timeapp
at java.util.ServiceLoader.fail(java.base@9-ea/ServiceLoader.java:386)
at java.util.ServiceLoader.checkModule(java.base@9-ea/ServiceLoader.java:371)
at java.util.ServiceLoader.<init>(java.base@9-ea/ServiceLoader.java:319)
at java.util.ServiceLoader.<init>(java.base@9-ea/ServiceLoader.java:351)
at java.util.ServiceLoader.load(java.base@9-ea/ServiceLoader.java:1021)
at com.example.timeappMain.main(com.example.timeapp/Main.java:9)
// module-info.java
module com.example.timeapp {
requires java.base;

exports com.example.timeapp.spi;

uses com.example.timeapp.spi.TimeProvider;
}
Adding a module
Current time: 2016-10-20T20:41:39.9614732
class com.example.timelocal.TimeLocalProvider
// com/example/timenetwork/TimeNetworkProvider.java
package com.example.timenetwork;
import com.example.timeapp.spi.TimeProvider;
import java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpResponse;
public class TimeNetworkProvider implements TimeProvider {

@Override
public String now() {
try {
return HttpClient.getDefault()
.request(URI.create("http://www.timeapi.org/utc/now"))
.header("User-Agent", "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.143 Safari/537.36")
.GET()
.response()
.body(HttpResponse.asString());
} catch (IOException | InterruptedException ex) {
throw new RuntimeException("Network error");
}
}
}
// module-info.java
module com.example.timenetworkmodule {
requires com.example.timeapp;
requires java.httpclient;

provides com.example.timeapp.spi.TimeProvider
with com.example.timenetwork.TimeNetworkProvider;
}
rem compile.cmd
echo Compile timeapp
%JAVAC9% -d mods/com.example.timeapp ^
TimeApp/src/module-info.java TimeApp/src/com/example/timeapp/Main.java ^
TimeApp/src/com/example/timeapp/spi/TimeProvider.java
echo Compile timelocalmodule
%JAVAC9% --module-path mods -d mods/com.example.timelocalmodule ^
TimeLocalModule/src/module-info.java ^
TimeLocalModule/src/com/example/timelocal/TimeLocal.java ^
TimeLocalModule/src/com/example/timelocal/TimeLocalProvider.java
echo Compile timenetworkmodule
%JAVAC9% --module-path mods -d mods/com.example.timenetworkmodule ^
TimeNetworkModule/src/module-info.java ^
TimeNetworkModule/src/com/example/timenetwork/TimeNetworkProvider.java
echo Run timeapp
%JAVA9% --module-path mods ^
-m com.example.timeapp/com.example.timeapp.Main
# compile.sh
echo "Compile timeapp"
$JAVAC9 -d mods/com.example.timeapp \
TimeApp/src/module-info.java \
TimeApp/src/com/example/timeapp/Main.java \
TimeApp/src/com/example/timeapp/spi/TimeProvider.java
echo "Compile timelocalmodule"
$JAVAC9 --module-path mods -d mods/com.example.timelocalmodule \
TimeLocalModule/src/module-info.java \
TimeLocalModule/src/com/example/timelocal/TimeLocal.java \
TimeLocalModule/src/com/example/timelocal/TimeLocalProvider.java
echo "Compile timenetworkmodule"
$JAVAC9 --module-path mods -d mods/com.example.timenetworkmodule \
TimeNetworkModule/src/module-info.java \
TimeNetworkModule/src/com/example/timenetwork/TimeNetworkProvider.java
echo "Run timeapp"
$JAVA9 --module-path mods \
-m com.example.timeapp/com.example.timeapp.Main
Current time: 2016-10-20T20:55:03.3944269
class com.example.timelocal.TimeLocalProvider
Current time: 2016-10-20T17:55:06+00:00
class com.example.timenetwork.TimeNetworkProvider

Example 3. Modules and resources

// module-info.java
module com.example.timeapp {
requires java.base;
requires java.desktop;

exports com.example.timeapp.spi;

uses com.example.timeapp.spi.TimeProvider;
}
// com/example/timeapp/spi/TimeProvider.java
package com.example.timeapp.spi;
import java.awt.Image;public interface TimeProvider {

String now();

Image icon();
}
// com/example/timeapp/Main.java
public final class Main extends JFrame {

public static void main(String[] args) {
final Main frame = new Main();
ServiceLoader<TimeProvider> serviceLoader = ServiceLoader.load(TimeProvider.class);
serviceLoader.forEach(t -> {
final JButton button = new JButton();
button.setText(t.getClass().getSimpleName());
final Image icon = t.icon();
if (icon != null) {
button.setIcon(new ImageIcon(icon));
}
button.addActionListener(e -> {
frame.outputLabel.setText(String.format("Current time: %s%n", t.now()));
});
frame.modulesPanel.add(button);
});
frame.pack();
frame.setVisible(true);
}

private final JPanel modulesPanel;
private final JLabel outputLabel;

public Main() {
super("Jigsaw Example");

modulesPanel = new JPanel();
modulesPanel.setLayout(new BoxLayout(modulesPanel, BoxLayout.LINE_AXIS));
add(modulesPanel, BorderLayoutNORTH);

outputLabel = new JLabel("output");
outputLabel.setHorizontalAlignment(SwingConstantsCENTER);
add(outputLabel, BorderLayout.CENTER);

setDefaultCloseOperation(EXIT_ON_CLOSE);
}
}
requires public java.desktop;
// module-info.java
module com.example.timeapp {
requires java.base;
requires public java.desktop;

exports com.example.timeapp.spi;

uses com.example.timeapp.spi.TimeProvider;
}
public class TimeLocalProvider implements TimeProvider {

private static Image icon;

@Override
public String now() {
return DateTimeFormatter.ISO_LOCAL_DATE_TIME.format(LocalDateTime.now());
}

@Override
public Image icon() {
if (icon == null) {
try (InputStream is = getClass().getResourceAsStream("/res/icon.png")) {
if (is != null)
icon = ImageIO.read(is);
} catch (IOException ignore) { }
}
return icon;
}
}
Resources are loaded successfully

Example 4. Automatic module

// com/example/timemidnight/MidnightProvider.java
package com.example.timemidnight;
import com.example.timeapp.spi.TimeProvider;
import java.awt.Image;
import java.io.IOException;
import java.io.InputStream;
import javax.imageio.ImageIO;
public class MidnightProvider implements TimeProvider {

private static Image icon;

@Override
public String now() {
return "00:00";
}

@Override
public Image icon() {
if (icon == null) {
try (InputStream is = getClass().getResourceAsStream("/res/icon.png")) {
if (is != null)
icon = ImageIO.read(is);
} catch (IOException ignore) { }
}
return icon;
}
}
Dependencies in modulepath
// module-info.java
module com.example.timeapp {
requires java.base;
requires public java.desktop;
requires midnight;

exports com.example.timeapp.spi;

uses com.example.timeapp.spi.TimeProvider;
}
Stream.concat(
StreamSupport.stream(serviceLoader.spliterator(), false),
Stream.of(new MidnightProvider())) // we can directly access class from automatic module
.forEach(t -> ...
Result

Thanks to Igor Strebezhev

Victor Melnik

Written by

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade