1
我的博客 转载请注明原创出处。

Java 9开始,在Java的世界里多了一个叫模块(JSR376)的特性。模块系统的前身是Jigsaw项目。最初,该项目仅仅是为JDK设计、实现一个模块系统。后来项目组也希望它能为开发者所用——虽然,一开始它并不是Java SE平台规范的组成部分。随着项目的不断深入,Java平台对标准模块系统的呼求也日益增长,JCP批准该项目升级为JavaSE平台的一部分,也能服务于Java MEJava EE平台的需求。

官方对Java平台模块系统是这样描述的:

一种新的Java编程组件,即模块。它是自描述的代码与数据的集合,有以下特性:

  • 引入了一个新的可选阶段——链接时,它介于编译时和运行时之间,在此期间可以将一组模块组装并优化为定制的运行时镜像。
  • 为工具javacjlink增加了一些选项,以及在Java中,你可以指定模块路径,这些路径定位模块的定义。
  • 引入模块化JAR文件,该文件是一个JAR文件,其根目录中包含module-info.class文件。
  • 引入JMOD格式,它是一种类似于JAR的打包格式,但它可以包含原生代码和配置文件。

JDK本身已经模块化。有以下改变:

  • 使你能够将JDK的模块组合成各种配置,包括:

    • JREJDK一样的配置。
    • Java SE 8中定义的每个压缩配置文件的内容大致相同。
    • 自定义的配置,仅包含一组指定的模块及其所需的模块。
  • 重构JDKJRE运行时镜像以适应模块并提高性能、安全性和可维护性。
  • 定义了一个新的URI方案,用于命名存储在运行时映像中的模块,类和资源,而不会泄露映像的内部结构或格式。
  • 删除认可的标准覆盖机制和扩展机制。
  • Java运行时镜像中删除rt.jartools.jar
  • 默认情况下,大多数JDK的内部API都不可访问,但在所有或大部分功能都支持替换之前,可以访问一些关键的、广泛使用的内部API

运行jdeps -jdkinternals命令以确定你的的代码是否使用了内部JDK API

兼容性

先来说说模块系统的兼容性。Java一直是比较保守的,体现在更新上就是良好的兼容性。虽然看了官方对Java平台模块系统的描述好像改动非常大,但是你的旧项目即使不模块化也是能在新JDK上运行的。后面会讲到没有模块化的类包是如何与模块交互的。

为什么要模块化

既然不模块化也能好好的运行,那么为什么要这么大费周章的折腾代码呢?

第一点,Java 9前的Java程序,即使是一个简单的输出Hello World的程序,也必须将整个JDKJRE运行时镜像打包进去才能运行,这时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.sejava.se.eejava.se模块收集Java SE的不与Java EE重叠的部分。java.se.ee模块收集组成Java SE的所有模块,包括与Java EE重叠的模块。

待续


Yuicon
495 声望24 粉丝

« 上一篇
一致性Hash