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 中创建了一个[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被打包时标识了它的主类是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/[email protected]
[email protected] jar:file:///d/mlib/[email protected]/!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

原文地址:https://www.cnblogs.com/GarfieldEr007/p/9940722.html

时间: 2024-11-08 06:58:24

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

Java 9终于要包含Jigsaw项目了

当Jigsaw在Java 9中最终发布时,这个项目的历史已经超过八年了. 转载于:http://www.itxuexiwang.com/a/liunxjishu/2016/0228/180.html?1456925937 在最初的几年中,它必须要与另外两个类似的Java规范请求(Java Specification Request )进行竞争,这两个规范名为JSR 277 Java模块系统(Java Module System)以及JSR 294 增强的模块化支持(Improved Modula

Jigsaw项目会解决Java的JAR地狱问题么?

本文来源于我在InfoQ中文站翻译的文章,原文地址是:http://www.infoq.com/cn/news/2015/12/jigsaw-jar-hell Nicolai Parlog是一位热情的软件工程师,数字版权与开源软件的狂热拥护者:他对AssertJ.ControlsFX.FindBugs及Property Alliance等项目都做出过重要的贡献.近日,Parlog就Jigsaw项目撰写了一篇文章,谈到了Jigsaw项目的一些不足以及改进之处.Jigsaw项目有着雄心勃勃的宏伟目标

Java 9 正式发布,终落地 Jigsaw 项目

9 月 21 日,大家期待已久的 Java 9 终于正式发布了,本文与大家一起快速回顾一下 IntelliJ IDEA 中 Java 9 的支持,并了解 IntelliJ IDEA 2017.3 中针对 Java 9 的推出的新功能. Jigsaw 和 JPMS 提及 Java 9,最大的功能莫过于 Jigsaw 项目下的核心 Java 平台模块化系统(JPMS,Java Platform Module System) . Jigsaw 本身是一项很有野心的项目,它的目标是改进 Java SE

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

OSGi是什么 OSGi亦称做Java语言的动态模块系统,它为模块化应用的开发定义了一个基础架构.OSGi容器已有多家开源实现,比如Knoflerfish.Equinox和Apache的Felix.您可以通过这些容器,把您的应用程序劈分为多个模块单元,这样,您就可以更容易地管理这些模块单元之间的交叉依赖关系. OSGi规范和Servlet规范及EJB规范类似,该规范定义了两种对象,一是容器对外提供的服务对象,另一个是容器和您的应用程序之间必须遵守的契约,其中,服务对象是容器要实现的.您如果想要在

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

推荐一篇好的文章介绍java日志系统框架的设计的文章:http://soft.chinabyte.com/database/438/11321938.shtml 文章内容总结: 日志系统对跟踪调试.程序状态记录.数据恢复等功能有重要作用 日志系统一般作为服务进程或者系统调用存在,我们一般程序中使用系统调用 常用日志系统包括log4j的简单介绍 日志系统的系统架构 日志系统的信息分级 日志输出的设计 下面是全文的引用: 在Java领域,存在大量的日志组件,open-open收录了21个日志组件.日

基于ligerUI+jbpm5自定义表单+Node.js的J2ee大型金融项目《财务预算系统》开发全程实录

基于ligerUI+jbpm5自定义表单+Node.js的J2ee大型金融项目<财务预算系统>开发全程实录 对这个课程感兴趣的可以加我雾非雾QQ2748165793, 讲师介绍: 3年对日外包电信行业软件专家,六年项目开发经验,二年高端培训机构教学管理经验,惠普全球研发中心骨干.现任北风金牌合作讲师. 项目经验丰富: 日本电信NTT DATA SYSTEM 日本航空ANA SYSTEM 日本TAHOO BB 在线信息平台 惠普DDC--InternalManagement SYSTEM(内部管

如何正确地实现Java模块与inter-module Maven构建测试依赖关系

我有一个多模块使用Maven和Java项目. 我现在想迁移到Java 9/10/11和实现模块(如JSR 376:Java平台模块系统). 随着项目已经由Maven模块,和依赖直,为项目创建模块描述符很直接. 现在每个Maven模块有自己的模块描述符(module-info.java),在src/main/java文件夹中. 没有测试类的模块描述符. 然而,我偶然发现了我一直未能解决的问题,并没有找到任何描述如何解决:我怎么能有inter-module吗测试与Maven和Java模块依赖关系?

Java秒杀系统实战系列~整合Shiro实现用户登录认证

摘要: 本篇博文是"Java秒杀系统实战系列文章"的第五篇,在本篇博文中,我们将整合权限认证-授权框架Shiro,实现用户的登陆认证功能,主要用于:要求用户在抢购商品或者秒杀商品时,限制用户进行登陆!并对于特定的url(比如抢购请求对应的url)进行过滤(即当用户访问指定的url时,需要要求用户进行登陆). 内容: 对于Shiro,相信各位小伙伴应该听说过,甚至应该也使用过!简单而言,它是一个很好用的用户身份认证.权限授权框架,可以实现用户登录认证,权限.资源授权.会话管理等功能,在本

java电话计费系统

每天记录学习,每天会有好心情.*^_^* 最近了解了基于web的java电话计费系统项目,在这个平台记录一下这个基于web的java电话计费系统项目,方便以后再次使用或学习的时候能够及时的翻阅.在完成基于web的java电话计费系统项目的时候,考虑了很多框架.最终决定选用SSM(MYECLIPSE),该框架具有极强的移植性,多平台性,便于操作性等优点.此框架能在MYECLIPSE开发工具中完美的编写和运行,基于web的java电话计费系统为一个 后台项目.采用当前非常流行的B/S体系结构,以JA