Project Icon

kotlin-inject

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

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

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

样例

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

项目侧边栏1项目侧边栏2
推荐项目
Project Cover

豆包MarsCode

豆包 MarsCode 是一款革命性的编程助手,通过AI技术提供代码补全、单测生成、代码解释和智能问答等功能,支持100+编程语言,与主流编辑器无缝集成,显著提升开发效率和代码质量。

Project Cover

AI写歌

Suno AI是一个革命性的AI音乐创作平台,能在短短30秒内帮助用户创作出一首完整的歌曲。无论是寻找创作灵感还是需要快速制作音乐,Suno AI都是音乐爱好者和专业人士的理想选择。

Project Cover

有言AI

有言平台提供一站式AIGC视频创作解决方案,通过智能技术简化视频制作流程。无论是企业宣传还是个人分享,有言都能帮助用户快速、轻松地制作出专业级别的视频内容。

Project Cover

Kimi

Kimi AI助手提供多语言对话支持,能够阅读和理解用户上传的文件内容,解析网页信息,并结合搜索结果为用户提供详尽的答案。无论是日常咨询还是专业问题,Kimi都能以友好、专业的方式提供帮助。

Project Cover

阿里绘蛙

绘蛙是阿里巴巴集团推出的革命性AI电商营销平台。利用尖端人工智能技术,为商家提供一键生成商品图和营销文案的服务,显著提升内容创作效率和营销效果。适用于淘宝、天猫等电商平台,让商品第一时间被种草。

Project Cover

吐司

探索Tensor.Art平台的独特AI模型,免费访问各种图像生成与AI训练工具,从Stable Diffusion等基础模型开始,轻松实现创新图像生成。体验前沿的AI技术,推动个人和企业的创新发展。

Project Cover

SubCat字幕猫

SubCat字幕猫APP是一款创新的视频播放器,它将改变您观看视频的方式!SubCat结合了先进的人工智能技术,为您提供即时视频字幕翻译,无论是本地视频还是网络流媒体,让您轻松享受各种语言的内容。

Project Cover

美间AI

美间AI创意设计平台,利用前沿AI技术,为设计师和营销人员提供一站式设计解决方案。从智能海报到3D效果图,再到文案生成,美间让创意设计更简单、更高效。

Project Cover

稿定AI

稿定设计 是一个多功能的在线设计和创意平台,提供广泛的设计工具和资源,以满足不同用户的需求。从专业的图形设计师到普通用户,无论是进行图片处理、智能抠图、H5页面制作还是视频剪辑,稿定设计都能提供简单、高效的解决方案。该平台以其用户友好的界面和强大的功能集合,帮助用户轻松实现创意设计。

投诉举报邮箱: service@vectorlightyear.com
@2024 懂AI·鲁ICP备2024100362号-6·鲁公网安备37021002001498号