我的博客 转载请注明原创出处。
序
从Java 9
开始,在Java
的世界里多了一个叫模块(JSR376)的特性。模块系统的前身是Jigsaw
项目。最初,该项目仅仅是为JDK
设计、实现一个模块系统。后来项目组也希望它能为开发者所用——虽然,一开始它并不是Java SE
平台规范的组成部分。随着项目的不断深入,Java
平台对标准模块系统的呼求也日益增长,JCP
批准该项目升级为JavaSE
平台的一部分,也能服务于Java ME
和Java EE
平台的需求。
官方对Java
平台模块系统是这样描述的:
一种新的Java
编程组件,即模块。它是自描述的代码与数据的集合,有以下特性:
- 引入了一个新的可选阶段——链接时,它介于编译时和运行时之间,在此期间可以将一组模块组装并优化为定制的运行时镜像。
- 为工具
javac
、jlink
增加了一些选项,以及在Java
中,你可以指定模块路径,这些路径定位模块的定义。 - 引入模块化
JAR
文件,该文件是一个JAR
文件,其根目录中包含module-info.class
文件。 - 引入
JMOD
格式,它是一种类似于JAR
的打包格式,但它可以包含原生代码和配置文件。
JDK本身已经模块化。有以下改变:
-
使你能够将
JDK
的模块组合成各种配置,包括:- 与
JRE
和JDK
一样的配置。 - 与
Java SE 8
中定义的每个压缩配置文件的内容大致相同。 - 自定义的配置,仅包含一组指定的模块及其所需的模块。
- 与
- 重构
JDK
和JRE
运行时镜像以适应模块并提高性能、安全性和可维护性。 - 定义了一个新的
URI
方案,用于命名存储在运行时映像中的模块,类和资源,而不会泄露映像的内部结构或格式。 - 删除认可的标准覆盖机制和扩展机制。
- 从
Java
运行时镜像中删除rt.jar
和tools.jar
。 - 默认情况下,大多数
JDK
的内部API
都不可访问,但在所有或大部分功能都支持替换之前,可以访问一些关键的、广泛使用的内部API
。
运行jdeps -jdkinternals
命令以确定你的的代码是否使用了内部JDK API
。
兼容性
先来说说模块系统的兼容性。Java一直是比较保守的,体现在更新上就是良好的兼容性。虽然看了官方对Java
平台模块系统的描述好像改动非常大,但是你的旧项目即使不模块化也是能在新JDK
上运行的。后面会讲到没有模块化的类包是如何与模块交互的。
为什么要模块化
既然不模块化也能好好的运行,那么为什么要这么大费周章的折腾代码呢?
第一点,Java 9
前的Java
程序,即使是一个简单的输出Hello World
的程序,也必须将整个JDK
、JRE
运行时镜像打包进去才能运行,这时Java
引以为傲的数量繁多的类库反而成了累赘。比如开源的优秀编程库——Guava
里有很多很实用的工具类,有时我们可能只用到了其中一个类而已,却不得不将整个Guava
类库打包进我们的项目。
第二点,没法定义类是否能被其他包里的类引用到。比如我们编写了一个工具类,如果希望这个类只能被某些包里的其他类引用到,不暴露给其他包,Java 9
前的Java
程序是做不到这一点的。
如何模块化
模块化一个项目只要在项目的根目录创建一个module-info.class
文件就可以了。
如图所示,我们创建了一个名为module
的模块并用关键字exports
导出了test
包。如果有其他模块导入module
模块就可以引用到Main
类了,值得注意的是和test
同级的包和test
内部的包因为没有被导出,都是不能被引用到的。
而引用一个模块则是用关键字requires
:
然后就可以使用test
包内的类了:
import test.Main;
/**
* @author Yuicon
*/
public class Test {
public static void main(String[] args) {
Main main = new Main();
System.out.println(main);
}
}
不过在导入前还需要在模块依赖里添加要导入的模块:
java.base
模块是默认导入的,里面有我们常用的类库:
模块的强封装性
我们知道模块化后就能控制那些包可以被引用到了。不过不止如此,一个模块的类是不能访问到其他模块里的类的私有属性的。听起来好像是理所当然的,这是因为原先我们是可以用反射来访问到私有属性的。模块化后就算反射也不能访问到了,算是加强了安全性。
不过这样的话,那些依赖反射来获取私有属性的框架和库就倒霉了。为了兼容这些框架和库,我们可以在模块定义里加一个关键字open
:
module-info.class
open module module {
exports test;
}
这样我们就声明了一个开放的模块,在模块的所有软件包上授予深入的反射访问权限(访问公共和私有API)。
模块语句
在模块声明文件里一共有五种模块语句,分别是:
- 导出语句(exports statement)
- 开放语句(opens statement)
- 需要语句(requires statement)
- 使用语句(uses statement)
- 提供语句(provides statement)
package test.driver;
/**
* @author Yuicon
*/
public interface Driver {
int getCode();
}
package test;
import test.driver.Driver;
/**
* @author Yuicon
*/
public class DriverImpl implements Driver {
@Override
public int getCode() {
return 10086;
}
}
module module {
exports test.driver; // 导出包
provides Driver
with DriverImpl; // 为接口Driver提供实现
}
module queue {
requires module; // 导入包
opens test; // 开放包的反射权限
uses Driver; // 声明使用接口
}
package main;
import test.driver.Driver;
import java.util.ServiceLoader;
/**
* @author Yuicon
*/
public class Main {
public static void main(String[] args) {
// 获取实现
ServiceLoader<Driver> serviceLoader = ServiceLoader.load(Driver.class);
serviceLoader.findFirst().ifPresent(driver -> System.out.println(driver.getCode()));
}
}
输出:
10086
Process finished with exit code 0
聚合模块
你可以创建一个不包含任何代码的模块,它收集并重新导出其他模块的内容,这样的模块称为聚合模块。假设有几个模块依赖于五个模块,你可以为这五个模块创建一个聚合模块。现在,你的模块只能依赖于一个模块——聚合模块。
为了方便,Java 9
包含几个聚合模块,如java.se
和java.se.ee
。java.se
模块收集Java SE
的不与Java EE
重叠的部分。java.se.ee
模块收集组成Java SE
的所有模块,包括与Java EE
重叠的模块。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。