是否应允许将Kotlin挂起函数用作合法的CDI生产者/Bean?
Kotlin挂起函数与Jakarta CDI的适配问题
根据Jakarta CDI 2.0(及4.0)规范:
若生产者方法的返回类型包含通配符类型参数,或是组件类型包含通配符类型参数的数组类型,容器会自动检测到该问题并将其视为定义错误。
CDI规范的定义不够精确,不清楚它指的是类定义还是JVM生成的字节码,这会直接影响Kotlin挂起函数的适配逻辑。
Kotlin编译器会将挂起函数转换为延续传递风格(CPS):分析协程的执行上下文,将其转换为带有表示协程状态的局部变量的状态机。由于Kotlin不支持联合类型,转换后会自动添加Continuation和通配符结果(Any?)。
当多个挂起函数处于同一状态机中,与函数调用关联的同一个Continuation对象可能会在它们之间传递,具体取决于多种执行因素。我的理解是,Continuation会在首次调用挂起函数的运行时实例化,不同上下文调用同一函数时会创建不同的Continuation实例。
我基于Kotlin/Arrow、Spring、Quarkus、Micronaut搭建了多个探索项目,将六边形端口定义为类型别名:
typealias AssignKeyboard = suspend (String) -> Either<KeyboardError, Unit> typealias AssignMouse = suspend (String) -> Either<MouseError, Unit>
生产者Bean的定义示例:
@ApplicationScoped // 或@Bean、@Singleton // @Named(BeanName.ASSIGN_KEYBOARD) fun assignLocalKeyboardBean(shellAdapter: ShellAdapter): AssignKeyboard = // 业务实现代码 @ApplicationScoped // 或@Bean、@Singleton // @Named(BeanName.ASSIGN_MOUSE) fun assignLocalMouseBean(shellAdapter: ShellAdapter): AssignMouse = // 业务实现代码
注入点示例:
class KeyboardController( // @Inject // @Named(BeanName.ASSIGN_KEYBOARD) private val assignKeyboard: AssignKeyboard, ) { // 业务实现代码 }
各框架对挂起函数生产者的支持表现存在差异:
- Spring支持将挂起函数用作生产者,无需指定限定符即可正常注入
- Quarkus曾支持通过挂起函数创建生产者(近期已禁用该特性),必须借助限定符才能完成注入,它对该类型的表示示例为:
kotlin.jvm.functions.Function2<String, ? super kotlin.coroutines.Continuation<? super arrow.core.Either<? extends rg.KeyboardError, ? extends kotlin.Unit>>, ?> - Micronaut在未使用
@Named限定符时,会检测到通配符问题并判定为定义错误
核心疑问:
CDI应当如何适配Kotlin挂起函数?挂起函数能否安全应用于CDI场景?是否需要引入智能分析器,在类型校验阶段忽略与注入上下文强绑定的Continuation及其结果?
更多关于Kotlin协程的细节可参考Kotlin官方规范中的异步编程章节。
更新
完整源代码可查看以下仓库:
- cdi-quarkus
- cdi-spring
- cdi-micronaut
内容的提问来源于stack exchange,提问作者Robert Gonciarz
相关产品推荐
相关产品推荐

