Java模块化
1. Java模块化概述
1.1 JDK8及以前开发模式
- 每个java文件被明确地放入到一个包中
- java文件编译后的class文件,可以压缩为jar包,供别的程序调用
- 一个程序可以使用类库,类库通常以jar包呈现
- 将所有程序jar包,类库jar包,都放在classpath上,来运行程序
- 以jar为中心
1.2 Jar Hell
- jar文件无法控制被人访问其内部的public的类
- 无法控制不同jar包中,相同的类名(包名+类名)
- java运行时,无法判定classpath路径上的jar有多少个不同版本的文件,java加载第一个符合名字的类
- java运行时,无法预判classpath路径上是否缺失了一些关键类
1.3 模块化原则
- 强封装性:一个模块必须能够对其他模块隐藏其部分代码
- 定义良好的接口:模块必须向其他模块公开定义良好且稳定的接口
- 显示依赖:明确一个模块需要哪些模块的支持才能完成工作
1.4 Java模块化系统
-
Java9开始引入新的模块化系统:Jigsaw拼图
- 以模块(module)为中心
- 对JDK本身进行模块化
- 提供一个应用程序可以使用的模块系统
-
以Java11为例
- 一共71个模块
- 最底层的是java.base
- 每个模块都有明确的依赖模块,不存在循环依赖
- 使用java --list-modules,可以查看JDK的模块列表
- 每个类都自动引用java.base模块
- 使用java --describe-module查看平台模块声明
2. 模块创建和运行
2.1 Java Jigsaw
-
自Java9推出,以模块为中心
-
在模块中,仍以包-类文件结构存在
-
在每个模块中,都有一个module-info.java
-
说明这个模块依赖哪些其他的模块,输出本模块哪些内容
module java.prefs { requires java.xml; export java.util.prefs; }
-
-
Module的命名时由module-info.java来控制
-
链接jlink,制作自定义运行时镜像(custom runtime image)
- 舍弃无用庞大的JDK库
- 适合在容器中快速部署运行
3. 模块信息文件
3.1 module-info.java
-
模块安全控制的核心
-
模块和外界沟通的总负责
-
名字和内容组成
-
模块名字
- 模块名称必须唯一
- 可以不和包名相同
- 使用有代表性的词语
- 不需要包括版本号
-
示例
module module.hello { //输出给别人使用 exports cn.hello; //本模块需依赖的模块 requires java.xml; }
3.2 requires调用其他模块
- requires可以添加多个
- 单纯requires,模块依赖不会传递,即调用module.hello不会自动调用java.xml
- 传递依赖使用require transitive
- requires N,编译和运行都依赖于N
- requires transitive N,编译和运行都传递依赖于N
- requires static N,编译依赖于N,运行可选
- requires transitive static N,编译传递依赖于N,运行可选
3.3 exports将当前模块输出
- 只有输出,别人才能使用
- exports可以指定某些包输出,exports
- 限定输出到特定的模块使用,exports
to ,
3.4 opens将当前模块开放用于发射
-
exports导出的包的public部分可以反射,其他权限修饰的内容和未导出的内容无法反射(setAccessible(true)也无效)
-
opens可以打开一些包,其他模块可以反射调用这些包及内容
-
open module打开整个模块
-
打开一个包,opens
-
仅对某些模块打开一个包,opens
to , open module module.first { exports first.p1; // opens firts.p1; }
4. 服务
4.1 服务介绍
-
Java模块系统引入的新功能,实现解耦
-
模块对外只暴露接口,隐藏实现类
-
provides提供接口,with实现类(不导出)
-
uses消费接口
-
ServiceLoader加载接口的实现类
-
示例
module module.first { exprots first.p1; provides first.p1.Shoe with p2.DoubleStar; } module module.second { requires module.first; uses first.p1.Shoe; } SeviceLoader<Shoe> objs = ServiceLoader.load(Shoe.class); for (Shoe obj : objs) { obj.walk(); }
-
ServiceLoader通过load加载接口的实现类(with语句提供)
-
每次load,(默认情况下)都会产生新的各自独享的实例,没有唯一的服务实例
-
可以调用reload进行刷新
4.2 创建服务实例
Java模块系统提供两种方法创建服务实例
-
服务实现类有public的无参构造函数
-
使用单独的静态提供者方法
- 一个名为provider的public static无参数方法
- 返回服务接口或者子类
provides first.p1.Shoe with first.p1.ShoeFactory; public class ShoeFactory { public static Shoe provider() { Shoe result = new DoubleStar(); return result; } }
5. Java模块化应用
5.1 Java模块化特点
- 从根源上对JDK进行模块化,降低最终程序运行时负载
- 在jar层上增加一个module机制
- 引入exports/requires/opens明确模块边界和依赖关系,程序更隐私安全
- 引入服务provides/uses使得程序更解耦
- jlink制作运行时映像,使运维更高效