You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

寻求Kotlin多平台、与实现无关的依赖注入方案

适配Spring Boot与Ktor的Kotlin多平台库DI方案

核心思路是基于Jakarta Inject API做抽象层,同时为Spring Context和Koin分别实现适配桥接,让库本身完全不绑定任何DI框架,用户可以根据自身项目选择对应适配模块即可。

1. 库核心代码:用Jakarta Inject定义无绑定的配置与Bean

库的公共多平台模块只依赖Jakarta Inject的基础注解(@Inject、@Singleton),同时自定义一个标记配置类的注解(类似Spring的@Configuration),统一Bean的声明方式:

// 多平台公共模块代码
import jakarta.inject.Inject
import jakarta.inject.Singleton

// 自定义配置类标记注解
@Target(AnnotationTarget.CLASS)
annotation class LibraryModule

// 库的配置类,类似Spring @Configuration
@LibraryModule
class DataModule {
    // 声明单例Bean,对应Spring @Bean + @Singleton
    @Singleton
    fun userRepository(): UserRepository {
        return JdbcUserRepository()
    }

    // 依赖注入其他Bean,用@Inject标记参数
    @Singleton
    fun userService(@Inject repo: UserRepository): UserService {
        return DefaultUserService(repo)
    }
}

// 业务Bean,用Jakarta注解声明依赖
interface UserRepository
class JdbcUserRepository : UserRepository

interface UserService
class DefaultUserService @Inject constructor(val repo: UserRepository) : UserService

2. Spring Boot适配:桥接Jakarta注解到Spring Context

Spring本身原生支持Jakarta Inject的@Inject和@Singleton,我们只需要实现一个ImportBeanDefinitionRegistrar,扫描库中带@LibraryModule的配置类,自动将其中的Bean方法注册到Spring上下文:

// Spring适配模块(仅JVM)
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar
import org.springframework.core.type.AnnotationMetadata
import org.springframework.beans.factory.support.BeanDefinitionRegistry
import org.springframework.beans.factory.support.GenericBeanDefinition
import org.springframework.core.io.support.PathMatchingResourcePatternResolver
import org.springframework.core.type.classreading.CachingMetadataReaderFactory

class LibrarySpringRegistrar : ImportBeanDefinitionRegistrar {
    override fun registerBeanDefinitions(metadata: AnnotationMetadata, registry: BeanDefinitionRegistry) {
        // 扫描classpath中所有带@LibraryModule的类
        val resolver = PathMatchingResourcePatternResolver()
        val readerFactory = CachingMetadataReaderFactory(resolver)
        val resources = resolver.getResources("classpath*:**/*.class")
        
        resources.forEach { resource ->
            val reader = readerFactory.getMetadataReader(resource)
            val classMetadata = reader.classMetadata
            if (classMetadata.hasAnnotation(LibraryModule::class.qualifiedName)) {
                val configClass = Class.forName(classMetadata.className)
                
                // 注册配置类本身到Spring上下文
                registry.registerBeanDefinition(configClass.simpleName, GenericBeanDefinition().apply {
                    beanClass = configClass
                })
                
                // 解析配置类中的Bean方法
                configClass.declaredMethods.filter { it.isAnnotationPresent(Singleton::class.java) }.forEach { method ->
                    val beanDef = GenericBeanDefinition().apply {
                        beanClass = method.returnType
                        factoryBeanName = configClass.simpleName
                        factoryMethodName = method.name
                        scope = "singleton"
                        // 自动注入方法参数中的依赖
                        autowireMode = org.springframework.beans.factory.config.AutowireCapableBeanFactory.AUTOWIRE_BY_TYPE
                    }
                    registry.registerBeanDefinition(method.returnType.simpleName, beanDef)
                }
            }
        }
    }
}

// Spring Boot应用中引入库的方式
@SpringBootApplication
@Import(LibrarySpringRegistrar::class)
class SpringApp

3. Ktor(Koin)适配:桥接Jakarta注解到Koin模块

Koin支持多平台,我们可以写一个Koin模块生成器,扫描库的@LibraryModule配置类,将其中的Bean声明转换成Koin的single/factory定义,同时处理@Inject的依赖注入:

// Koin适配模块(多平台)
import org.koin.core.module.Module
import org.koin.dsl.module
import jakarta.inject.Singleton
import jakarta.inject.Inject

fun libraryKoinModule(): Module = module {
    // JVM可用反射扫描,JS/Native建议手动指定配置类(受反射限制)
    val configClasses = listOf(DataModule::class.java)
    
    configClasses.forEach { configClass ->
        // 注册配置类实例
        single { configClass.getDeclaredConstructor().newInstance() }
        
        // 解析配置类中的Bean方法
        configClass.declaredMethods.filter { it.isAnnotationPresent(Singleton::class.java) }.forEach { method ->
            val beanProvider = {
                val configInstance = get(configClass)
                // 解析方法参数,自动注入Koin中的依赖
                val args = method.parameters.map { param ->
                    if (param.isAnnotationPresent(Inject::class.java)) {
                        get(param.type.kotlin)
                    } else null
                }.filterNotNull()
                method.invoke(configInstance, *args.toTypedArray())
            }
            
            if (method.isAnnotationPresent(Singleton::class.java)) {
                single(method.returnType.kotlin) { beanProvider() }
            } else {
                factory(method.returnType.kotlin) { beanProvider() }
            }
        }
    }
    
    // 直接处理带@Singleton的业务Bean
    single<UserRepository> { JdbcUserRepository() }
    single<UserService> { DefaultUserService(get()) }
}

// Ktor应用中引入库的方式
fun Application.module() {
    install(Koin) {
        modules(libraryKoinModule())
    }
}

关键注意事项

  • 多平台类扫描兼容:JVM可以用反射扫描classpath,JS/Native由于反射能力有限,建议通过编译时注解处理器生成配置类清单,或者让用户手动传入配置类列表。
  • 编译时优化:如果担心运行时扫描的性能开销,可以用KAPT写一个注解处理器,在编译时为Spring生成对应的@Configuration类,为Koin生成对应的Module代码,运行时直接加载生成的代码即可。
  • 依赖隔离:库的核心模块只依赖Jakarta Inject API,Spring和Koin的适配代码分别放在独立的子模块中,用户按需引入即可,完全避免DI框架绑定。

内容的提问来源于stack exchange,提问作者meijuh

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.27 15:17:03