Jigsaw 项目:Java 模块系统新手引导
前言
随着 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.jar
和com.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 模块系统新手引导的更多相关文章
- Java 9终于要包含Jigsaw项目了
当Jigsaw在Java 9中最终发布时,这个项目的历史已经超过八年了. 转载于:http://www.itxuexiwang.com/a/liunxjishu/2016/0228/180.html? ...
- 探索Java9 模块系统和反应流
Java9 新特性 ,Java 模块化,Java 反应流 Reactive,Jigsaw 模块系统 Java平台模块系统(JPMS)是Java9中的特性,它是Jigsaw项目的产物.简而言之,它以更简 ...
- Java秒杀系统实战系列~构建SpringBoot多模块项目
摘要:本篇博文是“Java秒杀系统实战系列文章”的第二篇,主要分享介绍如何采用IDEA,基于SpringBoot+SpringMVC+Mybatis+分布式中间件构建一个多模块的项目,即“秒杀系统”! ...
- OSGi 系列(一)之什么是 OSGi :Java 语言的动态模块系统
OSGi 系列(一)之什么是 OSGi :Java 语言的动态模块系统 OSGi 的核心:模块化.动态.基于 OSGi 就可以模块化的开发 java 应用,模块化的部署 java 应用,还可以动态管理 ...
- OSGi是什么:Java语言的动态模块系统(一)
OSGi是什么 OSGi亦称做Java语言的动态模块系统,它为模块化应用的开发定义了一个基础架构.OSGi容器已有多家开源实现,比如Knoflerfish.Equinox和Apache的Felix.您 ...
- 项目四:Java秒杀系统方案优化-高性能高并发实战
技术栈 前端:Thymeleaf.Bootstrap.JQuery 后端:SpringBoot.JSR303.MyBatis 中间件:RabbitMQ.Redis.Druid 功能模块 分布式会话,商 ...
- Java日志系统框架的设计与实现
推荐一篇好的文章介绍java日志系统框架的设计的文章:http://soft.chinabyte.com/database/438/11321938.shtml 文章内容总结: 日志系统对跟踪调试.程 ...
- ABP(现代ASP.NET样板开发框架)系列之4、ABP模块系统
点这里进入ABP系列文章总目录 基于DDD的现代ASP.NET开发框架--ABP系列之4.ABP模块系统 ABP是“ASP.NET Boilerplate Project (ASP.NET样板项目)” ...
- atitit 商业项目常用模块技术知识点 v3 qc29
atitit 商业项目常用模块技术知识点 v3 qc29 条码二维码barcodebarcode 条码二维码qrcodeqrcode 条码二维码dm码生成与识别 条码二维码pdf147码 条码二维码z ...
随机推荐
- 2017年上海金马五校程序设计竞赛:Problem G : One for You (博弈)
Description Given a m × n chessboard, a stone is put on the top-left corner (1, 1). Kevin and Bob ta ...
- LeetCode 4 Median of Two Sorted Array
There are two sorted arrays A and B of size m and n respectively. Find the median of the two sorted ...
- linux驱动学习(二) Makefile高级【转】
转自:http://blog.csdn.net/ghostyu/article/details/6866863 版权声明:本文为博主原创文章,未经博主允许不得转载. 在我前一篇写的[ linux驱动学 ...
- HTTP===返回结果的HTTP状态码
HTTP 状态码负责表示客户端 HTTP 请求的返回结果.标记服务器端的处理是否正常.通知出现的错误等工作. 1.状态码告知从服务器端返回的请求结果 状态码的职责是当客户端向服务器端发送请求时,描述返 ...
- JS实现上下左右对称的九九乘法表
JS实现上下左右对称的九九乘法表 css样式 <style> table{ table-layout:fixed; border-collapse:collapse; } td{ padd ...
- golang之http
go获取html页面 package main import ( "fmt" "io/ioutil" "net/http" "ne ...
- 解决vim没有颜色的办法
首先打开vim,输入命令 scriptnames看看vim加载了哪些脚本. :scriptnames 输出入下 : /home/users/xxx/.vimrc : /home/users/xxx/t ...
- MATLAB的简单动画制作
这里介绍两种类型的动画实现,一种使用getframe和movie命令实现帧动画,另一种使用comet(comet3)命令实现画图过程的动画. ①getframe和movie命令实现帧动画 例如,创建一 ...
- 使用Derby ij客户端工具
Derby是开源的.嵌入式的Java数据库程序,ij是Derby提供的客户端工具,相当于其他数据库提供的sqlplus工具. ij是纯Java的程序,不用安装,使用起来就像运行普通的Java应用程序一 ...
- 2017 ACM-ICPC 亚洲区(青岛赛区)网络赛 1009
#include<cmath> #include<set> #include<list> #include<deque> #include<map ...