一个服务注册 & 发现的 Kotlin Multiplatform 库。
- 注解 + KSP 代码生成,注册表本身无反射(Native / JS / Wasm 可用)
- 运行时核心操作:
setService/getService,外加initialize/install注册入口 - 生成代码进入
commonMain(kspCommonMainMetadata),所有平台共享同一份注册代码 - JVM / Android 手写 FastServiceLoader 解析生成的
META-INF/services资源自动发现注册对象(跳过 JAR 校验,异常时回退ServiceLoader)
由以下组成
- annotation module
- annotation processor model
生成对应的 annotation 代码,连接到 service center:
ServiceCenter.setService(IFoo.class, FooImpl.class)
- service center
负责 service 注册、发现的核心:
setService,getService
对应到本仓库(KMP 下没有 Java annotation processing,代码生成使用 KSP;IFoo.class 的 Kotlin 写法为 IFoo::class):
| 需求 | 模块 |
|---|---|
| annotation module | service-center-annotations(@Service、IService) |
| annotation processor model | service-center-compiler(KSP 处理器,生成注册代码) |
| service center | service-center(注册表运行时) |
service-center/ # 运行时:ServiceCenter、ServiceRegistrar、异常
service-center-annotations/ # @Service、IService
service-center-compiler/ # KSP 处理器,生成 <Name>Generated 注册类与 META-INF/services 资源
sample/ # 集成示例与测试(含 KSP 接线)
sample-android/ # 可运行的 Android app,消费 sample,演示 initialize()/getService()
logging/ # 应用层日志:以 Maven 依赖引入 Loga(jvm/android/ios)
plugins {
alias(libs.plugins.kotlinMultiplatform)
alias(libs.plugins.ksp)
}
kotlin {
sourceSets {
commonMain {
kotlin.srcDir("build/generated/ksp/metadata/commonMain/kotlin")
resources.srcDir("build/generated/ksp/metadata/commonMain/resources")
dependencies {
implementation("io.github.xechoz:service-center:0.0.1")
}
}
}
}
dependencies {
add("kspCommonMainMetadata", "io.github.xechoz:service-center-compiler:0.0.1")
}
tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompilationTask<*>>().configureEach {
if (name != "kspCommonMainKotlinMetadata") {
dependsOn("kspCommonMainKotlinMetadata")
}
}
tasks.matching { it.name.startsWith("ksp") && it.name != "kspCommonMainKotlinMetadata" }.configureEach {
dependsOn("kspCommonMainKotlinMetadata")
}
// 生成的 META-INF/services 资源需先由 KSP 产出
tasks.withType<org.gradle.language.jvm.tasks.ProcessResources>().configureEach {
dependsOn("kspCommonMainKotlinMetadata")
}
tasks.matching { it.name.endsWith("JavaRes") }.configureEach {
dependsOn("kspCommonMainKotlinMetadata")
}可选 KSP 参数:
ksp {
arg("serviceCenter.generatedPackage", "com.example.generated")
arg("serviceCenter.generatedName", "ServiceCenterGenerated")
}// commonMain
import me.xechoz.servicecenter.IService
import me.xechoz.servicecenter.Service
interface Greeter : IService {
fun greet(): String
}
@Service
class GreeterImpl : Greeter {
override fun greet(): String = "hello"
}@Service 类必须实现 IService 的一个子接口,注册键就是该子接口;查表同样通过子接口:
生成(包名与类名可通过 KSP 参数覆盖):
public class ServiceCenterGenerated : ServiceRegistrar {
override fun register() {
ServiceCenter.setService(Greeter::class) { GreeterImpl() }
}
}生成类是 public 无参构造;同时生成 META-INF/services/me.xechoz.servicecenter.ServiceRegistrar(内容为生成类全名),JVM / Android 上由库内置的加载器按名实例化,因此无需引用任何生成类。加载器直接读取该资源并跳过 JAR 校验(避免 Android 上读整包做签名校验的卡顿),仅在异常时才回退 ServiceLoader。
fun main() {
ServiceCenter.initialize() // 发现并注册 classpath 上所有模块的注册对象,可重复调用
val greeter = ServiceCenter.getService<Greeter>()
println(greeter.greet())
}多模块时不需要逐个列出生成对象,initialize() 一次发现全部。
JS / Wasm / Native 没有可靠的自动发现机制,仍按模块显式 install:
ServiceCenter.install(AppGenerated(), DataGenerated())install 按 registrar 类型幂等,重复安装同一个 registrar 会跳过。
| 项目 | 行为 |
|---|---|
| 绑定类型 | @Service 类必须实现 IService 的一个子接口,注册键为该直接父接口(规则见下) |
| 生命周期 | 懒加载单例:首次 getService 创建并缓存 |
| 缺失服务 | getService 抛 ServiceNotFoundException(消息含已注册类型列表);getServiceOrNull 返回 null |
| 自动加载 | JVM / Android 调用一次 ServiceCenter.initialize(),经手写的 FastServiceLoader 解析 META-INF/services 资源(失败回退 ServiceLoader)发现并注册所有模块的注册对象;其他平台为 no-op,需显式 install |
| 重复注册 | 两个不同 registrar 绑定同一服务类型时抛 DuplicateServiceException;同一 registrar 重复 install / initialize 幂等跳过 |
| 触发时机 | 不做惰性触发,getService 不会自动初始化;由使用方显式 initialize() 或 install(...) |
| 测试支持 | ServiceCenter.reset() 清空注册表、已安装 registrar 与初始化标记 |
| 线程安全 | 注册表使用 atomicfu 写时复制;单例创建使用 lazy(SYNCHRONIZED) |
绑定规则:
| @Service 类的直接父类型 | 行为 |
|---|---|
恰好一个 IService 子接口(如 IFooService : IService) |
绑定该接口:getService<IFooService>() |
同时直接写 IService + 一个子接口 |
绑定子接口(直接写的 IService 不计入) |
只有 IService 本身,或没有父类型 |
编译报错:必须实现 IService 的子接口 |
多个 IService 子接口(如 Store、Cache) |
编译报错:只允许一个服务接口 |
非 IService 体系的接口(如 Closeable) |
忽略,不参与绑定 |
| 父类型是类而非接口 | 编译报错:只支持接口 |
@Service 类的约束(KSP 编译期报错,带文件位置):
- 只能是 class 或 object,且必须是可访问(public/internal)的具体类型
- class 必须有可访问的无参构造(允许全部参数带默认值);不支持构造注入
- 不能是 inner / expect / abstract / sealed
| 平台 | 状态 |
|---|---|
| jvm | 测试通过 |
| android | 测试通过(host test)、主源码编译通过 |
| iosArm64 / iosSimulatorArm64 / iosX64 | klib 编译通过(Linux 交叉编译;完整测试需 macOS) |
| linuxX64 | 测试通过 |
| mingwX64 | klib 编译通过(测试需 Windows/wine) |
| macosArm64 | klib 编译通过(测试需 macOS) |
| js / wasmJs | Node 测试通过 |
- 自动加载(
initialize())只在 JVM / Android 生效;JS / Wasm / Native 需显式install - 自动发现按名加载注册类,Android release(R8)依赖库内置的 consumer keep 规则保留生成类(
service-center/consumer-rules.pro,经 KMPoptimization.consumerKeepRules发布到 AAR) - 只处理
commonMain中的@Service;target 源集(如iosMain)中的声明不会被处理 - 注册键为
KClass,Repo<User>与Repo<Order>无法区分 - 无构造注入、作用域、限定符、多绑定;每个
@Service类只允许一个服务接口 - 同一编译单元内多个类绑定到同一类型会在编译期报错
./gradlew :service-center:jvmTest :sample:jvmTest
./gradlew :service-center:testAndroidHostTest :sample:testAndroidHostTest
./gradlew :service-center:linuxX64Test :sample:linuxX64Test
./gradlew :service-center:jsNodeTest :sample:jsNodeTest
./gradlew :service-center:wasmJsNodeTest :sample:wasmJsNodeTestsample-android 是可运行的 Android app(Compose + Material3),启动时调用 ServiceCenter.initialize() 并从 sample 的 AAR 中自动发现生成的 registrar:
./gradlew :sample-android:assembleDebug
adb install -r sample-android/build/outputs/apk/debug/sample-android-debug.apksample 模块即集成验证:ServiceCenterGenerated 由 service-center-compiler 在构建中真实生成,测试覆盖接口绑定、object、默认参数构造、单例、缺失与重复注册。
发布使用 vanniktech maven-publish(publishToMavenCentral + signAllPublications),版本号由 gradle.properties 中的 servicecenter.version 控制。
- Gradle 插件(
service-center-gradle-plugin)自动完成 KSP 接线,消费方只加一行插件 - Native 零显式初始化:
@EagerInitialization(JVM/Android 已由initialize()+ FastServiceLoader 实现) - 编译期图校验:循环依赖、缺失绑定、重复绑定诊断
- 限定符(
@Named)、作用域(Singleton/Factory)、@Provides模块与 Set/Map 多绑定 ServiceCenter.describe()注册表 dump;异常附候选类型建议- binary-compatibility-validator 与 GitHub Actions 多平台 CI(ubuntu:jvm/linuxX64/js,macos:iOS)