@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 文件夹中找到关于特定用例的其他文档。
您可以在这里找到各种样例


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


最适合小白的AI自动化工作流平台
无需编码,轻松生成可复用、可变现的AI自动化工作流

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


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


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


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


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


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


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


最新版Sora2模型免费使用,一键生成无水印视频
最新版Sora2模型免费使用,一键生成无水印视频
最新AI工具、AI资讯
独家AI资源、AI项目落地

微信扫一扫关注公众号