【问题标题】:Inject all implementations of a certain trait/class in play using guice使用 guice 注入某个 trait/class 的所有实现
【发布时间】:2018-08-30 18:23:29
【问题描述】:

我有

trait Builder[T, K] {
    def build(brick: T) : K

对于这个特征,我有多个实现...

class StringBuilder extends Builder[Foo, String] { ... }
class HouseBuilder  extends Builder[Baa, House]  { ... }
class BaaBuilder    extends Builder[Baz, Int]    { ... }

根据给定的类型,我想从一种实现中进行选择。像这样的东西(伪代码):

class BuildingComponent @Inject()(builder: Set[Builder]){
    def doIt(item: Any) = {
       item match {
          case _: Foo => builder.filter(Foo).build(item)
          case _: Baa => builder.filter(Baa).build(item)
          case _: Baz => builder.filter(Baz).build(item)
    }
}

所以2分:

  1. 如何注入特征“Builder”的所有实现? 我发现了一堆方向相同的问题(使用multibinder,TypeLiteral等,但没有一个面临注入所有实现的问题。它只是关于“如何注入特定实现”)我知道如何绑定多个实例使用多重绑定器;但如果它是一个泛型类...

  2. 最后我想使用一种外观模式。有一个“构建器”要注入,它会注入所有实现并知道需要什么构建器(参见上面的 match-case-fracment)。但我没有使用匹配案例,而是关注 MapBinder。类似于将 Builder 实现绑定到 Map,它使用类作为键。

例如(伪代码)

class BuildingComponent @Inject()(builder: Map[Class,Builder]){
  def doIt(item: Any) = {
     builder.get(item.class).build(item)
  }
}

【问题讨论】:

    标签: scala generics dependency-injection guice


    【解决方案1】:

    Guice 只初始化它知道的类。因此,您可以将所有实现注入到您的外观中并根据需要对其进行排序。每次添加新实现时,您都需要更改此外观。所以不太好..

    替代方案

    要动态地通知 guice 您的实现,您需要进行一些反思。如果您可以将Builder 作为sealed 特征(获取所有子类的示例,您可以找到here)或使用第三方库(例如reflections),则可以使用标准scala。

    我会解释最后一个案例

    您需要在 build.sbt 中导入:

    libraryDependencies ++= Seq(
      "com.google.inject.extensions" % "guice-multibindings" % "<your guice version>",
      "org.reflections" % "reflections" % "0.9.11")
    

    您需要创建模块并通知 guice,例如如果是播放框架,您需要放入 application.conf

    play.modules.enabled += "com.example.MyModule"
    

    现在,我假设您能够将所有实现放入同一个包中(您可以查看文档如何在其他情况下获取所有实现)。假设它是com.example.builders。同样在我的示例中,我假设您能够将类型参数 T 和 K 移动到类型别名中(使用泛型,绑定和注入将有更多技巧 - 您可以尝试自己找到这种方式)。你的建造者将是:

    trait Builder {
      type T
      type K
      def build(brick: T) : K
    }
    

    现在到你的模块MyModule:

    package com.example
    
    import com.google.inject.AbstractModule
    import com.google.inject.multibindings.Multibinder
    import org.reflections.Reflections
    
    
    class MyModule extends AbstractModule {
      override def configure(): Unit = {
        import scala.collection.JavaConverters._
        val r = new Reflections("com.example.builders")
        val subtypes = r.getSubTypesOf(classOf[Builder])
    
        val executorBinder = Multibinder.newSetBinder(binder(), classOf[Builder])
        subtypes.asScala.foreach {clazz =>
          executorBinder.addBinding().to(clazz)
        }
      }
    }
    

    最后,你可以在需要的地方注入builders: java.util.Set[Builder]。

    更新(添加了如何处理类型化实现的示例)

    例如:你需要额外的类来保存你的砖类型信息。只要Builder 是特征,我就使用abstract class 应该没问题

    import scala.reflect.runtime.universe._
    
    trait Builder[T, K] {
      def build(brick: T): K
    }
    
    abstract class TypeChecker[T: TypeTag] {
      this: Builder[T, _] =>
    
      def isDefinedAt[C: TypeTag](t: C) = {
          typeOf[C] =:= typeOf[T]
      }
    }
    
    class Foo
    class Baa
    class House
    
    // first builder implementation
    class StringBuilder
      extends TypeChecker[Foo]
        with Builder[Foo, String] {
      override def build(brick: Foo) = {
        println("StringBuilder")
        ""
      }
    }
    // second builder implementation
    class HouseBuilder
      extends TypeChecker[Baa]
        with Builder[Baa, House] {
      override def build(brick: Baa) = {
        println("HouseBuilder")
        new House
      }
    }
    
    // our set of builders
    val s: Set[Builder[_, _] with TypeChecker[_]] = Set(
      new StringBuilder,
      new HouseBuilder
    )
    
    
    // here we check and apply arrived brick on our set of builders
    def check[T: TypeTag](t: T) =
      s.filter(_.isDefinedAt(t)).
        foreach {b => b.asInstanceOf[Builder[T, _]].build(t)}
    
    
    check(new Foo)
    check(new Baa)
    

    【讨论】:

    • 感谢您的快速答复。总结一下:guice 不能做我想做的事。另一种选择是自己做事,使用反射,并限制自己,例如将所有内容放在同一个包中,使用密封特征等。因此,如果构建器特征来自库并且不可编辑,我会丢失......?!
    • 我描述了我之前实现的案例。我的特质在我的控制之下,可以改变它以简化我的方式。我只是不想研究类型参数的方式,但这是可能的(了解TypeLiteral,例如答案:stackoverflow.com/questions/24657127/…)。使用相同的包 - 再次,我只是为自己简化了,但是使用 reflections lib 你可以找到不受一个包限制的子类型(请参阅关于扫描仪)。因此,您不受Builder 本身的限制,只需不直接使用我的解决方案,而是将其应用于您的情况。
    • 好的,所以我尝试了您的解决方案,到目前为止似乎可以工作,但有一个例外:使用泛型作为类型别名,似乎不起作用。更具体地说:如何确定正确的实施方式?例如。我有两个构建器的实现。一种用 Foo 和 Baa 覆盖类型,另一种用 Bar 和 Baz 覆盖。在我注入 Set[Builder] 的组件中,我得到了类型信息“Foo”和“Baa”。现在的问题是,如何使用给定的类型信息过滤这个 Set 以获得正确的实现?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-05-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-10-05
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多