美文网首页
Kodein-DI 7.0.0(五):依赖注入与检索

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

作者: 何意悲欢0_o | 来源:发表于2020-08-10 13:36 被阅读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 */
      }
      

    相关文章

      网友评论

          本文标题:Kodein-DI 7.0.0(五):依赖注入与检索

          本文链接:https://www.haomeiwen.com/subject/uofjdktx.html