本节中使用的示例:
val di = DI {
bind<Dice>() with factory { sides:Int -> RandomDice(sides) }
bind<DataSource>() with singleton { SqliteDS.open("path/to/file") }
bind<Random>() with provider { SecureRandom() }
bind<FileAccess>() with factory { path:String,mode:Int -> FileAccess.open(path,mode) }
constant("answer") with "fourty-two"
}
-
检索规则
当依赖绑定类型为
provider
、instance
、singleton
、eagerSingleton
、constant
时,以下规则适用:- 作为一个
provider
:() -> T
- 作为一个
instance
:T
当依赖绑定类型为
factory
、multiton
时,只能作为工厂方法检索:(A) -> T
- 作为一个
factory
:(A) -> T
- 作为一个
provider
:() -> T
,如果检索时提供了参数A - 作为一个
instance
:() -> T
,如果检索时提供了参数A
- 作为一个
-
注入(Injection)与检索(Retrieval)
注入:通过构造函数像该类提供其依赖。
检索:类自身负责获取自己的依赖
使用依赖注入会比较麻烦,因为类不知道它的容器。使用检索相对容易,但是需要将类绑定到Kodein-DI上。
如果开发library,则应该尽可能地使用injection,以避免强迫library用户也使用Kodein-DI。
如果开发应用程序,则应考虑retrieval,因为它更加易于使用并提供了更多的工具。
-
基本方法
不管是使用injection还是retrieval,都可以使用三种相同的名称和参数,这些方法是:
-
instance()
:如果需要一个实例:T
-
provider()
:如果需要一个提供者:() -> T
-
factory()
:如果需要一个工厂:(A) > T
这三个方法都可以使用
tag
:instance(tag = "whatever")
-
-
-
injection
要使用依赖注入:
- 在类的构造函数中声明依赖项
- 使用Kodein-DI的
newInstance
方法创建类对象
-
简单情况
//MainController依赖了DataSource与Random两个对象 class MainController(val ds:DataSource,val rnd:Random) { /*...*/ } //通过injection来获取MainController的实例 val controller by di.newInstance { MainController(instance(),instance(tag = "whatever")) }
如果不确定类是否绑定到Kodein-DI,可以使用
*OrNull
方法。 -
多参数工厂
注入与多参数工厂绑定的值时,必须将参数包装在数据类中:
data class ControllerParams(val path:String,val timeout:Int) val controller by di.newInstance{ FileController(instance(args = ControllerParams("path/to/file",0))) }
-
Currying factories
可以使用arg参数从工厂绑定类型中检索
provider
或instance
://RollController类将构造函数依赖项绑定到工厂 class RollController(val dice:Dice) { /*...*/ } //通过注入其依赖关系来创建RollerController val controller by di.newInstance { RollController(instance(arg = 6)) } //注意,如果需要将工厂与多个参数绑定,则需要使用数据类来封装多个参数 data class Params(val arg1:Int,val arg2:Int) val controller by di.newInstance { RollController(instance(arg = Param(1,1))) }
-
定义上下文
检索时,有时可能需要手动定义上下文。可以使用
on
方法:val controller by di.on(context = myContext).newInstance{ OtherController(instance(arg = 6),instance()) }
有时,上下文在构造的时候不可以直接用,这是可以在定义一个惰性的上下文,仅在需要的时候去访问:
val controller by di.on { requireActivity() } .newInstance { OtherController(instance(arg = 6), instance()) }
-
retrieval (检索DI容器)
-
默认情况下,所有的实例化都是懒加载的
- 仅在实际需要依赖项的时候它们才会被检索
- 只有上下文初始化后,相关的依赖才会被初始化,比如Android的Activity
-
通过
instance
检索绑定类型val diceFactory:(Int)->Dice by di.factory() val dataSource:DataSource by di.instance() val randomProvider:()->Random by di.provider() val answerConstant:String by di.instance(tag = "answer")
如果不确定类型是否已经绑定,可以使用
*OrNull
方法:val diceFactory:((Int)->Dice)? by di.factoryOrNull() val dataSource:DataSource? by di.instanceOrNull() val randomProvide:(()->Random)? by di.providerOrNull() val answerConstant:String? by di.instanceOrNull(tag = "answer")
-
常量
如果绑定了常量,并且名称和类型相匹配,则可以使用
constant()
获取:val answer:String by di.constant()
-
命名绑定
如果使用的是
tag
绑定,并且变量命名与标记匹配,则可使用named
来代替instance()
的arg
参数传递:val di = DI { bind<Foo>(tag = "foo") with provider { Foo1() } } val foo:Foo by di.named.instance()
-
多参数工厂
检索与多参数工厂绑定的值时,必须将参数包装在数据类中:
data class FileParams(val path:String,val maxSize:Int) val fileAccess:FileAccess by di.instance(args = FileParams("/path/to/file",0))
- 获取工厂
//检索接受一个参数(Int)并返回Int的工厂 val f1:(Int) -> Int by di.factory()
-
Currying factories
可以通过
arg
参数检索provider
或instance
:val sixSideDiceProvide:() -> Dice by di.provider(arg = 6) val tewntySideDice:Dice by di.instance(arg = 20)
如果使用多参数绑定工厂,仍然需要使用数据类来传递多个参数:
data class DiceParams(val startNumber:Int,val sides:Int) val sixtyToSixtySixDice:Dice by di.instance(arg = DiceParams(60,6))
-
定义上下文
如果使用作用域,则可能需要指定上下文:
val session:Session by di.on(context = request).instance()
如果使用同一上下文检索多个依赖项,则可以使用上下文对象创建多个对象:
val reqDI = di.on(context = request) val session:Session by reqDI.instance()
-
使用触发器
如果希望在特定时间而不是首次访问时检索依赖项,则可以使用一种机制来决定什么时候来检索,这种机制叫做触发器:
val trigger = DITrigger() val dice:Dice by di.on(trigger = trigger).instance() /* ... */ //强制检索,取消了懒加载 trigger.trigger()
-
延迟访问
kodein-DI提供了
LazyDI
对象,该对象允许仅在需要时才懒加载访问DI对象:val di = LazyDI { /* access to a di instance */ } val ds: DataSource by di.instance() /*...*/ //只有在这个时候,DI实例才会检索 dice.roll()
也可以调用拓展方法实现LazyDI:
val di by DI.lazy{ bind<Env>() with instance(Env.getInstance()) } val env: Env by di.instance() /*...*/ env.doSomething()
-
延迟初始化
kodein-DI提供了
LateInitDI
,它允许在延迟检索之后定义对象:val di = LateInitDI() val env:Env by di.instance() di.baseDI = /* 延迟初始化一个di实例 */ env.doSomething() //如果这一步在di.baseDI 之前运行,则将触发异常
-
匹配所有类型
kodein-DI允许检索给定类型匹配的所有实例
val instances:List<Foo> by di.allInstances()
同理,
allProviders
、allFactories
也支持。
-
-
直接检索
如果不想使用属性委托,可以通过Kodein-DI直接获取,DI大多可用的功能都可以用
DirectDI
获取:val directDI = di.direct val ds:DataSource = directDI.instance() val controller = directDI.newInstance { MainController(instance),instance(tag = "whatever") }
如果打算仅使用
DirectDI
直接访问,则可以将DI定义为DirectDI
:val di = DI.direct { /* bindings */ }
和DI提供的
DIAwave
一样,DirectDI提供了DirectDIAware
:class MyManager(override val directDI:DirectDI):DirectDIAware { private val diceFactory:((Int)->Dice)? = factoryOrNUll() private val dataSource:DataSource? = instanceOrNull() private val randomProvider:()->Dice = di.provider(arg = 6) private val answerConstant:String? = instanceOrNull(tag = "answer") private val sixSideDiceProvider:()->Dice = di.provider(arg = 6) private val twentySideDice:Dice = di.instance(arg = 20) }
在Java中,Kodein-DI不允许使用java声明模块或依赖项。但允许通过DirectDI检索依赖项。只需要将
DirectDI
提供给java类:import sratic org.kodein.di.TypesKt.TT; public class JavaClass { private final Function1(Integer,Dice) diceFactory; private final DataSource dataSource; private final Function0<Random> randomProvider; private final String anserConstant; public JavaClass(DirectDI di){ diceFactory = di.Factory(TT(Integer.class),TT(Dice.class),null); dataSource = di.Instance(TT(DataSource.class),null); randomProvider = di.Provider(TT(Random.class),null); answerConstant = di.Instance(TT(String.class),"answer"); } }
java受类型擦除的约束,如果注册泛型类的Class绑定,例如
bind<List<String>>()
,则要使用TyoedRefrence
来规避Java的类型擦除:class JavaClass{ private final List<String> list; public JavaClass(TypeDI di){ list = di.Instance(TT(new TypeRefrence<List<String>>(){}),null); } }
-
错误消息
默认情况下,Kodein-DI错误消息中包含简单的类名,如果想要返回全类名,可以设置
fullDescriptionOnError
:val di = DI { fullDescriptionOnError = true }
如果创建了多个DI实例,则可以为所有的DI设置默认的
fullDescriptionOnError
://在DI实例创建之前 DI.defaultFullDescriptionOnError = true val di = DI { /* other bindings */ }