kotlin-inject

kotlin-inject

优化Kotlin项目依赖管理的编译时注入框架

kotlin-inject是一个专为Kotlin设计的编译时依赖注入库。该框架通过注解实现依赖注入,支持组件、作用域和限定符等功能。它提供灵活API以管理复杂依赖关系,支持多平台开发,并具有出色性能。kotlin-inject适用于不同规模的Kotlin项目,能有效简化依赖管理流程。

kotlin-inject依赖注入编译时KotlinKSPGithub开源项目

kotlin-inject

CircleCI Maven Central Sonatype Snapshot 一个用于Kotlin的编译时依赖注入库。

@Component abstract class AppComponent { abstract val repo: Repository @Provides protected fun jsonParser(): JsonParser = JsonParser() protected val RealHttp.bind: Http @Provides get() = this } interface Http @Inject class RealHttp : Http @Inject class Api(private val http: Http, private val jsonParser: JsonParser) @Inject class Repository(private val api: Api)
val appComponent = AppComponent::class.create() val repo = appComponent.repo

下载

使用 ksp

settings.gradle

pluginManagement { repositories { gradlePluginPortal() mavenCentral() } }

build.gradle

plugins { id("org.jetbrains.kotlin.jvm") version "1.9.0" id("com.google.devtools.ksp") version "1.9.0-1.0.13" } repositories { mavenCentral() google() } dependencies { ksp("me.tatarka.inject:kotlin-inject-compiler-ksp:0.7.1") implementation("me.tatarka.inject:kotlin-inject-runtime:0.7.1") }

使用方法

让我们逐行查看上面的示例,看看它都做了什么。

@Component abstract class AppComponent {

kotlin-inject的基本构建块是一个组件,你可以通过在抽象类上使用@Component注解来声明它。这个组件的实现将会为你自动生成。

abstract val repo: Repository

在你的组件中,你可以声明抽象的只读属性或函数来返回指定类型的实例。这里就是魔法发生的地方。kotlin-inject会在生成的实现中为你找出如何构造该类型。它是如何知道怎么做的呢?有几种方式:

@Provides protected fun jsonParser(): JsonParser = JsonParser()

对于外部依赖,你可以在组件中声明一个函数或只读属性来为某个类型创建实例。kotlin-inject将使用返回类型来提供这个实例,以满足需求的地方。 注意:始终明确声明返回类型是一个好习惯,这样可以清楚地知道提供的是什么类型。它可能并不总是你所期望的!

protected val RealHttp.bind: Http @Provides get() = this

你可以为提供函数/属性声明参数,以帮助你构造实例。这里我们接收一个RealHttp的实例,并将其作为Http接口的实现提供。你可以看到这里有一点语法糖,扩展函数/属性的接收者类型被视为一个参数。另一种写法是:

@Provides fun http(http: RealHttp): Http = http
@Inject class RealHttp : Http @Inject class Api(private val http: Http, private val jsonParser: JsonParser) @Inject class Repository(private val api: Api)

对于你自己的依赖,你只需要用@Inject注解标记类即可。这将使用主构造函数来创建实例,不需要其他配置!

val appComponent = AppComponent::class.create() val repo = appComponent.repo

最后,你可以使用生成的.create()扩展函数来创建组件的实例。

特性

组件参数

如果需要将任何实例传递到组件中,可以将它们声明为构造函数参数。然后可以将它们传递给生成的 create 函数。你可以选择用 @Provides 注解它,以将该值提供给依赖图。

@Component abstract class MyComponent(@get:Provides protected val foo: Foo)
MyComponent::class.create(Foo())

如果参数是另一个组件,可以用 @Component 注解它,它的依赖也将对子组件可用。这允许你将它们组合成一个图。

@Component abstract class ParentComponent { @Provides fun provideFoo(): Foo = ... } @Component abstract class ChildComponent(@Component val parent: ParentComponent) { abstract val foo: Foo }
val parent = ParentComponent::class.create() val child = ChildComponent::class.create(parent)

限定符

如果你有多个相同类型的实例需要区分,可以使用 @Qualifier。它们在注入时将被视为不同的类型。它们可以放在变量或类型上。

@Qualifier @Target( AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.FUNCTION, AnnotationTarget.VALUE_PARAMETER, AnnotationTarget.TYPE ) annotation class Named(val value: String) @Component abstract class MyComponent { @Provides fun dep1(): @Named("one") Dep = Dep("one") @Provides fun dep2(): @Named("two") Dep = Dep("two") @Provides fun provides(@Named("one") dep1: Dep, @Named("two") dep2: Dep): Thing = Thing(dep1, dep2) } @Inject class InjectedClass(@Named("one") dep1: Dep, @Named("two") dep2: Dep)

类型别名支持

另外,不同的类型别名将被视为不同的类型。(注意:这在未来的版本中将被移除,因此请考虑使用 @Qualifier 注解代替。将会提供迁移路径。)

typealias Dep1 = Dep typealias Dep2 = Dep @Component abstract class MyComponent { @Provides fun dep1(): Dep1 = Dep("one") @Provides fun dep2(): Dep2 = Dep("two") @Provides fun provides(dep1: Dep1, dep2: Dep2): Thing = Thing(dep1, dep2) } @Inject class InjectedClass(dep1: Dep1, dep2: Dep2)

函数注入

你也可以使用类型别名来注入顶层函数。用 @Inject 注解你的函数,并创建一个同名的类型别名。

typealias myFunction = () -> Unit @Inject fun myFunction(dep: Dep) { }

然后你可以在任何地方使用这个类型别名,你将得到一个调用顶层函数并带有请求的依赖的函数。

@Inject class MyClass(val myFunction: myFunction) @Component abstract class MyComponent { abstract val myFunction: myFunction }

你可以选择将显式参数作为函数的最后几个参数传递。

typealias myFunction = (String) -> String @Inject fun myFunction(dep: Dep, arg: String): String = ...

作用域

默认情况下,kotlin-inject 会在每个注入的地方创建一个新的依赖实例。如果你想重用一个实例,你可以将它限定在一个组件的作用域内。该实例将与该组件的生命周期相同。

首先创建你的作用域注解。

@Scope @Target(CLASS, FUNCTION, PROPERTY_GETTER) annotation class MyScope

然后用该作用域注解标注你的组件。

@MyScope @Component abstract class MyComponent()

最后,用该作用域标注你的 provides 和 @Inject 类。

@MyScope @Component abstract class MyComponent { @MyScope @Provides protected fun provideFoo(): Foo = ... } @MyScope @Inject class Bar()

组件继承

你可以在未标注 @Component 的接口或抽象类上定义 @Provides 和作用域注解。这允许你有多个实现,这对测试等场景很有用。例如,你可以有一个这样的抽象类:

@NetworkScope abstract class NetworkComponent { @NetworkScope @Provides abstract fun api(): Api }

然后你可以有多个实现

@Component abstract class RealNetworkComponent : NetworkComponent() { override fun api(): Api = RealApi() } @Component abstract class TestNetworkComponent : NetworkComponent() { override fun api(): Api = FakeApi() }

然后你可以将抽象类提供给你的应用组件

@Component abstract class AppComponent(@Component val network: NetworkComponent)

然后在你的应用中,你可以这样做

AppComponent::class.create(RealNetworkComponent::class.create())

在测试中你可以这样做

AppComponent::class.create(TestNetworkComponent::class.create())

多重绑定

你可以使用 @IntoMap@IntoSet 注解分别将多个绑定收集到 MapSet 中。 对于集合,返回你想放入集合的类型,然后你可以注入或提供一个 Set<MyType>

@Component abstract class MyComponent { abstract val allFoos: Set<Foo> @IntoSet @Provides protected fun provideFoo1(): Foo = Foo("1") @IntoSet @Provides protected fun provideFoo2(): Foo = Foo("2") }

对于映射,返回一个 Pair<Key, Value>

@Component abstract class MyComponent { abstract val fooMap: Map<String, Foo> @IntoMap @Provides protected fun provideFoo1(): Pair<String, Foo> = "1" to Foo("1") @IntoMap @Provides protected fun provideFoo2(): Pair<String, Foo> = "2" to Foo("2") }

函数支持和辅助注入

有时你想延迟依赖的创建或手动提供额外的参数。你可以通过注入返回依赖的函数而不是直接注入依赖来实现这一点。

最简单的情况是你不接受任何参数,这给你一个可以创建依赖的函数。

@Inject class Foo @Inject class MyClass(fooCreator: () -> Foo) { init { val foo = fooCreator() } }

如果你定义了参数,你可以使用这些参数来辅助依赖的创建。为此,用 @Assisted 注解标记这些参数。函数应该接受相同数量的辅助参数,顺序相同。

@Inject class Foo(bar: Bar, @Assisted arg1: String, @Assisted arg2: String) @Inject class MyClass(fooCreator: (arg1: String, arg2: String) -> Foo) { init { val foo = fooCreator("1", "2") } }

延迟加载

同样,你可以注入一个 Lazy<MyType> 来懒惰地构造和重用一个实例。

@Inject class Foo @Inject class MyClass(lazyFoo: Lazy<Foo>) { val foo by lazyFoo }

默认参数

你可以为注入的参数使用默认参数。如果类型存在于图中,它将被注入,否则将使用默认值。

@Inject class MyClass(val dep: Dep = Dep("default")) @Component abstract class ComponentWithDep { abstract val myClass: MyClass @Provides fun dep(): Dep = Dep("injected") } @Component abstract class ComponentWithoutDep { abstract val myClass: MyClass } ComponentWithDep::class.create().myClass.dep // Dep("injected") ComponentWithoutDep::class.create().myClass.dep // Dep("default")

选项

你可以为处理器提供一些额外的选项。

  • me.tatarka.inject.enableJavaxAnnotations=true 除了提供的注解外,还可以使用 @javax.inject.* 注解。如果你正在迁移现有代码或想要从你在 JVM 上使用的注入库中抽象出来,这可能很有用。

  • me.tatarka.inject.generateCompanionExtensions=true 这将在伴生对象上生成 create() 方法,而不是在组件的类上。这允许你做 MyComponent.create() 而不是 MyComponent::class.create()。但是,由于 Kotlin 的限制,你将不得不为你的组件显式指定一个伴生对象。

@Component abstract class MyComponent { companion object }
  • me.tatarka.inject.dumpGraph=true 这将在构建时打印出依赖图。这可能有助于调试问题。

附加文档

你可以在 docs 文件夹中找到关于特定用例的其他文档。

样例

您可以在这里找到各种样例

编辑推荐精选

Keevx

Keevx

AI数字人视频创作平台

Keevx 一款开箱即用的AI数字人视频创作平台,广泛适用于电商广告、企业培训与社媒宣传,让全球企业与个人创作者无需拍摄剪辑,就能快速生成多语言、高质量的专业视频。

即梦AI

即梦AI

一站式AI创作平台

提供 AI 驱动的图片、视频生成及数字人等功能,助力创意创作

扣子-AI办公

扣子-AI办公

AI办公助手,复杂任务高效处理

AI办公助手,复杂任务高效处理。办公效率低?扣子空间AI助手支持播客生成、PPT制作、网页开发及报告写作,覆盖科研、商业、舆情等领域的专家Agent 7x24小时响应,生活工作无缝切换,提升50%效率!

TRAE编程

TRAE编程

AI辅助编程,代码自动修复

Trae是一种自适应的集成开发环境(IDE),通过自动化和多元协作改变开发流程。利用Trae,团队能够更快速、精确地编写和部署代码,从而提高编程效率和项目交付速度。Trae具备上下文感知和代码自动完成功能,是提升开发效率的理想工具。

热门AI工具生产力协作转型TraeAI IDE
蛙蛙写作

蛙蛙写作

AI小说写作助手,一站式润色、改写、扩写

蛙蛙写作—国内先进的AI写作平台,涵盖小说、学术、社交媒体等多场景。提供续写、改写、润色等功能,助力创作者高效优化写作流程。界面简洁,功能全面,适合各类写作者提升内容品质和工作效率。

AI助手AI工具AI写作工具AI辅助写作蛙蛙写作学术助手办公助手营销助手
问小白

问小白

全能AI智能助手,随时解答生活与工作的多样问题

问小白,由元石科技研发的AI智能助手,快速准确地解答各种生活和工作问题,包括但不限于搜索、规划和社交互动,帮助用户在日常生活中提高效率,轻松管理个人事务。

聊天机器人AI助手热门AI工具AI对话
Transly

Transly

实时语音翻译/同声传译工具

Transly是一个多场景的AI大语言模型驱动的同声传译、专业翻译助手,它拥有超精准的音频识别翻译能力,几乎零延迟的使用体验和支持多国语言可以让你带它走遍全球,无论你是留学生、商务人士、韩剧美剧爱好者,还是出国游玩、多国会议、跨国追星等等,都可以满足你所有需要同传的场景需求,线上线下通用,扫除语言障碍,让全世界的语言交流不再有国界。

讯飞智文

讯飞智文

一键生成PPT和Word,让学习生活更轻松

讯飞智文是一个利用 AI 技术的项目,能够帮助用户生成 PPT 以及各类文档。无论是商业领域的市场分析报告、年度目标制定,还是学生群体的职业生涯规划、实习避坑指南,亦或是活动策划、旅游攻略等内容,它都能提供支持,帮助用户精准表达,轻松呈现各种信息。

热门AI工具AI办公办公工具讯飞智文AI在线生成PPTAI撰写助手多语种文档生成AI自动配图
讯飞星火

讯飞星火

深度推理能力全新升级,全面对标OpenAI o1

科大讯飞的星火大模型,支持语言理解、知识问答和文本创作等多功能,适用于多种文件和业务场景,提升办公和日常生活的效率。讯飞星火是一个提供丰富智能服务的平台,涵盖科技资讯、图像创作、写作辅助、编程解答、科研文献解读等功能,能为不同需求的用户提供便捷高效的帮助,助力用户轻松获取信息、解决问题,满足多样化使用场景。

模型训练热门AI工具内容创作智能问答AI开发讯飞星火大模型多语种支持智慧生活
Spark-TTS

Spark-TTS

一种基于大语言模型的高效单流解耦语音令牌文本到语音合成模型

Spark-TTS 是一个基于 PyTorch 的开源文本到语音合成项目,由多个知名机构联合参与。该项目提供了高效的 LLM(大语言模型)驱动的语音合成方案,支持语音克隆和语音创建功能,可通过命令行界面(CLI)和 Web UI 两种方式使用。用户可以根据需求调整语音的性别、音高、速度等参数,生成高质量的语音。该项目适用于多种场景,如有声读物制作、智能语音助手开发等。

下拉加载更多