前言

随着 2017 年 10 月 Java 9 的发布,Java 能够使用模块系统了,但是中文互联网上的资料太少,许多关于 Java 模块系统的文章都只是介绍了模块系统的好处,或者给了一些毫无组织的代码片段,新手在第一次使用模块系统时往往不知道如何下手。

好在 OpenJDK 官方文档给出了一个很详细的新手引导,即使是从没使用过模块系统的人,按照新手引导也能完成自己的第一个 Java 模块。我在这里只是将其翻译成中文(英语水平有限,如有纰漏,欢迎指出),希望更多人能学会如何使用模块系统,加速 Java 类库的模块化进程。

原文地址:Project Jigsaw: Module System Quick-Start Guide

Jigsaw 项目:模块系统新手引导

这篇文档给开始使用模块系统的开发者提供了一个简单示例。

示例中的文件路径使用前斜杠(/),路径分隔符是冒号(:)。使用微软的 Windows 操作系统的开发者在路径中应当使用后斜杠(\),路径分隔符为分号(;)。

Greetings

第一个示例是一个名叫com.greetings的模块,它只是简单的打印一句“Greetings”。这个模块由两个源文件组成:模块声明文件(module-info.java)和主类。

为了方便,模块的源码放在一个和模块名相同的目录中。

src/com.greetings/com/greetings/Main.java
src/com.greetings/module-info.java $ cat src/com.greetings/module-info.java $ 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选项指定主模块,分隔符后面的值是主模块中包含 main 方法的类的类名。

Greetings world

第二个示例更新了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

打包

目前为止,示例中被编译的模块散落在文件系统中。为了更方便的传输与部署,通常会将模块打包成一个modular JAR(模块化的 JAR 包)。一个 modular JAR 相当于一个包含了一个 module-info.class 在它的顶层目录的普通 JAR 包。下面的例子在目录 mlib 中创建了一个org.astro@1.0.jarcom.greetings.jar

$ mkdir mlib

$ jar --create --file=mlib/org.astro@1.0.jar \
--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 org.astro@1.0.jar

在这个例子中,模块org.astro被打包时标识了它的版本号是 1.0 。模块com.greetings被打包时标识了它的主类是com.greetings.Main。我们现在可以不需要指定主类来执行模块com.greetings了:

$ java -p mlib -m com.greetings
Greetings world!

通过使用-p来代替--module-path,命令可以被缩短。

jar 工具有许多新的选项(通过jar -help查看),其中一个就是打印一个 modular JAR 的模块声明。

$ jar --describe-module --file=mlib/org.astro@1.0.jar
org.astro@1.0 jar:file:///d/mlib/org.astro@1.0.jar/!module-info.class
exports org.astro
requires java.base mandated

缺少导入或缺少导出

现在我们来看看对于前面的例子,如果在模块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的模块声明中的导出项(exports):

$ 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

服务

服务能够让服务消费者模块和服务提供者模块解耦。

这个例子有一个服务消费者模块和一个服务提供者模块:
- 模块com.socket导出了网络套接字的 API 。这个 API 在包com.socket中所以这个包被导出。这个 API 是可拔插的,允许不同的实现。服务类型是相同模块中的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)。

该工具目前要求模块路径中的模块都是被用 modular JAR 或者 JMOD 格式打包的。JDK 构建用 JMOD 格式打包标准的、和 JDK 指定的模块。

下面的例子创建了一个包含com.greetings模块以及其传递依赖的运行时镜像:

jlink --module-path $JAVA_HOME/jmods:mlib --add-modules com.greetings --output greetingsapp

--module-path的值是包含了要打包的模块的 路径。在微软的 Windows 操作系统中要将路径分隔符 ':' 替换为 ';' 。

$JAVA_HOME/jmods是包含了java.base.jmod和其他标准 JDK 模块的目录。

模块路径中的mlib目录包含了模块com.greetings的部件(artifact)。

jlink 工具支持许多高级的选项来自定义生成了镜像,用jlink --help查看更多选项。

--patch-module

从 Doug Lea 的 CVS 中查看java.util.concurrent包中的 class 文件的开发者将会习惯使用-Xbootclasspath/p编译源文件和部署这些 class 文件。

-Xbootclasspath/p已经被移除,在一个模块中,用来覆盖 class 文件的模块替换选项是--patch-module。它也可以被用于增加模块的内容。javac也支持在编译代码时加上--patch-module选项,“犹如”某个模块的一部分一样。

这里有一个编译新版本的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 ...

更多信息

from: https://zhuanlan.zhihu.com/p/41129220

Jigsaw 项目:Java 模块系统新手引导的更多相关文章

  1. Java 9终于要包含Jigsaw项目了

    当Jigsaw在Java 9中最终发布时,这个项目的历史已经超过八年了. 转载于:http://www.itxuexiwang.com/a/liunxjishu/2016/0228/180.html? ...

  2. 探索Java9 模块系统和反应流

    Java9 新特性 ,Java 模块化,Java 反应流 Reactive,Jigsaw 模块系统 Java平台模块系统(JPMS)是Java9中的特性,它是Jigsaw项目的产物.简而言之,它以更简 ...

  3. Java秒杀系统实战系列~构建SpringBoot多模块项目

    摘要:本篇博文是“Java秒杀系统实战系列文章”的第二篇,主要分享介绍如何采用IDEA,基于SpringBoot+SpringMVC+Mybatis+分布式中间件构建一个多模块的项目,即“秒杀系统”! ...

  4. OSGi 系列(一)之什么是 OSGi :Java 语言的动态模块系统

    OSGi 系列(一)之什么是 OSGi :Java 语言的动态模块系统 OSGi 的核心:模块化.动态.基于 OSGi 就可以模块化的开发 java 应用,模块化的部署 java 应用,还可以动态管理 ...

  5. OSGi是什么:Java语言的动态模块系统(一)

    OSGi是什么 OSGi亦称做Java语言的动态模块系统,它为模块化应用的开发定义了一个基础架构.OSGi容器已有多家开源实现,比如Knoflerfish.Equinox和Apache的Felix.您 ...

  6. 项目四:Java秒杀系统方案优化-高性能高并发实战

    技术栈 前端:Thymeleaf.Bootstrap.JQuery 后端:SpringBoot.JSR303.MyBatis 中间件:RabbitMQ.Redis.Druid 功能模块 分布式会话,商 ...

  7. Java日志系统框架的设计与实现

    推荐一篇好的文章介绍java日志系统框架的设计的文章:http://soft.chinabyte.com/database/438/11321938.shtml 文章内容总结: 日志系统对跟踪调试.程 ...

  8. ABP(现代ASP.NET样板开发框架)系列之4、ABP模块系统

    点这里进入ABP系列文章总目录 基于DDD的现代ASP.NET开发框架--ABP系列之4.ABP模块系统 ABP是“ASP.NET Boilerplate Project (ASP.NET样板项目)” ...

  9. atitit 商业项目常用模块技术知识点 v3 qc29

    atitit 商业项目常用模块技术知识点 v3 qc29 条码二维码barcodebarcode 条码二维码qrcodeqrcode 条码二维码dm码生成与识别 条码二维码pdf147码 条码二维码z ...

随机推荐

  1. Chocolatey 使用

    最近空了下来不干点什么就感觉脑袋热,可是出过的问题挖过的坑还是要自己去解决. 一直网络不好安装choco一直都是报错,今天又建立了chocolatey 在windows上来用,网络问题解决了,类似于m ...

  2. django中管理程序1

    为了解决启动关闭程序方便,在django中启动结束任务的问题. urls.py ################DJANGO start kill job####################### ...

  3. Linux eject弹出光驱

    Linux eject命令用于退出抽取式设备. 若设备已挂入,则eject会先将该设备卸除再退出. 语法 eject [-dfhnqrstv][-a <开关>][-c <光驱编号&g ...

  4. [FZU2254]英语考试

    在过三个礼拜,YellowStar有一场专业英语考试,因此它必须着手开始复习. 这天,YellowStar准备了n个需要背的单词,每个单词的长度均为m. YellowStar准备采用联想记忆法来背诵这 ...

  5. canvas的用法

    包括: 介绍. 基础入门.(兼容性.获取canvas上下文.绘制直线/描边,填充内容.绘制表格.) canvas是基于状态的绘图. 绘制矩形. 绘制圆形. 绘制文本. 绘制图片. 阴影. 渐变. 绘制 ...

  6. JVM垃圾收集规则和算法

    1.垃圾收集 Garbage Collection 程序计数器.虚拟机栈.本地方法栈这三部分内存随着线程生而生,随着线程灭而自然的回收,他们的大小在编译期间就大致确定了下来,所以对这部分的回收是具备确 ...

  7. docker从零开始 存储(六)存储驱动如何选择

    Docker存储驱动程序 理想情况下,将非常少的数据写入容器的可写层,并使用Docker卷来写入数据.但是,某些工作负载要求您能够写入容器的可写层.这是存储驱动程序的用武之地. Docker使用可插拔 ...

  8. java两种实现二分查找方式

    二分查找法适用于 升序排列的数组,如果你所要操作的数组不是升序排序的,那么请用排序算法,排序一下. 说明:使用二分查找法相比顺序查找  节约了时间的开销,但是增加了空间使用.因为需要动态记录 起始索引 ...

  9. 使用Derby ij客户端工具

    Derby是开源的.嵌入式的Java数据库程序,ij是Derby提供的客户端工具,相当于其他数据库提供的sqlplus工具. ij是纯Java的程序,不用安装,使用起来就像运行普通的Java应用程序一 ...

  10. Android:Activity统一堆栈管理(实现随时finish特定或是所有Activty)

    直接上代码: Activity管理类:AppManager /** * 应用程序Activity管理类:用于Activity管理和应用程序退出 * * @author BiHaidong * @ver ...