kotlin-inject
@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
注解分别将多个绑定收集到 Map
或 Set
中。
对于集合,返回你想放入集合的类型,然后你可以注入或提供一个 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 文件夹中找到关于特定用例的其他文档。
样例
您可以在这里找到各种样例