Java 模块化特性


Java 的不断更新迭代中 JDK 加入了一系列新的特性,而在 JDK 9 中则引入了全新的模块化特性,为工程结构管理提供了基础保障。

我们都知道在 Maven 中可以通过 module 实现模块的管理,JDK 的模块化则有异曲同工之妙。

下面就让我们来看一下模块块的作用以及引入其的目的。

1. 基础介绍

首先让我们看一下在 JDK 8 以及之前 Java 类的存储方式,在 Java 的安装目录下,可以看到所有的 Java 类都被放在 src.zip 文件。

再看一下 JDK 9 之后的安装目录,这里以 JDK 11 为例,你会发现此时 src.zip 文件已然不存在,取而代之是新增了 jmods 目录,目录下存放着一系列 .jmod 文件。

那这些 jmod 文件又是什么?这个正是本文的重点,也是模块化的核心。

JDK 8 之前所有模块都是打包为一个文件,即便是只用到单个类,仍需要全局依赖。同时,在之前的版本中对于作用域的限制过于宽泛,虽然 private 等可以实现访问限制,但反射的存在让这个限制形同虚设。

因此,在 JDK 9 中引入了模块化概念,将 src.zip 拆分为独立的 jmod 文件,每个子模块之间相互独立,使得工程管理更为轻量化。

2. 模块创建

了解了模块化的基本信息,让我们来看一下如何创建一个模块化工程?

实现方式也十分简单,通过 module-info.java 文件进程模块内类的管理,如果你打开上述提到的 jmod 文件,即可发现文件内正包含此文件。

接下来让我们来看一下 module-info.java 文件的内容结构,其定义模板如下:

方法 作用
module 指定当前模块的模块名。
requires 定义此模块所依赖的模块。
exports 定义此模块对外可以访问的包路径。

module <module.name> {

    requires <other.module.name>;

    exports <package.name>;
}

如果一个类所在的包路径没有通过 exports 对外暴露,则其它模块无法使用此类,对于类的访问进一步作出限制,即便通过反射方式调用仍无法访问。

exports 中同时提供了 to 关键字用于配置将包只暴露给指定的模块,实现更精细化的控制,如下述示例中即只将包 xyz.ibudai.test 开放给 my.mod 模块。

module test.mod {

    exports xyz.ibudai.test to my.mod;
}

同时每个模块默认都隐式依赖 java.base 模块,因此 requires java.base; 无需手动声明。

java.base 模块则正是 Java 的核心基础类模块,查看该模块文件即可看到其通过 exports 对外开放了 java.lang 等一系列核心包。

3. 模块编译

那如何将编译自己的 jmod 模块文件呢?其实也十分简单,首先将工程编译为 jar 文件,再由 jmod create 命令即可,格式如下:

jmod create --class-path <target.jar> <target.jmod>


# Example
jmod create --class-path hello.jar hello.jmod

4. 示例分析

下面通过示例演示模块化的效果。

新建工程 module-1,并创建包目录 xyz.ibudai.practice.jdk.pack1 并定义类 Demo1,代码如下:

package xyz.ibudai.pack1;

public class Demo1 {

    public static void sayHello() {
        System.out.println("Demo 1 say hello.");
    }
}

同理,新建包目录 xyz.ibudai.practice.jdk.pack2 并定义两个类 Demo2,这里就不再展示代码内容。

接下来让我们定义对应的 module-info.java 模块文件,注意文件名是固定的。在模块文件中通过 exports 关键字将包 xyz.ibudai.practice.jdk.pack1 对外暴露。

module ibudai.base1 {

    /* 默认依赖 java.base,可省略 */
    requires java.base;

    exports xyz.ibudai.practice.jdk.pack1;
}

新建另一工程 module-2,配置其模块文件 module-info.java 内容如下:

module ibudai.base2 {

    /* 依赖 ibudai.base1 */
    requires ibudai.base1;
}

在此新工程中新建测试类执行调用 Demo1 的静态方法能够正常执行,但如果尝试调用 Demo2 编译器则会提升包未对外导出。

5. 强制访问

作为一身反骨的 Javaer 而言,如果某个包没有对外暴露,那应该如何解决?

Java 仍给我们留了一条后路,在编译时添加下述命令:

--add-exports ibudai.base/xyz.ibudai.practice.jdk.pack2=ALL-UNNAMED

在启动时添加下述命令:

--add-opens ibudai.base/xyz.ibudai.practice.jdk.pack2=ALL-UNNAMED


文章作者: 烽火戏诸诸诸侯
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 烽火戏诸诸诸侯 !
  目录