模块化系统 quick start
本文档提供了一些简单的示例,以帮助开发人员开始使用模块。
示例中的文件路径使用正斜杠,路径分隔符是冒号。 Microsoft Windows上的开发人员应使用带有反斜杠的文件路径和分号作为路径分隔符.
Greetings
第一个例子是一个名为com.greetings的模块,它只是打印“Greetings!”。 该模块由两个源文件组成:模块声明(module-info.java)和main class。
按照惯例,模块的源代码位于模块名称的目录中.
src/com.greetings/com/greetings/Main.java
src/com.greetings/module-info.java
$ cat src/com.greetings/module-info.java
module com.greetings { }
$ cat src/com.greetings/com/greetings/Main.java
package com.greetings;
public class Main {
public static void main(String[] args) {
System.out.println("Greetings!");
}
}
使用以下命令将源代码编译到mods/com.greetings目录:
$ mkdir -p mods/com.greetings
$ javac -d mods/com.greetings \
src/com.greetings/module-info.java \
src/com.greetings/com/greetings/Main.java
现在我们使用以下命令运行示例:
$ java --module-path mods -m com.greetings/com.greetings.Main
--module-path是模块路径,其值是包含模块的一个或多个目录。 -m选项指定主模块,斜杠后面的值是模块中主类的类名
Greetings Word
第二个示例更新模块声明以声明对模块org.astro的依赖性。 模块org.astro导出API包org.astro。
src/org.astro/module-info.java
src/org.astro/org/astro/World.java
src/com.greetings/com/greetings/Main.java
src/com.greetings/module-info.java
$ cat src/org.astro/module-info.java
module org.astro {
exports org.astro;
}
$ cat src/org.astro/org/astro/World.java
package org.astro;
public class World {
public static String name() {
return "world";
}
}
$ cat src/com.greetings/module-info.java
module com.greetings {
requires org.astro;
}
$ cat src/com.greetings/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());
}
}
这些模块一次编译一个。 编译模块com.greetings的javac命令指定模块路径,以便可以解析对模块org.astro的引用及其导出包中的类型。
$ mkdir -p mods/org.astro mods/com.greetings
$ javac -d mods/org.astro \
src/org.astro/module-info.java src/org.astro/org/astro/World.java
$ javac --module-path mods -d mods/com.greetings \
src/com.greetings/module-info.java src/com.greetings/com/greetings/Main.java
该示例的运行方式与第一个示例完全相同:
$ java --module-path mods -m com.greetings/com.greetings.Main
Greetings world!
多模块编译
在上一个示例中,模块com.greetings和模块org.astro分别编译。 也可以使用一个javac命令编译多个模块:
$ mkdir mods
$ javac -d mods --module-source-path src $(find src -name "*.java")
$ find mods -type f
mods/com.greetings/com/greetings/Main.class
mods/com.greetings/module-info.class
mods/org.astro/module-info.class
mods/org.astro/org/astro/World.class
打包
在到目前为止的示例中,编译模块的内容在文件系统上展开。 出于运输和部署的目的,将模块打包为模块化JAR通常更方便。 模块化JAR是一个常规JAR文件,在其顶级目录中有一个module-info.class。 以下示例在目录mlib中创建[email protected]和com.greetings.jar。
$ mkdir mlib
$ jar --create --file=mlib/[email protected] \
--module-version=1.0 -C mods/org.astro .
$ jar --create --file=mlib/com.greetings.jar \
--main-class=com.greetings.Main -C mods/com.greetings .
$ ls mlib
com.greetings.jar [email protected]
在此示例中,打包模块org.astro以指示其版本为1.0。 模块com.greetings已打包,表明其Main class是com.greetings.Main。 我们现在可以执行模块com.greetings而无需指定其Main class:
$ java -p mlib -m com.greetings
Greetings world!
使用-p作为--module-path的替代方法也可以缩短命令行。
jar工具有许多新选项(参见jar -help),其中之一是打印打包为模块化JAR的模块的模块声明。
$ jar --describe-module --file=mlib/[email protected]
[email protected] jar:file:///d/mlib/[email protected]/!module-info.class
exports org.astro
requires java.base mandated
缺少requires或exports
现在让我们看看当我们错误地省略com.greetings模块声明中的requires时,上一个示例会发生什么:
$ cat src/com.greetings/module-info.java
module com.greetings {
// requires org.astro;
}
$ javac --module-path mods -d mods/com.greetings \
src/com.greetings/module-info.java src/com.greetings/com/greetings/Main.java
src/com.greetings/com/greetings/Main.java:2: error: package org.astro is not visible
import org.astro.World;
(package org.astro is declared in module org.astro, but module com.greetings does not read it) 1 error
我们现在修复了这个模块声明,但引入了一个不同的错误,这次我们省略了org.astro模块声明的导出:
$ cat src/com.greetings/module-info.java
module com.greetings {
requires org.astro;
}
$ cat src/org.astro/module-info.java
module org.astro {
// exports org.astro;
}
$ javac --module-path mods -d mods/com.greetings \
src/com.greetings/module-info.java src/com.greetings/com/greetings/Main.java
$ javac --module-path mods -d mods/com.greetings \
src/com.greetings/module-info.java src/com.greetings/com/greetings/Main.java
src/com.greetings/com/greetings/Main.java:2: error: package org.astro is not visible
import org.astro.World;
^
(package org.astro is declared in module org.astro, which does not export it)
1 error
Services
Services允许服务使用者模块和服务提供者模块之间的松耦合。
此示例具有服务使用者模块和服务提供者模块:
- com.socket模块导出网络套接字的API。 API在com.socket包中,因此export此包。 API是可插拔的,以允许替代实现。 service type是同一模块中的com.socket.spi.NetworkSocketProvider,因此也会导出包com.socket.spi。
- org.fastsocket模块是一个服务提供者模块。 它提供了com.socket.spi.NetworkSocketProvider的实现。 它不会导出任何包。
以下是com.socket模块的源代码。
$ cat src/com.socket/module-info.java
module com.socket {
exports com.socket;
exports com.socket.spi;
uses com.socket.spi.NetworkSocketProvider;
}
$ cat src/com.socket/com/socket/NetworkSocket.java
package com.socket;
import java.io.Closeable;
import java.util.Iterator;
import java.util.ServiceLoader;
import com.socket.spi.NetworkSocketProvider;
public abstract class NetworkSocket implements Closeable {
protected NetworkSocket() { }
public static NetworkSocket open() {
ServiceLoader<NetworkSocketProvider> sl
= ServiceLoader.load(NetworkSocketProvider.class);
Iterator<NetworkSocketProvider> iter = sl.iterator();
if (!iter.hasNext())
throw new RuntimeException("No service providers found!");
NetworkSocketProvider provider = iter.next();
return provider.openNetworkSocket();
}
}
$ cat src/com.socket/com/socket/spi/NetworkSocketProvider.java
package com.socket.spi;
import com.socket.NetworkSocket;
public abstract class NetworkSocketProvider {
protected NetworkSocketProvider() { }
public abstract NetworkSocket openNetworkSocket();
}
以下是模块org.fastsocket的源代码。
$ cat src/org.fastsocket/module-info.java
module org.fastsocket {
requires com.socket;
provides com.socket.spi.NetworkSocketProvider
with org.fastsocket.FastNetworkSocketProvider;
}
$ cat src/org.fastsocket/org/fastsocket/FastNetworkSocketProvider.java
package org.fastsocket;
import com.socket.NetworkSocket;
import com.socket.spi.NetworkSocketProvider;
public class FastNetworkSocketProvider extends NetworkSocketProvider {
public FastNetworkSocketProvider() { }
@Override
public NetworkSocket openNetworkSocket() {
return new FastNetworkSocket();
}
}
$ cat src/org.fastsocket/org/fastsocket/FastNetworkSocket.java
package org.fastsocket;
import com.socket.NetworkSocket;
class FastNetworkSocket extends NetworkSocket {
FastNetworkSocket() { }
public void close() { }
}
为简单起见,我们将两个模块一起编译。 在实践中,服务消费者模块和服务提供者模块几乎总是分开编译。
$ mkdir mods
$ javac -d mods --module-source-path src $(find src -name "*.java")
最后,我们修改我们的模块com.greetings以使用API。
$ cat src/com.greetings/module-info.java
module com.greetings {
requires com.socket;
}
$ cat src/com.greetings/com/greetings/Main.java
package com.greetings;
import com.socket.NetworkSocket;
public class Main {
public static void main(String[] args) {
NetworkSocket s = NetworkSocket.open();
System.out.println(s.getClass());
}
}
$ javac -d mods/com.greetings/ -p mods $(find src/com.greetings/ -name "*.java")
最后我们运行它:
$ java -p mods -m com.greetings/com.greetings.Main
class org.fastsocket.FastNetworkSocket
输出确认已找到服务提供者,并且它已用作NetworkSocket的工厂。
连接
jlink是链接器工具,可用于链接一组模块及其传递依赖性,以创建自定义模块化运行时镜像(请参阅JEP 220)。 该工具目前要求模块路径上的模块以模块化JAR或JMOD格式打包。 JDK构建以JMOD格式打包标准和JDK特定模块。
以下示例创建一个运行时镜像,其中包含模块com.greetings及其传递依赖项:
jlink --module-path $JAVA_HOME/jmods:mlib --add-modules com.greetings --output greetingsapp
-module-path的值是包含打包模块的目录的PATH。 在Microsoft Windows上将路径分隔符':'替换为';' 。
$JAVA_HOME/jmods是包含java.base.jmod和其他标准和JDK模块的目录。
module path上的mlib目录包含模块com.greetings的工件。
jlink工具支持许多高级选项来自定义生成的镜像,有关更多选项,请参阅jlink --help
--patch-module
从Doug Lea的CVS中检出java.util.concurrent类的开发人员将用于编译源文件并使用-Xbootclasspath/p部署这些类。
-Xbootclasspath/p已被删除,其模块替换是选项--patch-module来覆盖模块中的类。 它还可以用于增加模块的内容。 javac也支持--patch-module选项来编译代码“as if”模块的一部分。
这是一个编译新版本的java.util.concurrent.ConcurrentHashMap并在运行时使用它的示例:
javac --patch-module java.base=src -d mypatches/java.base \
src/java.base/java/util/concurrent/ConcurrentHashMap.java
java --patch-module java.base=mypatches/java.base ...