JDK9特性
一、模块化
模块是代码、数据和资源的集合。它是一组相关的包和类型(类、抽象类、接口等),包含代码、数据文件和一些静态资源。说人话就是,JDK9
在JDK8
之上对package
再进行了一层包装,以往的JDK
中在.jar
包中就是package
包了,也就是说如果你在工程中引入了一个.jar
,这么这整个.jar
下的包都被引入到你的工程,在你的工程中就可以使用引入的这个.jar
文件的所有包下的类。但是在JDK9
模块化之后,在.jar
下,package
上,添加了一层module
,也就是说,如果你在工程中引入一个.jar
,这时也不能访问到.jar
下的所有包,要看两个条件,一个条件是这个.jar
是否将这个包开放访问,二是当前模块是否需要访问.jar
中的某个包。
示例:
在工程中引入lombok
,显然根据上面说的,如果在JDK9
之前,这样就可以访问到lombok
中的相关类资源。但是这里是访问不到的。
1 | <?xml version="1.0" encoding="UTF-8"?> |
想要访问到lombok
中的类资源,这时需要两个条件都满足,当前工程才可以使用lombok
相关类。
lombok
将自身的某些允许访问的包暴露。1
2
3
4
5
6
7
8
9module lombok {
requires java.compiler;
requires java.instrument;
requires jdk.unsupported;
// lombok中暴露 lombok 这个包
exports lombok;
exports lombok.experimental;
... 省略部分代码
}当前工程需要
lombok
中的某些包1
2
3
4
5module user_center {
requires common;
// 当前工程中引入 lombok 包
requires lombok;
}
这两个条件都需要在对应的module-info.java
显示声明。
根据上面的例子可以看出,JDK9
是通过在顶层类路径下添加module-info.java
来控制模块化。这个文件称为模块描述符。可以通过反向的域名来定义模块名。
1.模块描述符
1.需要引入某些模块,使用requires
引入模块。
1 | // 默认情况是不支持透传, 也就是A -> B, B -> C, 下面这种写法A是不引用C的。 |
2.需要暴露某些模块,供其他模块引入。
1 | // 将某个包暴露, 其他引用模块才可以通过requires关键字引入 |
例子
A模块中有两个包路径:xiaocainiao.common.entity
和xiaocainiao.common.vo
。
1 | module common { |
B模块中有两个包路径:xiaocainiao.user.entity
和xiaocainiao.user.vo
。
1 | module user_center { |
这种情况下,B模块仍然也只能引用到A模块暴露出来的一个包,另一个没有暴露出来的包,B模块无法访问。这个时候对于B来说,是引用了整个A模块,当然也可以点对点引用。
1 | // 将A模块修改为仅对B模块暴露, 其他模块引入A模块后将无法访问到cn.com.xiaocainiaoya.common.facade |
我在学习jdk9
模块化时,这里有一个小插曲,我先引入了hutool5.7.20
的依赖,然后通过requires cn.hutool;
发现无法引入,而在idea
的pom.xml
有个小提示,提示我引入最新版本的hutool
,所以我尝试引入hutool5.8.18
后,发现requires cn.hutool;
生效了,但是我又观察了一下,hutool5.8.18
中并没有module-info.class
文件,这个时候我就很疑惑了,对比观察了下,发现两者的MANIFEST.MF
中存在一点差异。
1 | // hutool 5.7.20 |
到这你应该有所发现,在高版本中有一个参数Automatic-Module-Name
,属性是用来为自动模块指定一个模块名称的。通过声明这个属性,就可以为没有显式模块信息的 JAR 文件赋予一个模块名称,使其成为一个合法的模块。
然后进入git
看了下源码:
1 | <properties> |
3.开放更高访问权限,使用open
关键字。
在模块化系统中,模块会隔离其内部的包,只有 public
修饰的包才能在模块外部访问。然而,有时候我们希望允许其他模块访问模块中的所有非 public
类型,而不用逐个包或类型地进行声明。这时可以使用 open
关键字来声明一个模块为 “开放模块”,这样其他模块就可以访问该模块中的所有非 public
类型。
1 | // 使用open只是为了提高访问权限, 改exports暴露的还是要暴露 |
需要注意的是open
是运行时用于控制反射访问的权限,如果仅仅使用open
声明,那么类是无法被引用到,也就是编译器都无法通过,所以还需要使用exports
或者使用require static
配合使用。
通过open
引入模块后,使用反射操作是不需要.setAccessible(true)
操作授权。
在git
上看到一些JDK9
的示例工程中出现:
1 | module module.web { |
4.uses
和 provides
是模块化系统中用于服务(Service)的关键字。
uses
关键字用于声明一个模块使用某个服务。服务是一种提供特定功能的接口或抽象类,它可以由一个或多个模块实现。通过 uses
关键字,我们可以在模块中声明对特定服务的依赖,以便在运行时获取该服务的实现。这样,模块就可以在不依赖具体实现类的情况下使用服务。
1 | // common模块: 在facade包下有一个FeeFacade接口,在service包下有一个FeeService实现 |
在user-center
模块中使用ServiceLoader
进行加载。
1 | public static void main(String[] args) { |
2.可见性
接上面的open
关键字说到,看到一些实例工程中使用open
将包的权限开放给spring
中的部分模块。许多库依赖于反射来发挥它们的魔力,默认情况下,在JDK9
中只能访问到暴露包中的公共类、方法和字段,即使通过反射来访问非公共成员并调用setAccessible(true)
,也是无法访问到。只能通过open
、opens
、opens..to
的方式授予运行时访问权限。
3.模块路径
模块是从modulePath
加载的,就像jdk8
及之前的jdk
的类是从类路径加载。所以你一定知道什么是类路径地狱,有时可能会出现在本地环境中可以成功启动项目,在其他环境无法启动成功,这是由于java
对引用类的解析规则为第一次遇见策略,所以未知的排序依赖关系,可能会出现一些未知的问题。
从java9
开始,将进入另一种地狱:modulepath
地狱。
模块路径是一系列的模块:以文件夹或者JAR
方式呈现,如果模块是文件夹格式,则表示该模块是分解模块的格式;如果是jar
则是模块化的jar
.模块和模块描述符提供的可靠配置有助于消除许多此类运行时类路径问题。每个模块都显式地声明其依赖项,这些依赖项作为应用程序启动来解析。