Java 9模块系统中的uses用于声明本模块使用某服务接口,通过ServiceLoader.load()加载实现类,仅是编译期依赖声明;Dubbo扩展点规范由@SPI、配置文件路径、URL参数等运行时契约定义,与uses无关。

“uses 声明”并不是微内核架构或 Dubbo 等主流插件化框架中的标准语法或机制。在 Java 生态(包括 Dubbo、OSGi、Spring 插件化实践)中,没有 uses 关键字用于定义扩展点规范。它容易与 Java 模块系统(Java 9+ Module System)中的 uses 指令混淆——但该指令作用完全不同。
Java 9 模块系统中的 uses 是什么?
在 module-info.java 中,uses 用于声明本模块“使用”某个服务接口(Service Interface),即它会通过 ServiceLoader.load() 主动查找并加载该接口的实现类。它不定义扩展点规范,也不参与运行时插件注册或适配逻辑,仅是编译期/模块期的依赖声明。
-
uses com.example.spi.LogHandler;→ 表示本模块会调用ServiceLoader.load(LogHandler.class) - 真正定义“谁可以扩展、如何扩展、如何被发现”的,是 SPI 接口本身 + 配置文件(如
META-INF/dubbo/com.example.spi.LoadBalance) - Dubbo 的扩展点规范由
@SPI、@Adaptive、URL 参数、约定目录结构共同构成,与uses无关
微内核中定义扩展点规范的正确方式
核心变量(如负载策略、序列化方式、协议类型)的可替换能力,本质是将其抽象为标准化接口 + 显式契约,而非靠模块声明驱动。关键动作如下:
-
定义清晰的 SPI 接口:带默认值(
@SPI("random"))、方法语义明确(如select(...))、参数含URL传递上下文 -
约定扩展配置路径:Dubbo 要求在
META-INF/dubbo/或META-INF/dubbo/internal/下放置接口全限定名的文本文件,每行key=class -
通过 URL 统一注入配置:运行时所有扩展点行为由 URL 中的
loadbalance=random、serialization=kryo等参数动态决定 - 避免硬编码依赖:核心模块只依赖接口,不 import 任何具体实现类;实现类打包在独立模块中,由 SPI 机制自动发现
为什么不用 uses 声明来“定义规范”?
因为微内核的扩展性不依赖模块系统级声明,而依赖运行时可插拔契约:
-
uses是静态、单向、编译期绑定的,无法支持按需加载、条件激活、URL 动态覆盖等关键能力 - Dubbo 的
@Adaptive注解 +ExtensionLoader才是真正的扩展点运行时环境,它解析 URL、查找实现、缓存实例、支持 fallback - 若强行在 module-info 中写
uses,反而可能干扰 Dubbo 自己的 ExtensionLoader,导致重复加载或 ClassLoader 冲突
实战建议:聚焦契约,而非声明语法
要让团队成员快速理解并遵循扩展点规范,应提供:
- 一份标准 SPI 接口模板(含注释说明每个方法的职责、URL 参数含义、异常场景)
- 一个最小可运行的扩展实现示例(如自定义
ConsistentHashLoadBalance) - 构建脚本检查:确保新扩展模块包含正确的
META-INF/dubbo/xxx文件且 key 唯一 - 配置中心联动文档:说明如何通过
dubbo.reference.loadbalance=xxx在运行时切换










