Java SE 9 模块化示例
Java SE 9 模块化示例
作者:Grey
原文地址:Java SE 9 模块化示例
说明
Java SE 9
引入了模块系统,模块就是代码和数据的封装体。模块的代码被组织成多个包,每个包中包含Java类和接口;模块的数据则包括资源文件和其他静态信息。
在module-info.java
文件中,我们可以用新的关键词module
来声明一个模块。
Java 9将JDK划分为多个模块以支持各种配置。参考JEP 200: The Modular JDK
可以使用如下命令查询所有的模块(注:JDK 版本大于等于9)
java --list-modules
模块描述符是在名为module-info.java
的文件中定义的模块声明的编译版本。每个模块声明都以关键字module
开头,后跟唯一的模块名称和括在大括号中的模块正文,格式如下
module modulename {
}
模块声明的主体可以是空的,也可以包含各种模块指令(比如:exports
, module
, open
, opens
, provides
, requires
, uses
, with
等)。
下面介绍各种指令的含义
require 指令指定此模块依赖于另一个模块,这种关系称为模块依赖项。每个模块必须显式声明其依赖项。当模块A需要模块B时,模块A称为读取模块B,模块B由模块A读取。要指定对另一个模块的依赖关系,请使用 require,格式如下
requires modulename;
require static 指令,用于指示在编译时需要模块,但在运行时是可选的。这称为可选依赖项。
requires transitive 表示模块的隐含可读性,
如果这样写
module a {
requires b;
}
表示
a模块强制要求存在b模块,这称为可靠配置。
a模块可以读入b模块的文件,这称为可读性。
a模块可以访问b模块的代码,这称为可访问性。
但是如果写成
module a {
requires transitive b;
}
表示:
如果其他模块比如c依赖于a模块,a模块使用requires transitive
引用b模块,那么c模块即便没有直接声明依赖于b模块,它对b模块也是具有可读性的,这个称之为隐含可读性。
即:a requires transitive b
加上c requires a
,则c对b也是可读。如果去掉transitive
,则c对b不可读。
exports 指令指定模块的一个包,其public
类型(及其嵌套的public
类型和protected
类型)应可供所有其他模块中的代码访问。
exports...to 指令使您能够在逗号分隔的列表中精确指定哪些模块或模块的代码可以访问导出的包,这称为限定导出。
uses 指令指定此模块使用的服务,使该模块成为服务使用者。服务是实现接口或扩展uses
指令中指定的抽象类的类的对象。
provides...with 指令指定模块提供服务实现,使模块成为服务提供者。指令的 provide 部分指定列出的接口或抽象类,而 with 部分指令指定实现接口或扩展抽象类的服务提供程序类的名称。
open , opens, 和 opens...to 指令用于了解包中的所有类型以及某个类型的所有成员(甚至是其私有成员),无论您是否允许此功能。在 JDK 9 之前,这一功能是通过反射来实现的。
opens 指令
opens package
允许仅运行时访问包(package)。
opens...to 指令
opens package to comma-separated-list-of-modules
允许特定模块(module)仅运行时访问包(package)。
open 指令
open module modulename {
// module directives
}
允许仅运行时访问模块中的所有包。
环境准备
本地的 JDK 版本应该大于等于 JDK 9。
使用示例
本文的示例均参考于:Project Jigsaw: Module System Quick-Start Guide
注:以下示例均在Windows
下进行,在Mac
或者Linux
下把\\
换成/
。javac
的版本一定要大于等于JDK 9
。
示例1
新建一个example-01
文件夹,作为项目根目录,在项目根目录下创建一个src
文件夹,在这个文件夹下,创建一个文件夹作为module
,文件夹名称为:com.greetings
。在com.greetings
文件夹下新建module-info.java
文件夹,同时,在com.greetings
文件夹中新建包,
即在com.greetings
文件夹下新建com
文件夹,在com
文件夹下新建greetings
文件夹。在greetings
文件夹下新建Main.java
。
目录结构如下
- example-01
- src
- com.greetings
- module-info.java
- com
- greetings
- Main.java
module-info.java
文件中内容如下
module com.greetings {
}
Main.java
中内容如下
package com.greetings;
public class Main {
public static void main(String[] args) {
System.out.println("Greetings!");
}
}
在example-01
目录下新建mods
文件夹,用于存放编译后的目录,在mods
文件夹中新建一个com.greetings
的目录,
目录结构变成了如下
- example-01
- mods
- com.greetings
- src
- com.greetings
- module-info.java
- com
- greetings
- Main.java
在example-01
目录下执行如下编译命令,
javac -d mods\\com.greetings src\\com.greetings\\module-info.java src\\com.greetings\\com\\greetings\\Main.java
编译完毕后,项目目录如下
- example-01
- mods
- com.greetings
- module-info.class
- com
- greetings
- Main.class
- src
- com.greetings
- module-info.java
- com
- greetings
- Main.java
接下来运行java
命令进行执行
java --module-path mods -m com.greetings/com.greetings.Main
控制台输出结果
Greetings!
--module-path
是模块路径,其值是一个或多个包含模块的目录。-m
选项指定的是主模块,斜线后的值是模块中主类的类全名。
示例2
如上例,我们准备好文件夹目录和相关文件
- example-02
- mods
- com.greetings
- org.astro
- src
- com.greetings
- module-info.java
- com
- greetings
- Main.java
- org.astro
- module-info.java
- org
- astro
- World.java
其中src\\org.astro\\module-info.java
内容如下
module org.astro {
exports org.astro;
}
src\\org.astro\\org\\astro\\World.java
内容如下
package org.astro;
public class World {
public static String name() {
return "world";
}
}
src\\com.greetings\\module-info.java
module com.greetings {
requires org.astro;
}
`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());
}
}
然后,在example-02
下分别对org.astro
和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
编译完成后,项目目录如下
// TODO
- example-02
- mods
- com.greetings
- module-info.class
- com
- greetings
- Main.class
- org.astro
- module-info.class
- org
- astro
- World.class
- src
- com.greetings
- module-info.java
- com
- greetings
- Main.java
- org.astro
- module-info.java
- org
- astro
- World.java
然后运行
java --module-path mods -m com.greetings/com.greetings.Main
控制台打印
Greetings world!
示例3
本示例演示如何打包,新建example-03
文件夹,把示例2的所有代码和目录都原封不动拷贝进去。
- example-03
- mods
- com.greetings
- org.astro
- src
- com.greetings
- module-info.java
- com
- greetings
- Main.java
- org.astro
- module-info.java
- org
- astro
- World.java
在example-03
文件夹中新建一个mlib
的文件夹,用于存放打包后的文件。
- example-03
- mlib
- mods
- com.greetings
- org.astro
- src
- com.greetings
- module-info.java
- com
- greetings
- Main.java
- org.astro
- module-info.java
- org
- astro
- World.java
执行打包命令,根据依赖关系,应该先打org.astro
的包。
jar --create --file=mlib\\org.astro@1.0.jar --module-version=1.0 -C mods\\org.astro .
然后再打com.greetings
的包
jar --create --file=mlib\\com.greetings.jar --main-class=com.greetings.Main -C mods\\com.greetings .
打包完成后,目录结构如下
- example-03
- mlib
- com.greetings.jar
- org.astro@1.0.jar
- mods
- com.greetings
- org.astro
- src
- com.greetings
- module-info.java
- com
- greetings
- Main.java
- org.astro
- module-info.java
- org
- astro
- World.java
接下来执行jar
包
java -p mlib -m com.greetings
控制台打印
Greetings world!
jar
工具有许多新的选项(见jar -help),其中之一是打印作为模块化JAR打包的模块的模块声明。
jar --describe-module --file=mlib/org.astro@1.0.jar
控制台输出
org.astro@1.0 jar:file:///C:/workspace/hello-module/example-03/mlib/org.astro@1.0.jar!/module-info.class
exports org.astro
requires java.base mandated
示例4
服务允许服务消费者模块和服务提供者模块之间进行松散耦合。
这个例子有一个服务消费者模块和一个服务提供者模块。
模块com.socket
输出了一个网络套接字的API
。该API
在包com.socket
中,所以这个包被导出了。该API
是可插拔的,允许替代的实现。服务类型是同一模块中的com.socket.spi.NetworkSocketProvider
类,因此包com.socket.spi
也是导出的。
模块org.fastsocket
是一个服务提供者模块。它提供了com.socket.spi.NetworkSocketProvider
的一个实现。它没有输出任何包。
创建example-04
文件夹和相关目录
- example-04
- mods
- com.socket
- com.greetings
- org.fastsocket
- src
- com.socket
- module-info.java
- com
- socket
- spi
- NetworkSocketProvider.java
- NetworkSocket.java
- com.greetings
- module-info.java
- com
- greetings
- Main.java
- org.fastsocket
- module-info.java
- org
- fastsocket
- FastNetworkSocketProvider
- FastNetworkSocket.java
其中src\\com.socket\\module-info.java
module com.socket {
exports com.socket;
exports com.socket.spi;
uses com.socket.spi.NetworkSocketProvider;
}
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();
}
}
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();
}
src\\org.fastsocket\\module-info.java
module org.fastsocket {
requires com.socket;
provides com.socket.spi.NetworkSocketProvider
with org.fastsocket.FastNetworkSocketProvider;
}
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();
}
}
src\\org.fastsocket\\org\\fastsocket\\FastNetworkSocket.java
代码如下
package org.fastsocket;
import com.socket.NetworkSocket;
class FastNetworkSocket extends NetworkSocket {
FastNetworkSocket() { }
public void close() { }
}
src/com.greetings/module-info.java
代码如下
module com.greetings {
requires com.socket;
}
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());
}
}
接下来是编译,根据依赖顺序
先编译com.socket
javac -d mods --module-source-path src src\\com.socket\\module-info.java src\\com.socket\\com\\socket\\NetworkSocket.java src\\com.socket\\com\\socket\\spi\\NetworkSocketProvider.java
再编译org.fastsocket
javac -d mods --module-source-path src src\\org.fastsocket\\module-info.java src\\org.fastsocket\\org\\fastsocket\\FastNetworkSocket.java src\\org.fastsocket\\org\\fastsocket\\FastNetworkSocketProvider.java
最后编译com.greetings
javac -d mods --module-source-path src src\\com.greetings\\module-info.java src\\com.greetings\\com\\greetings\\Main.java
最后执行
java -p mods -m com.greetings/com.greetings.Main
控制台打印出结果
class org.fastsocket.FastNetworkSocket
以上输出确认了服务提供者已经被定位,并且它被用作NetworkSocket
的工厂。
示例5
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
模块的目录。
模块路径上的mlib
目录包含com.greetings
模块的工件。
jlink
工具支持许多高级选项来定制生成的图像,更多选项见jlink --help
示例6
--patch-module
从Doug Lea的CVS中签出java.util.concurrent
类的开发者将习惯于用-Xbootclasspath/p
来编译源文件和部署这些类。
-Xbootclasspath/p
已经被删除,它的模块替代品是选项--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 ...
代码
参考资料
Project Jigsaw: Module System Quick-Start Guide
Java 9 Modules - Developing Java 9 Modules with Apache Maven
Java 9 module example using maven
Java SE 9 模块化示例的更多相关文章
- Java SE 9 多版本兼容 JAR 包示例
Java SE 9 多版本兼容 JAR 包示例 作者:Grey 原文地址:Java SE 9 多版本兼容 JAR 包示例 说明 Java 9 版本中增强了Jar 包多版本字节码文件格式支持,也就是说在 ...
- Java SE 10 Application Class-Data Sharing 示例
Java SE 10 Application Class-Data Sharing 示例 作者:Grey 原文地址:Java SE 10 Application Class-Data Sharing ...
- Java SE 17 新增特性
Java SE 17 新增特性 作者:Grey 原文地址:Java SE 17 新增特性 源码 源仓库: Github:java_new_features 镜像仓库: GitCode:java_new ...
- Java SE 6 新特性: Java DB 和 JDBC 4.0
http://www.ibm.com/developerworks/cn/java/j-lo-jse65/index.html 长久以来,由于大量(甚至几乎所有)的 Java 应用都依赖于数据库,如何 ...
- 通过Java SE 7自带的监控服务(WatchService API)实现类似.NET FileWatcher的功能
Java SE 7 Tutorial中增加了一个监控目录变更情况的示例,用于介绍其新发布的WatchService API. 但对于用惯了.NET FileWatcher的用户而言,如果用于项目我认为 ...
- java se 6在solaris的可观察性特征分析
java平台标准版(java se)6,代码名为"mustang",是最新的java se发行版本(正在开发中).java se 6源码和二进制代码都可以在www.java ...
- [转] Java se 7新特性研究(二)
详见: http://blog.yemou.net/article/query/info/tytfjhfascvhzxcytp82 今天主要研究Java se 7中异常处理的新功能.从今天开始正在 ...
- Java SE之XML<一>XML文档规约
[文档整理系列] Java SE之XML<一>XML文档规约 1.xml文档引用: 1.外部私有DTD: <!DOCTYPE rootNodeName SYSTEM "ur ...
- 在 Java SE 6 中监视和诊断性能问题
Java™ Platform, Standard Edition 6 (Java SE) 专注于提升性能,提供的增强工具可以管理和监视应用程序以及诊断常见的问题.本文将介绍 Java SE 平台中监视 ...
随机推荐
- vue组件传参的方法--bus事件总线
定义:事件总线是实现vue任意组件之前传递参数的一种编程技巧,本质上就是组件的自定义事件.事件总线有很多种写法,具体的思路就是创造一个大家都可以访问到的公共的属性,在这个公共的属性上面可以调用$on, ...
- AT32F415 修改时钟和晶振方法(原创)
1. 简介 我们几乎是国内第一批使用AT32F415芯片的客户,那个时候芯片还没涨价,岁月一切静好.使用AT32F415 做了几个小产品,也在持续出货.后来大家都知道,涨价缺货愈演愈烈.好在我们提前囤 ...
- cuda在ubuntu的安装使用分享
前言 之前给大家分享过opencv在jetson nano 2gb和ubuntu设备中使用并且展示了一些人脸识别等的小demo.但是对于图像处理,使用gpu加速是很常见 .(以下概念介绍内容来自百科和 ...
- 图解MySQL逻辑备份的实现流程
1. 摘要 数据作为一家公司的重要资产,其重要程度不言而喻.数据库为数据提供存取服务,担任着重要的角色,如果因数据误删.服务器故障.病毒入侵等原因导致数据丢失或服务不可用,会对公司造成重大损失,所以数 ...
- 【C++ 字符串题目】 输入三个人名,按字母顺序排序输出
题目来源:https://acm.ujn.edu.cn Problem A: [C++ 字符串] 输入三个人名,按字母顺序排序输出 Time Limit: 1 Sec Memory Limit: 1 ...
- 一文掌握软件安全必备技术 SAST
上一篇文章中,我们讨论了软件供应链的概念并了解到近年来软件供应链安全事件层出不穷.为了保障软件供应链安全,我们需要了解网络安全领域中的一些主要技术.本篇文章将介绍其中一个重要技术--SAST. 当开发 ...
- 自嗨ReentrantReadWriteLock
import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util ...
- springboot执行流程
构造方法初始化,创建一个新的实例,这个应用程序的上下文要从指定的来源加载bean public SpringApplication(ResourceLoaderresourceLoader,Class ...
- RPA SAP财务内部对账机器人
[简介] 本机器人用于使用SAP软件的集团公司间往来对账前台登录SAP账户和密码,需退出PC微信,输入法切换为英文半角状态. [详细流程] 1.清空Excel-VBA管理工具原始数据 2.输入对账时间 ...
- Python使用腾讯云-短信服务发送手机短信
目前[腾讯云短信]为客户提供[国内短信].[国内语音]和[海外短信]三大服务,腾讯云短信SDK支持以下操作: 国内短信 国内短信支持操作: • 指定模板单发短信 • 指定模板群发短信 • 拉取短信回执 ...