-
Java模块化系统(Java Platform Module System),Java9引入的核心特性,为了解决传统Java的依赖混乱、代码封装性差和运行时臃肿。
-
模块化的核心目标:
- 强封装性:模块声明外部暴漏的(exports),隐藏内部实现
- 显式依赖管理:模块需声明依赖的其他模块(requires),避免隐式依赖冲突
- 优化运行时:只加载必要的模块,减少占用内容和启动时长
- 解决JAR地狱(依赖管理冲突,版本不一致):通过版本隔离和依赖声明,避免类路径冲突。
-
模块定义(module-info.java):每个模块必须包含一个module-info.java文件,定义模块的元数据:
module com.example.myapp {
requires java.sql; // 依赖其他模块
rqeuires transitive com.example.tuils; //传递依赖(依赖此模块的模块自动继承)
exports com.example.api; //对外暴漏的包
opens com.example.internal; //允许反射访问(如Spring/Hibernate)
provides com.example.Service with com.example.SeviceImpl; //服务提供
uses com.exmaple.spi.Provider; //服务消费
}
- 关键概念与语法
| 关键字 | 作用 |
|---|---|
| requires | 声明对其他模块的依赖 |
| requires transitive | 传递依赖,依赖此模块的模块自动继承此依赖 |
| exports | 导出包,允许其他模块访问此包中的公共类 |
| opens | 开放包,允许其他模块通过反射访问此包中的类(即使未导出) |
| provides...with | 声明服务接口的实现类 |
| uses | 声明需要消费的服务接口 |
-
模块类型
- 命名模块(Named Module):显式声明module-info.java的模块,依赖显式声明
- 自动模块(Auto Module): ① 传统JAR放在模块路径上,自动转化为模块,依赖其他所有模块(隐式requires)② 模块名基于jAR名推导(如
my-library-1.0.jar→ 模块名my.library) - 未命名模块(Uname Module): 类路径上的JAR被视为未命名模块,可以访问所有模块,但无法被其他模块依赖。
-
服务加载机制(Service Loader): 通过provides 和 uses 实现解耦的服务发现
- 服务接口定义
package com.example.spi; public interface Provider { void execute(); }- 服务提供者模块
module com.example.provider { provides com.example.spi.Provider with com.example.ProviderImpl; }- 服务消费者模块:
module com.example.consumer { requires com.example.spi; uses com.example.spi.Provider; }- 使用服务
ServiceLoader<Provider> loader = ServiceLoader.load(Provider.class); loader.forEach(Provider::execute); -
模块化迁移的挑战:
- 反射访问限制:未opens的包无法通过反射访问(常见于Spring/Hibernate), 解决方案:使用Opens或命令选项 --add-opens。
- 依赖冲突:自动模块可能会引入隐式依赖
- 动态加载类:Class.forName() 可能会失败,需要改用模块感知的加载方式。
面试常见问题与答案
-
为什么需要模块化?
- 解决类路径混乱、增强封装性、优化运行时性能。
-
如何让其他模块访问本模块的非导出类?
- 使用
opens开放包,或通过--add-opens命令行参数。
- 使用
-
模块化如何与服务加载机制结合?
- 通过
provides和uses声明服务提供者和消费者,实现解耦的插件化架构。
- 通过
-
模块化对反射的影响是什么?
- 默认禁止反射访问未开放的包,需显式配置。
-
如何将传统JAR转换为模块?
- 放在模块路径上成为自动模块,或添加
module-info.java定义为命名模块。但自动模块可能会引入隐式依赖
- 放在模块路径上成为自动模块,或添加