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 文件夹中找到关于特定用例的其他文档。

样例

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

编辑推荐精选

Vora

Vora

免费创建高清无水印Sora视频

Vora是一个免费创建高清无水印Sora视频的AI工具

Refly.AI

Refly.AI

最适合小白的AI自动化工作流平台

无需编码,轻松生成可复用、可变现的AI自动化工作流

酷表ChatExcel

酷表ChatExcel

大模型驱动的Excel数据处理工具

基于大模型交互的表格处理系统,允许用户通过对话方式完成数据整理和可视化分析。系统采用机器学习算法解析用户指令,自动执行排序、公式计算和数据透视等操作,支持多种文件格式导入导出。数据处理响应速度保持在0.8秒以内,支持超过100万行数据的即时分析。

AI工具酷表ChatExcelAI智能客服AI营销产品使用教程
TRAE编程

TRAE编程

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

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

AI工具TraeAI IDE协作生产力转型热门
AIWritePaper论文写作

AIWritePaper论文写作

AI论文写作指导平台

AIWritePaper论文写作是一站式AI论文写作辅助工具,简化了选题、文献检索至论文撰写的整个过程。通过简单设定,平台可快速生成高质量论文大纲和全文,配合图表、参考文献等一应俱全,同时提供开题报告和答辩PPT等增值服务,保障数据安全,有效提升写作效率和论文质量。

AI辅助写作AI工具AI论文工具论文写作智能生成大纲数据安全AI助手热门
博思AIPPT

博思AIPPT

AI一键生成PPT,就用博思AIPPT!

博思AIPPT,新一代的AI生成PPT平台,支持智能生成PPT、AI美化PPT、文本&链接生成PPT、导入Word/PDF/Markdown文档生成PPT等,内置海量精美PPT模板,涵盖商务、教育、科技等不同风格,同时针对每个页面提供多种版式,一键自适应切换,完美适配各种办公场景。

AI办公办公工具AI工具博思AIPPTAI生成PPT智能排版海量精品模板AI创作热门
潮际好麦

潮际好麦

AI赋能电商视觉革命,一站式智能商拍平台

潮际好麦深耕服装行业,是国内AI试衣效果最好的软件。使用先进AIGC能力为电商卖家批量提供优质的、低成本的商拍图。合作品牌有Shein、Lazada、安踏、百丽等65个国内外头部品牌,以及国内10万+淘宝、天猫、京东等主流平台的品牌商家,为卖家节省将近85%的出图成本,提升约3倍出图效率,让品牌能够快速上架。

iTerms

iTerms

企业专属的AI法律顾问

iTerms是法大大集团旗下法律子品牌,基于最先进的大语言模型(LLM)、专业的法律知识库和强大的智能体架构,帮助企业扫清合规障碍,筑牢风控防线,成为您企业专属的AI法律顾问。

SimilarWeb流量提升

SimilarWeb流量提升

稳定高效的流量提升解决方案,助力品牌曝光

稳定高效的流量提升解决方案,助力品牌曝光

Sora2视频免费生成

Sora2视频免费生成

最新版Sora2模型免费使用,一键生成无水印视频

最新版Sora2模型免费使用,一键生成无水印视频

下拉加载更多