
Annotation-driven compiler code generation for reflection-free service registration and discovery. Runtime set/get with initialize/install, compile-time binding checks, lazy singletons, fast META-INF/services loader with fallback.
一个服务注册 & 发现的 Kotlin Multiplatform 库。
setService / getService,外加 initialize / install 注册入口commonMain(kspCommonMainMetadata),所有平台共享同一份注册代码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 编译期报错,带文件位置):
| 平台 | 状态 |
|---|---|
| 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
service-center/consumer-rules.pro,经 KMP optimization.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 控制。
service-center-gradle-plugin)自动完成 KSP 接线,消费方只加一行插件@EagerInitialization(JVM/Android 已由 initialize() + FastServiceLoader 实现)@Named)、作用域(Singleton/Factory)、@Provides 模块与 Set/Map 多绑定ServiceCenter.describe() 注册表 dump;异常附候选类型建议一个服务注册 & 发现的 Kotlin Multiplatform 库。
setService / getService,外加 initialize / install 注册入口commonMain(kspCommonMainMetadata),所有平台共享同一份注册代码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 编译期报错,带文件位置):
| 平台 | 状态 |
|---|---|
| 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
service-center/consumer-rules.pro,经 KMP optimization.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 控制。
service-center-gradle-plugin)自动完成 KSP 接线,消费方只加一行插件@EagerInitialization(JVM/Android 已由 initialize() + FastServiceLoader 实现)@Named)、作用域(Singleton/Factory)、@Provides 模块与 Set/Map 多绑定ServiceCenter.describe() 注册表 dump;异常附候选类型建议