在 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