Java 9 Module System allows a package to be exported to one or more specific modules, but not to others. To achieve that we need to use exports .. to clause:
module my.module{
exports my.package to other.module, another.module;
}
This feature was needed to avoid exposing internal packages to all modules while allowing them to be accessed by only selected friendly modules. For example, JDK java.base has many packages which should not be exposed to everyone. Following is a relevant snippet of the java.base module:
module java.base {
............
exports com.sun.security.ntlm to java.security.sasl;
exports jdk.internal to jdk.jfr;
exports jdk.internal.jimage to jdk.jlink;
exports jdk.internal.jimage.decompressor to jdk.jlink;
exports jdk.internal.jmod to
jdk.compiler,
jdk.jlink;
exports jdk.internal.loader to
java.instrument,
java.logging;
exports jdk.internal.logger to java.logging;
exports jdk.internal.math to java.desktop;
.....................
}
Example
In this example, we are going to create three modules. The second module will export a package only 'to' the first module. The third module which will require both these two modules, cannot access the package which it is not qualified for.
We are going to use multi-module directory structure (tutorial here) so that we don't have to compile each module individually.
The First Module
core.ui/ui/widget/WidgetController.javapackage ui.widget;
public class WidgetController {
public void display(String str){
System.out.println(str);
}
}
core.ui/module-info.javamodule core.ui {
exports ui.widget;
}
The Second Module
core.util/util/internal/Util.javapackage util.internal;
public class Util {
public static void showString(String str) {
System.out.println(str);
}
}
core.util/util/common/StringUtil.javapackage util.common;
import java.util.Arrays;
import java.util.List;
public class StringUtil {
public static List<String> split(String str, String expr) {
return Arrays.asList(str.split(expr));
}
}
core.util/module-info.javamodule core.util {
exports util.common;
exports util.internal to core.ui;
}
As seen above, the package 'util.common' is exported without any qualifications (so it will be accessible to all other modules) while 'util.internal' is only exported to a qualified module 'core.ui'.
The Third Module
main.app/com/example/Main.javapackage com.example;
import ui.widget.WidgetController;
import util.common.StringUtil;
import java.util.List;
public class Main {
public static void main(String[] args) {
WidgetController wc = new WidgetController();
wc.display("a test string");
List<String> list = StringUtil.split("a,b", ",");
System.out.println(list);
}
}
main.app/module-info.javamodule main.app {
requires core.ui;
requires core.util;
}
Here is our project's directory structure.
D:\qualified-exports>"tree /A /F" \---src +---core.ui | | module-info.java | | | \---ui | \---widget | WidgetController.java | +---core.util | | module-info.java | | | \---util | +---common | | StringUtil.java | | | \---internal | Util.java | \---main.app | module-info.java | \---com \---example Main.java
Compiling
Let's save the list of all java classes in a file:
D:\qualified-exports>dir /B /S src\*.java > javaFiles.txt
Confirm our file has all java source files:
D:\qualified-exports>type javaFiles.txt D:\qualified-exports\src\core.ui\module-info.java D:\qualified-exports\src\core.ui\ui\widget\WidgetController.java D:\qualified-exports\src\core.util\module-info.java D:\qualified-exports\src\core.util\util\common\StringUtil.java D:\qualified-exports\src\core.util\util\internal\Util.java D:\qualified-exports\src\main.app\module-info.java D:\qualified-exports\src\main.app\com\example\Main.java
Compiling now:
D:\qualified-exports>javac -d outDir --module-source-path src @javaFiles.txt
After compilation:
D:\qualified-exports>"tree /A /F" | javaFiles.txt | +---outDir | +---core.ui | | | module-info.class | | | | | \---ui | | \---widget | | WidgetController.class | | | +---core.util | | | module-info.class | | | | | \---util | | +---common | | | StringUtil.class | | | | | \---internal | | Util.class | | | \---main.app | | module-info.class | | | \---com | \---example | Main.class | \---src +---core.ui | | module-info.java | | | \---ui | \---widget | WidgetController.java | +---core.util | | module-info.java | | | \---util | +---common | | StringUtil.java | | | \---internal | Util.java | \---main.app | module-info.java | \---com \---example Main.java
Running
D:\qualified-exports>java --module-path outDir --module main.app/com.example.Main a test string [a, b]
Using --describe-module:
D:\qualified-exports>java --module-path outDir --describe-module core.ui core.ui file:///D:/qualified-exports/outDir/core.ui/ exports ui.widget requires java.base mandated
D:\qualified-exports>java --module-path outDir --describe-module core.util core.util file:///D:/qualified-exports/outDir/core.util/ exports util.common requires java.base mandated qualified exports util.internal to core.ui
D:\qualified-exports>java --module-path outDir --describe-module main.app main.app file:///D:/qualified-exports/outDir/main.app/ requires core.util requires java.base mandated requires core.ui contains com.example
Using jdeps
jdeps can be used to see all package level dependencies:
D:\qualified-exports>jdeps --module-path outDir --module main.app main.app [file:///D:/qualified-exports/outDir/main.app/] requires core.ui requires core.util requires mandated java.base (@9.0.1) main.app -> core.ui main.app -> core.util main.app -> java.base com.example -> java.io java.base com.example -> java.lang java.base com.example -> java.util java.base com.example -> ui.widget core.ui com.example -> util.common core.util
As seen above, the package util.internal is not in com.example dependencies list.
Attempting to access unqualified package
Let's see what will happen if our Main class (in main.app module) attempts to use the unqualified package's classes (in util.internal package):
main.app/com/example/Main.java
package com.example;
import ui.widget.WidgetController;
import util.common.StringUtil;
import util.internal.Util;
import java.util.List;
public class Main {
public static void main(String[] args) {
Util.showString("a test String");
}
}
Compiling:
D:\qualified-exports>javac -d outDir --module-source-path src @javaFiles.txt D:\qualified-exports\src\main.app\com\example\Main.java:5: error: package util.internal is not visible import util.internal.Util; ^ (package util.internal is declared in module core.util, which does not export it to module main.app) 1 error
Example ProjectDependencies and Technologies Used: |