Java SE 9 模块化

Java SE 9 引入了模块系统,模块就是代码和数据的封装体。模块的代码被组织成多个包,每个包中包含Java类和接口;模块的数据则包括资源文件和其他静态信息。

module-info.java 文件中,我们可以用新的关键词 module 来声明一个模块。

可以使用如下命令查询所有的模块(注: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.astrocom.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

编译完成后,项目目录如下

- 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.classexports org.astrorequires 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 sl                = ServiceLoader.load(NetworkSocketProvider.class);            Iterator 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 )。

该工具目前要求模块路径上的模块以模块化的 JARJMOD 格式打包。 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 ...

记得点赞关注+转发!!!

Java   SE
发表评论
留言与评论(共有 0 条评论) “”
   
验证码:

相关文章

推荐文章