Java 9模块化系统在双亲委派模型上增加模块层可见性与封装控制,类加载仍由ClassLoader子类执行,但受模块图约束;模块不被直接加载,而是通过ModuleLayer管理,访问需满足requires/exports声明。

Java 9 引入的模块化系统(JPMS,Java Platform Module System)并未改变类加载的基本机制,而是**在原有双亲委派模型之上增加了模块层的可见性与封装控制**。模块本身不直接“被类加载器加载”,而是由模块系统管理其描述、依赖和导出关系;真正执行类加载的仍是 ClassLoader 子类(如 AppClassLoader、PlatformClassLoader),但它们的行为受模块图(ModuleGraph)约束。
模块如何影响类加载过程
当 JVM 启动并解析模块路径(--module-path 或 -p)时,会构建一个运行时模块图。此时:
- 每个具名模块(named module)会被映射到一个
ModuleReference,再由ModuleLayer管理其Module实例; - 类加载器通过
Module::getClassLoader()关联到对应模块,但实际加载仍调用ClassLoader::findClass(); - 若某类属于模块 A,而模块 A 未导出(
exports)该包,或调用方模块 B 未声明requiresA,则即使类存在,也会抛出IllegalAccessError或编译失败 —— 这是模块系统在链接阶段施加的访问限制,不是类加载失败; - 未命名模块(unnamed module,即传统 classpath 下的类)仍由应用类加载器加载,但它能读取所有模块(兼容性设计),而具名模块默认不能读取未命名模块,除非显式加
--add-reads。
手动加载模块内类的正确方式
不能直接用 Class.forName("com.example.MyClass") 跨模块随意加载 —— 若目标类所在模块未对当前模块开放,会触发运行时访问检查失败。稳妥做法是:
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
- 确保模块声明了正确的
requires和exports(或opens,用于反射); - 使用当前模块的类加载器加载:
MyClass.class.getModule().getClassLoader().loadClass("com.example.MyClass"); - 若需从其他模块加载,先确认模块可读(
ModuleLayer.boot().modules()查找目标模块),再调用其getClassLoader(); - 反射访问私有成员时,需模块声明
opens包,或启动时加--add-opens java.base/java.lang=ALL-UNNAMED等参数。
动态创建模块层与自定义类加载
可通过 ModuleLayer.Controller 构建新模块层,实现插件式加载:
立即学习“Java免费学习笔记(深入)”;
- 用
ModuleFinder定位模块 JAR; - 用
Configuration.resolveAndBind()解析依赖; - 用
ModuleLayer.defineModulesWithManyLoaders()将模块绑定到指定类加载器; - 之后可通过新层中的
Module获取类加载器,再加载类 —— 此时类加载受新模块图约束,而非启动层。
常见误区提醒
不要误以为 “模块 = 新类加载器” 或 “模块类必须用新 API 加载”。实际上:
-
ClassLoader.getSystemClassLoader()在模块环境下仍是AppClassLoader,它负责加载未命名模块的类; - 模块信息(
module-info.class)由 JVM 内部解析,不走常规类加载流程; -
Class::getModule()返回非 null 表示该类属于某个具名模块,但加载动作本身未变; - 混合使用模块路径和类路径时,优先级和可见性规则需格外注意,推荐尽量避免混用。

















