Java的模块化长征——从OSGi到JPMS的十年恩怨

简介: Java模块化是一场持续了十多年的运动,经历了从第三方OSGi标准到官方JPMS(Java平台模块系统)的漫长演进。

Java模块化是一场持续了十多年的运动,经历了从第三方OSGi标准到官方JPMS(Java平台模块系统)的漫长演进。JDK 9引入的模块化系统不仅是Java语言最大的结构性变化之一,也是JDK自身重构的基础——它将庞大的rt.jar拆分为多个模块,允许应用只包含所需的部分,从而减少内存占用和启动时间。

OSGi是Java模块化的先驱。早在2000年,OSGi联盟就定义了基于Bundle的模块化规范。OSGi Bundle是包含元数据(MANIFEST.MF)的JAR文件,声明了导出的包、导入的包、以及可选的Require-Bundle。OSGi运行在特殊的框架中(如Equinox、Felix),支持模块的动态安装、启动、停止和更新,而无需重启JVM。
参考:https://ltglu.cn/category/sleep-psychology.html

OSGi的强大之处在于其类加载器架构。每个Bundle拥有独立的类加载器,模块之间的类隔离通过不同的类加载器实现。Bundle可以导出特定包,其他Bundle只能访问被导出的包。这种严格的隔离使得同一个JVM中可以运行不同版本的同一库,这在应用服务器和IDE(如Eclipse)中非常有用。

然而,OSGi也有显著的缺点:类加载器架构复杂,调试困难;需要特殊的容器,不能在普通JVM中运行;元数据配置繁琐;动态更新在实际生产中使用较少。这些问题促使Java官方开发自己的模块化系统。

JPMS(Java Platform Module System,又称Project Jigsaw)在JDK 9中正式发布。JPMS的核心是模块描述符(module-info.java),编译后成为module-info.class。模块描述符声明:模块名称、导出的包、依赖的模块、提供的服务等。例如,module com.example.foo { exports com.example.foo.api; requires java.sql; }。
参考:https://ltglu.cn/category/sleep-environment.html

JPMS的类加载器架构比OSGi简单。JDK本身被划分为多个模块(java.base是所有模块的基础),应用模块在启动时被解析,形成一个有向无环图。类加载器仍然存在(Bootstrap、Extension、Application、自定义),但模块系统增强了类加载器的隔离能力。

模块路径是JPMS引入的新概念。传统的类路径(Classpath)是一个扁平的JAR列表,JVM在其中按顺序搜索类。模块路径上的模块支持模块化特性:强封装、可靠配置、以及服务绑定。模块路径上的JAR也可以作为自动模块(放在模块路径但没有module-info的JAR),自动模块名称从JAR文件名推断,并导出所有包。

强封装是JPMS最重要的安全特性。在模块系统中,一个模块只导出明确声明的包,未导出的包即使声明为public,在模块外部也不可见。这打破了长期以来Java中public意味着完全可见的规则,为平台安全提供了新的保障。JDK内部API(如sun.misc.Unsafe)被封装起来,迫使开发者使用官方支持的API。

可靠配置确保模块依赖在启动时被完整解析。如果模块A依赖模块B,但模块B不在模块路径上,JVM在启动时会报错,而不是等到运行时抛出ClassNotFoundException。这种早期错误检测大大提高了应用的可维护性。

服务绑定提供了一种松耦合的依赖注入机制。模块可以使用provides ... with ...声明服务提供者,使用uses声明服务消费者。模块系统在启动时自动将服务消费者绑定到可用的提供者,无需显式配置。
参考:https://ltglu.cn/category/sleep-products.html

JPMS的迁移挑战是巨大的。首先,现有库需要添加module-info.java才能成为显式模块,但这对成千上万的库来说是不现实的。解决方案是自动模块和未命名模块。自动模块位于模块路径上但没有module-info,它导出所有包,并依赖所有其他模块。未命名模块包含类路径上的所有代码,它可以访问所有模块,但模块不能访问未命名模块(除非使用--add-reads)。这种设计允许逐步迁移:先将应用部署到模块路径上,将类路径上的库作为自动模块,然后逐步为库添加模块描述符。

JDK自身的模块化是JPMS的最大成功。JDK 9将rt.jar(约60MB)拆分为大约100个模块。这意味着应用可以只包含所需的模块,使用jlink工具创建自定义的JRE镜像。对于微服务、容器化应用和嵌入式系统,这可以显著减少内存占用和镜像大小。

多版本JAR是JPMS的辅助特性,允许在同一个JAR中包含针对不同Java版本的类。这在模块化迁移中很有用——你可以为Java 9+提供module-info.class,同时保持对Java 8的兼容性。

模块化与IDE的集成是JPMS普及的关键。IntelliJ IDEA、Eclipse、NetBeans都支持创建和管理模块化项目,包括module-info.java的编写、模块路径的配置、以及模块图的可视化。构建工具Maven和Gradle也支持模块路径,但配置略有不同。

JPMS并非没有批评者。有人认为JPMS过于复杂,增加了学习成本;有人认为它的服务绑定不如Spring或Guice强大;还有人认为模块化带来的好处不足以抵消迁移成本。这些批评有一定道理,但JPMS的主要价值在于JDK自身的模块化和对大型应用的结构化支持,而不是作为通用的依赖注入框架。
参考:https://ltglu.cn
在实践中,是否使用JPMS取决于项目规模。对于小型应用,类路径和传统JAR已经足够。对于大型、长期维护的企业应用,模块化可以显著改善架构的可维护性。对于构建自定义JRE镜像的容器应用,JPMS几乎是必需的。

目录
相关文章
|
存储 缓存 文件存储
如何保证分布式文件系统的数据一致性
分布式文件系统需要向上层应用提供透明的客户端缓存,从而缓解网络延时现象,更好地支持客户端性能水平扩展,同时也降低对文件服务器的访问压力。当考虑客户端缓存的时候,由于在客户端上引入了多个本地数据副本(Replica),就相应地需要提供客户端对数据访问的全局数据一致性。
32698 79
如何保证分布式文件系统的数据一致性
|
前端开发 容器
HTML5+CSS3前端入门教程---从0开始通过一个商城实例手把手教你学习PC端和移动端页面开发第8章FlexBox布局(上)
HTML5+CSS3前端入门教程---从0开始通过一个商城实例手把手教你学习PC端和移动端页面开发第8章FlexBox布局
17753 20
|
设计模式 存储 监控
设计模式(C++版)
看懂UML类图和时序图30分钟学会UML类图设计原则单一职责原则定义:单一职责原则,所谓职责是指类变化的原因。如果一个类有多于一个的动机被改变,那么这个类就具有多于一个的职责。而单一职责原则就是指一个类或者模块应该有且只有一个改变的原因。bad case:IPhone类承担了协议管理(Dial、HangUp)、数据传送(Chat)。good case:里式替换原则定义:里氏代换原则(Liskov 
36684 19
设计模式(C++版)
|
存储 编译器 C语言
抽丝剥茧C语言(初阶 下)(下)
抽丝剥茧C语言(初阶 下)
|
机器学习/深度学习 人工智能 自然语言处理
带你简单了解Chatgpt背后的秘密:大语言模型所需要条件(数据算法算力)以及其当前阶段的缺点局限性
带你简单了解Chatgpt背后的秘密:大语言模型所需要条件(数据算法算力)以及其当前阶段的缺点局限性
24758 14
|
机器学习/深度学习 弹性计算 监控
重生之---我测阿里云U1实例(通用算力型)
阿里云产品全线降价的一力作,2023年4月阿里云推出新款通用算力型ECS云服务器Universal实例,该款服务器的真实表现如何?让我先测为敬!
36662 15
重生之---我测阿里云U1实例(通用算力型)
|
SQL 存储 弹性计算
Redis性能高30%,阿里云倚天ECS性能摸底和迁移实践
Redis在倚天ECS环境下与同规格的基于 x86 的 ECS 实例相比,Redis 部署在基于 Yitian 710 的 ECS 上可获得高达 30% 的吞吐量优势。成本方面基于倚天710的G8y实例售价比G7实例低23%,总性价比提高50%;按照相同算法,相对G8a,性价比为1.4倍左右。
|
存储 算法 Java
【分布式技术专题】「分布式技术架构」手把手教你如何开发一个属于自己的限流器RateLimiter功能服务
随着互联网的快速发展,越来越多的应用程序需要处理大量的请求。如果没有限制,这些请求可能会导致应用程序崩溃或变得不可用。因此,限流器是一种非常重要的技术,可以帮助应用程序控制请求的数量和速率,以保持稳定和可靠的运行。
29838 52

热门文章

最新文章

下一篇
开通oss服务