0%

JDK9特性

JDK9特性

一、模块化

​ 模块是代码、数据和资源的集合。它是一组相关的包和类型(类、抽象类、接口等),包含代码、数据文件和一些静态资源。说人话就是,JDK9JDK8之上对package再进行了一层包装,以往的JDK中在.jar包中就是package包了,也就是说如果你在工程中引入了一个.jar,这么这整个.jar下的包都被引入到你的工程,在你的工程中就可以使用引入的这个.jar文件的所有包下的类。但是在JDK9模块化之后,在.jar下,package上,添加了一层module,也就是说,如果你在工程中引入一个.jar,这时也不能访问到.jar下的所有包,要看两个条件,一个条件是这个.jar是否将这个包开放访问,二是当前模块是否需要访问.jar中的某个包。

示例:

在工程中引入lombok,显然根据上面说的,如果在JDK9之前,这样就可以访问到lombok中的相关类资源。但是这里是访问不到的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<artifactId>common</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>

<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.26</version>
</dependency>
</dependencies>
</project>

想要访问到lombok中的类资源,这时需要两个条件都满足,当前工程才可以使用lombok相关类。

  1. lombok将自身的某些允许访问的包暴露。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    module lombok {
    requires java.compiler;
    requires java.instrument;
    requires jdk.unsupported;
    // lombok中暴露 lombok 这个包
    exports lombok;
    exports lombok.experimental;
    ... 省略部分代码
    }
  2. 当前工程需要lombok中的某些包

    1
    2
    3
    4
    5
    module user_center {
    requires common;
    // 当前工程中引入 lombok 包
    requires lombok;
    }

这两个条件都需要在对应的module-info.java显示声明。

根据上面的例子可以看出,JDK9是通过在顶层类路径下添加module-info.java来控制模块化。这个文件称为模块描述符。可以通过反向的域名来定义模块名。

1.模块描述符

1.需要引入某些模块,使用requires引入模块。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 默认情况是不支持透传, 也就是A -> B, B -> C, 下面这种写法A是不引用C的。
module my.module {
requires module.name;
}

// 透传,也就是A -> B, B -> C, 下面这种写法A也是依赖于C
module my.module {
requires transitive module.name;
}

// 静态引入:编译的时候必须要包含这些类的jar包才能够编译通过。但是在运行的时候我们可能不会用到这些类
// 表示在编译时的依赖是强制的,但在运行时是可选的
module my.module {
requires static module.name;
}

2.需要暴露某些模块,供其他模块引入。

1
2
3
4
5
6
7
8
// 将某个包暴露, 其他引用模块才可以通过requires关键字引入
module my.module {
exports module.name;
}
// 点对点暴露
module my.module {
exports module.nameA to module.nameB, module.nameC, module.nameD;
}

例子

A模块中有两个包路径:xiaocainiao.common.entityxiaocainiao.common.vo

1
2
3
4
module common {
// 暴露A模块的其中一个包路径
exports xiaocainiao.common.entity;
}

B模块中有两个包路径:xiaocainiao.user.entityxiaocainiao.user.vo

1
2
3
4
module user_center {
// B模块引用整个A模块
requires common;
}

这种情况下,B模块仍然也只能引用到A模块暴露出来的一个包,另一个没有暴露出来的包,B模块无法访问。这个时候对于B来说,是引用了整个A模块,当然也可以点对点引用。

1
2
3
4
// 将A模块修改为仅对B模块暴露, 其他模块引入A模块后将无法访问到cn.com.xiaocainiaoya.common.facade
module common {
exports cn.com.xiaocainiaoya.common.facade to user_center;
}

​ 我在学习jdk9模块化时,这里有一个小插曲,我先引入了hutool5.7.20的依赖,然后通过requires cn.hutool; 发现无法引入,而在ideapom.xml有个小提示,提示我引入最新版本的hutool,所以我尝试引入hutool5.8.18后,发现requires cn.hutool;生效了,但是我又观察了一下,hutool5.8.18中并没有module-info.class文件,这个时候我就很疑惑了,对比观察了下,发现两者的MANIFEST.MF中存在一点差异。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// hutool 5.7.20
Manifest-Version: 1.0
Archiver-Version: Plexus Archiver
Built-By: looll
Created-By: Apache Maven 3.8.1
Build-Jdk: 1.8.0_261

// hutool 5.8.18
Manifest-Version: 1.0
Implementation-Title: hutool-all
Automatic-Module-Name: cn.hutool
Implementation-Version: 5.8.18
Build-Timestamp: 2023-04-16T15:40:28Z
Built-By: looll
Build-OS: Windows 11
Build-Jdk-Spec: 1.8
Created-By: Maven JAR Plugin 3.3.0
Build-Jdk: 1.8.0_333

到这你应该有所发现,在高版本中有一个参数Automatic-Module-Name,属性是用来为自动模块指定一个模块名称的。通过声明这个属性,就可以为没有显式模块信息的 JAR 文件赋予一个模块名称,使其成为一个合法的模块。

然后进入git看了下源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<properties>
<Automatic-Module-Name>cn.hutool</Automatic-Module-Name>
</properties>

<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.3.0</version>
<configuration>
<archive>
<index>true</index>
<manifest>
<addDefaultImplementationEntries>true</addDefaultImplementationEntries>
</manifest>
<manifestEntries>
<Build-OS>${os.name}</Build-OS>
<Built-By>${user.name}</Built-By>
<Build-Jdk>${java.version}</Build-Jdk>
<Build-Timestamp>${maven.build.timestamp}</Build-Timestamp>
<!--通过maven打包工具,将这个属性添加到MANIFEST.MF中-->
<Automatic-Module-Name>${Automatic-Module-Name}</Automatic-Module-Name>
</manifestEntries>
</archive>
</configuration>
</plugin>

3.开放更高访问权限,使用open关键字。

​ 在模块化系统中,模块会隔离其内部的包,只有 public 修饰的包才能在模块外部访问。然而,有时候我们希望允许其他模块访问模块中的所有非 public 类型,而不用逐个包或类型地进行声明。这时可以使用 open 关键字来声明一个模块为 “开放模块”,这样其他模块就可以访问该模块中的所有非 public 类型。

1
2
3
4
5
6
7
8
9
10
// 使用open只是为了提高访问权限, 改exports暴露的还是要暴露
// 整个模块open
open module common {
exports cn.com.xiaocainiaoya.common.entity;
}

module common {
// 单独暴露这个包
opens cn.com.xiaocainiaoya.common.entity;
}

需要注意的是open是运行时用于控制反射访问的权限,如果仅仅使用open声明,那么类是无法被引用到,也就是编译器都无法通过,所以还需要使用exports或者使用require static配合使用。

通过open引入模块后,使用反射操作是不需要.setAccessible(true)操作授权。

git上看到一些JDK9的示例工程中出现:

1
2
3
4
5
6
7
8
9
module module.web {
// ...省略部分代码

// 开放权限给spring的部分模块, 由于目前我还没有引入spirng, 可能是在spring中对其进行
// 反射之类的操作, 这里先记录下, 日后若遇到问题, 再进行详细的记录。
opens pers.darren to spring.beans, spring.core, spring.context;
opens pers.darren.controller.role to spring.beans, spring.core, spring.web;
opens pers.darren.controller.user to spring.beans, spring.core, spring.web;
}

4.usesprovides 是模块化系统中用于服务(Service)的关键字。

uses 关键字用于声明一个模块使用某个服务。服务是一种提供特定功能的接口或抽象类,它可以由一个或多个模块实现。通过 uses 关键字,我们可以在模块中声明对特定服务的依赖,以便在运行时获取该服务的实现。这样,模块就可以在不依赖具体实现类的情况下使用服务。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// common模块: 在facade包下有一个FeeFacade接口,在service包下有一个FeeService实现
module xiaocainiao.common {
exports cn.com.xiaocainiaoya.common.facade;
requires transitive lombok;

provides cn.com.xiaocainiaoya.common.facade.FeeFacade with cn.com.xiaocainiaoya.common.service.FeeService;
}

// userCenter模块, 通过uses可以使用ServiceLoader进行加载
module user_center {
// 引入common模块
requires xiaocainiao.common;

requires lombok;

uses cn.com.xiaocainiaoya.common.facade.FeeFacade;
}

user-center模块中使用ServiceLoader进行加载。

1
2
3
4
5
6
public static void main(String[] args) {
List<FeeFacade> moduleServices = ServiceLoader
.load(FeeFacade.class).stream()
.map(ServiceLoader.Provider::get)
.collect(Collectors.toList());
}

2.可见性

​ 接上面的open关键字说到,看到一些实例工程中使用open将包的权限开放给spring中的部分模块。许多库依赖于反射来发挥它们的魔力,默认情况下,在JDK9中只能访问到暴露包中的公共类、方法和字段,即使通过反射来访问非公共成员并调用setAccessible(true)也是无法访问到。只能通过openopensopens..to的方式授予运行时访问权限。

3.模块路径

​ 模块是从modulePath加载的,就像jdk8及之前的jdk的类是从类路径加载。所以你一定知道什么是类路径地狱,有时可能会出现在本地环境中可以成功启动项目,在其他环境无法启动成功,这是由于java对引用类的解析规则为第一次遇见策略,所以未知的排序依赖关系,可能会出现一些未知的问题。

​ 从java9开始,将进入另一种地狱:modulepath地狱。

模块路径是一系列的模块:以文件夹或者JAR方式呈现,如果模块是文件夹格式,则表示该模块是分解模块的格式;如果是jar则是模块化的jar.模块和模块描述符提供的可靠配置有助于消除许多此类运行时类路径问题。每个模块都显式地声明其依赖项,这些依赖项作为应用程序启动来解析。

-------------本文结束感谢您的阅读-------------