Kodein-DI 7.0.0(五):依赖注入与检索

本节中使用的示例:

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"
}
  • 检索规则

    当依赖绑定类型为providerinstancesingletoneagerSingletonconstant时,以下规则适用:

    • 作为一个provider() -> T
    • 作为一个instance:T

    当依赖绑定类型为factorymultiton时,只能作为工厂方法检索:(A) -> T

    • 作为一个factory:(A) -> T
    • 作为一个provider:() -> T,如果检索时提供了参数A
    • 作为一个instance:() -> T,如果检索时提供了参数A
  • 注入(Injection)与检索(Retrieval)

    注入:通过构造函数像该类提供其依赖。

    检索:类自身负责获取自己的依赖

    使用依赖注入会比较麻烦,因为类不知道它的容器。使用检索相对容易,但是需要将类绑定到Kodein-DI上。

    如果开发library,则应该尽可能地使用injection,以避免强迫library用户也使用Kodein-DI。

    如果开发应用程序,则应考虑retrieval,因为它更加易于使用并提供了更多的工具。

    • 基本方法

      不管是使用injection还是retrieval,都可以使用三种相同的名称和参数,这些方法是:

      1. instance():如果需要一个实例:T
      2. provider():如果需要一个提供者:() -> T
      3. factory():如果需要一个工厂:(A) > T

      这三个方法都可以使用tag

      instance(tag = "whatever")
      
  • injection

    要使用依赖注入:

    1. 在类的构造函数中声明依赖项
    2. 使用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参数从工厂绑定类型中检索providerinstance

      //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容器)

    • 默认情况下,所有的实例化都是懒加载的
      1. 仅在实际需要依赖项的时候它们才会被检索
      2. 只有上下文初始化后,相关的依赖才会被初始化,比如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参数检索providerinstance

      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()
      

      同理,allProvidersallFactories也支持。

  • 直接检索

    如果不想使用属性委托,可以通过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 */
    }
    
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,185评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,445评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,684评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,564评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,681评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,874评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,025评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,761评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,217评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,545评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,694评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,351评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,988评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,778评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,007评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,427评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,580评论 2 349