All source code is available at github.
Multiple Modules
Module org.astro
- exports package org.astro
[yunpxu@yunpxu-mac JavaModule]$ tree org.astro
org.astro
├── org.astro.iml
└── src
├── module-info.java
└── org
└── astro
└── World.java
module-info.java
[yunpxu@yunpxu-mac JavaModule]$ cat org.astro/src/module-info.java
module org.astro {
exports org.astro;
}
World.java
[yunpxu@yunpxu-mac JavaModule]$ cat org.astro/src/org/astro/World.java
package org.astro;
public class World {
public static String name() {
return "world";
}
}
Compile module
[yunpxu@yunpxu-mac JavaModule]$ mkdir -p out/production/org.astro
[yunpxu@yunpxu-mac JavaModule]$ javac -d out/production/org.astro/ org.astro/src/org/astro/World.java org.astro/src/module-info.java
[yunpxu@yunpxu-mac JavaModule]$ tree out/production/org.astro/
out/production/org.astro/
├── module-info.class
└── org
└── astro
└── World.class
Module com.greetings
- requires module org.astro
module-info.java
[yunpxu@yunpxu-mac JavaModule]$ cat com.greetings/src/module-info.java
module com.greetings {
requires org.astro;
}
Main.java
[yunpxu@yunpxu-mac JavaModule]$ cat com.greetings/src/com/greetings/Main.java
package com.greetings;
import org.astro.World;
public class Main {
public static void main(String[] args) {
System.out.format("Greetings %s!%n", World.name());
}
}
Compile and run module
[yunpxu@yunpxu-mac JavaModule]$ javac --module-path out/production/ -d out/production/com.greetings/ com.greetings/src/com/greetings/Main.java com.greetings/src/module-info.java
[yunpxu@yunpxu-mac JavaModule]$ java --module-path out/production/ -m com.greetings/com.greetings.Main
Greetings world!
Multi-module packaging
Multi-module compilation
[yunpxu@yunpxu-mac JavaModule]$ javac -d out/production/ --module-source-path './*/src' $(find org.astro/ -name '*.java') $(find com.greetings/ -name '*.java')
[yunpxu@yunpxu-mac JavaModule]$ tree out/production/com.greetings/
out/production/com.greetings/
├── com
│ └── greetings
│ └── Main.class
└── module-info.class
[yunpxu@yunpxu-mac JavaModule]$ tree out/production/org.astro/
out/production/org.astro/
├── module-info.class
└── org
└── astro
└── World.class
Pack module org.astro
[yunpxu@yunpxu-mac artifacts]$ cd out/artifacts
[yunpxu@yunpxu-mac artifacts]$ jar cvf org.astro.jar -C ../production/org.astro/ .
added manifest
added module-info: module-info.class
adding: org/(in = 0) (out= 0)(stored 0%)
adding: org/astro/(in = 0) (out= 0)(stored 0%)
adding: org/astro/World.class(in = 276) (out= 212)(deflated 23%)
Pack module com.greetings
The main class of module com.greetings is com.greetings.Main
[yunpxu@yunpxu-mac artifacts]$ jar cvfe com.greetings.jar com.greetings.Main -C /Users/yunpxu/IdeaProjects/JavaModule/out/production/com.greetings/ .
added manifest
added module-info: module-info.class
adding: com/(in = 0) (out= 0)(stored 0%)
adding: com/greetings/(in = 0) (out= 0)(stored 0%)
adding: com/greetings/Main.class(in = 541) (out= 348)(deflated 35%)
Run module com.greetings
[yunpxu@yunpxu-mac artifacts]$ java --module-path . -m com.greetings
Greetings world!
Services
Module com.service
Module com.service defines a service EchoService, and also uses this service(through ServiceLoader).
[yunpxu@yunpxu-mac JavaModule]$ tree com.service
com.service
├── com.service.iml
└── src
├── com
│ └── service
│ └── EchoService.java
└── module-info.java
EchoService interface
[yunpxu@yunpxu-mac JavaModule]$ cat com.service/src/com/service/EchoService.java
package com.service;
import java.util.ServiceLoader;
import java.util.stream.Stream;
public interface EchoService {
void echo(String message);
/**
* Load first service provider, if no service provider found throw RuntimeException.
*
* @return
*/
static EchoService getServiceProvider() {
return ServiceLoader.load(EchoService.class).findFirst().orElseThrow(() -> new RuntimeException("Service Unavailable"));
}
/**
* Load all service providers.
*
* @return
*/
static Stream<EchoService> getServiceProviders() {
return ServiceLoader.load(EchoService.class).stream().map(ServiceLoader.Provider::get);
}
}
module-info
[yunpxu@yunpxu-mac JavaModule]$ cat com.service/src/module-info.java
module com.service {
exports com.service;
uses com.service.EchoService;
}
Module com.service.impl
Module com.service.impl provides two implementations for service EchoService.
[yunpxu@yunpxu-mac JavaModule]$ tree com.service.impl
com.service.impl
├── com.service.impl.iml
└── src
├── com
│ └── service
│ └── impl
│ ├── EchoGreetingServiceImpl.java
│ └── EchoServiceImpl.java
└── module-info.java
EchoServiceImpl
[yunpxu@yunpxu-mac JavaModule]$ cat com.service.impl/src/com/service/impl/EchoServiceImpl.java
package com.service.impl;
import com.service.EchoService;
public class EchoServiceImpl implements EchoService {
@Override
public void echo(String message) {
System.out.println(message);
}
}
EchoGreetingServiceImpl
[yunpxu@yunpxu-mac JavaModule]$ cat com.service.impl/src/com/service/impl/EchoGreetingServiceImpl.java
package com.service.impl;
import com.service.EchoService;
public class EchoGreetingServiceImpl implements EchoService {
@Override
public void echo(String message) {
System.out.println("Greeting " + message);
}
}
module-info
[yunpxu@yunpxu-mac JavaModule]$ cat com.service.impl/src/module-info.java
module com.service.impl {
requires com.service;
provides com.service.EchoService with com.service.impl.EchoServiceImpl,
com.service.impl.EchoGreetingServiceImpl;
}
Module com.service.client
Module com.service.client consumes service EchoService.
[yunpxu@yunpxu-mac JavaModule]$ tree com.service.client
com.service.client
├── com.service.client.iml
└── src
├── com
│ └── service
│ └── client
│ └── Client.java
└── module-info.java
Client
[yunpxu@yunpxu-mac JavaModule]$ cat com.service.client/src/com/service/client/Client.java
package com.service.client;
import com.service.EchoService;
import java.util.stream.Stream;
public class Client {
public static void main(String[] args) {
EchoService echoService = EchoService.getServiceProvider();
echoService.echo("1");
Stream<EchoService> echoServices = EchoService.getServiceProviders();
echoServices.forEach(e -> e.echo("2"));
}
}
module-info
[yunpxu@yunpxu-mac JavaModule]$ cat com.service.client/src/module-info.java
module com.service.client {
requires com.service;
}
Compile Services
Compile module com.service, com.service.impl and com.service.client
[yunpxu@yunpxu-mac JavaModule]$ javac -d out/production/ --module-source-path './*/src' $(find com.service* -name '*.java')
Run Client
Run module com.service.client
[yunpxu@yunpxu-mac JavaModule]$ java --module-path out/production/ -m com.service.client/com.service.client.Client
1
2
Greeting 2
The Linker
You can use the jlink tool to assemble and optimize a set of modules and their dependencies into a custom runtime image.
Create Runtime Image
Create a runtime image with module com.service.client and com.service.impl and their dependencies(com.service and java.base).
[yunpxu@yunpxu-mac JavaModule]$ jlink --module-path out/production/ --add-modules com.service.client,com.service.impl --output out/artifacts/EchoApp
Runtime Image Hierarchy
[yunpxu@yunpxu-mac JavaModule]$ tree out/artifacts/EchoApp/
out/artifacts/EchoApp/
├── bin
│ ├── java
│ └── keytool
├── conf
│ ├── net.properties
│ └── security
│ ├── java.policy
│ ├── java.security
│ └── policy
│ ├── README.txt
│ ├── limited
│ │ ├── default_US_export.policy
│ │ ├── default_local.policy
│ │ └── exempt_local.policy
│ └── unlimited
│ ├── default_US_export.policy
│ └── default_local.policy
├── include
│ ├── classfile_constants.h
│ ├── darwin
│ │ └── jni_md.h
│ ├── jni.h
│ ├── jvmti.h
│ └── jvmticmlr.h
├── legal
│ └── java.base
│ ├── ADDITIONAL_LICENSE_INFO
│ ├── ASSEMBLY_EXCEPTION
│ ├── LICENSE
│ ├── aes.md
│ ├── asm.md
│ ├── c-libutl.md
│ ├── cldr.md
│ ├── icu.md
│ ├── public_suffix.md
│ └── unicode.md
├── lib
│ ├── classlist
│ ├── jli
│ │ └── libjli.dylib
│ ├── jrt-fs.jar
│ ├── jspawnhelper
│ ├── jvm.cfg
│ ├── libjava.dylib
│ ├── libjimage.dylib
│ ├── libjsig.dylib
│ ├── libnet.dylib
│ ├── libnio.dylib
│ ├── libosxsecurity.dylib
│ ├── libverify.dylib
│ ├── libzip.dylib
│ ├── modules
│ ├── security
│ │ ├── blacklisted.certs
│ │ ├── cacerts
│ │ ├── default.policy
│ │ └── public_suffix_list.dat
│ ├── server
│ │ ├── Xusage.txt
│ │ ├── libjsig.dylib
│ │ └── libjvm.dylib
│ └── tzdb.dat
└── release
Run Image
EchoApp is now a standalone app and can be ran on machines without java installed.
[yunpxu@yunpxu-mac JavaModule]$ ./out/artifacts/EchoApp/bin/java --list-modules
com.service
com.service.client
com.service.impl
java.base@11.0.1
[yunpxu@yunpxu-mac JavaModule]$ ./out/artifacts/EchoApp/bin/java --module com.service.client/com.service.client.Client
1
2
Greeting 2