Skip to content

Java基础-模块系统笔记(1) #11

@Yuicon

Description

@Yuicon

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重叠的模块。

待续

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions