chtefi
9/10/2016 - 1:28 AM

Guice's notes

Guice's notes

// In some java..
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
@BindingAnnotation
public @interface MyAnno {}


object Main extends App {
  @MyAnno
  class Dummy {
      def print(s: String*) = { println("print ! " +  s); "dummy" }
  }

  val inj = Guice.createInjector(new AbstractModule {
    override def configure(): Unit = {
      bindInterceptor(Matchers.annotatedWith(classOf[MyAnno]), Matchers.any(), new MethodInterceptor {
        override def invoke(methodInvocation: MethodInvocation): AnyRef = {
          println("calling " + methodInvocation.getMethod.toGenericString + " ARGS:" + methodInvocation.getArguments.mkString("|"))
          methodInvocation.proceed()
          "gotcha".asInstanceOf[AnyRef]
          // we called the real method but return a different result
        }
      })
    }
  })

  println(inj.getInstance(classOf[Dummy]).print("those", "are", "my", "args"))
}

/*
calling public java.lang.String com.example.Main3$Dummy.print(scala.collection.Seq<java.lang.String>) ARGS:WrappedArray(those, are, my, args)
print ! WrappedArray(those, are, my, args)
gotcha
*/
object Main extends App {

  class Test {

    @Inject @Named("v")
    var a: Int = _

    def init() = {
      println("init! " + a)
    }
  }

  val inj = Guice.createInjector(new AbstractModule {
    override def configure(): Unit = {

      bind(classOf[Test])
      bindConstant().annotatedWith(Names.named("v")).to(2) // just to see it has no impct IF we have a proper matches

      // listen to any provision request
      bindListener(Matchers.any(), new ProvisionListener {
        override def onProvision[T](provisionInvocation: ProvisionInvocation[T]): Unit = {
          println("ProvisionInvocation: " + provisionInvocation.getBinding)
        }
      })

      // listener to any binding
      bindListener(Matchers.any(), new TypeListener {
        override def hear[I](typeLiteral: TypeLiteral[I], typeEncounter: TypeEncounter[I]): Unit = {
          println("hear: " + typeLiteral)
        }
      })

      // listener to Test provisions and call .init() when injected
      bindListener(new AbstractMatcher[TypeLiteral[_]] {
        override def matches(t: TypeLiteral[_]): Boolean = classOf[Test].isAssignableFrom(t.getRawType)
      }, new TypeListener {
        override def hear[I](typeLiteral: TypeLiteral[I], typeEncounter: TypeEncounter[I]): Unit = {
          typeEncounter.register(new MembersInjector[I] {
            override def injectMembers(t: I): Unit = {
              println("injectMembers: " + t)
            }
          })
          typeEncounter.register(new InjectionListener[I] {
            override def afterInjection(i: I): Unit = {
              println("afterInjection: " + i)
              i.asInstanceOf[Test].init()
            }
          })
        }
      })

    }
  })

  @Inject
  var t: Test = _

  inj.injectMembers(this)
  println(t)

  /*
  hear: com.example.Main$Test
  hear: java.lang.Integer
  ProvisionInvocation: InstanceBinding{key=Key[type=java.lang.Integer, annotation=@com.google.inject.name.Named(value=v)], source=com.example.Main$$anon$1.configure(Main.scala:29), instance=2}
  hear: com.example.Main$
  ProvisionInvocation: ConstructorBinding{key=Key[type=com.example.Main$Test, annotation=[none]], source=com.example.Main$$anon$1.configure(Main.scala:28), scope=Scopes.NO_SCOPE}
  injectMembers: com.example.Main$Test@49c386c8
  afterInjection: com.example.Main$Test@49c386c8
  init! 2
  com.example.Main$Test@49c386c8
   */
}
// We can "intercept" the binding and do what we want with the injected items (access to the class and instance)

object Main extends App {

  class Test {
    def init() = println("init!")
  }

  val inj = Guice.createInjector(new AbstractModule {
    override def configure(): Unit = {

      bind(classOf[Test])

      bindListener(Matchers.any(), new TypeListener {
        override def hear[I](typeLiteral: TypeLiteral[I], typeEncounter: TypeEncounter[I]): Unit = {
          typeEncounter.register(new MembersInjector[I] {
            override def injectMembers(t: I): Unit = {
              if (classOf[Test].isAssignableFrom(t.getClass)) {
                t.asInstanceOf[Test].init()
              }
            }
          })
        }
      })

    }
  })

  @Inject
  var t: Test = _
  inj.injectMembers(this)
  println(t)
}
// In this example, we are mixing Guice and Jackson to create Guice's modules that exposes Jackson's modules.
// The guice modules depends themselves on some injections

package com.example

import javax.inject.Named

import com.fasterxml.jackson.annotation.{JsonTypeInfo, JsonTypeName}
import com.fasterxml.jackson.databind.{ObjectMapper, Module => JacksonModule}
import com.fasterxml.jackson.databind.module.{SimpleModule => JacksonSimpleModule}
import com.fasterxml.jackson.module.scala.DefaultScalaModule
import com.fasterxml.jackson.module.scala.experimental.ScalaObjectMapper
import com.google.inject.name.Names

import collection.JavaConverters._
import com.google.inject.{Guice, Inject, Key, Provides, AbstractModule => GuiceAbstractModule}
import net.codingwell.scalaguice.{ScalaModule => GuiceScalaModule}
import net.codingwell.scalaguice.InjectorExtensions._

///////////////////////////////////////////////////////////////////////////////

trait ExampleModule extends GuiceAbstractModule {
  def jacksonModules(): List[JacksonModule]
}

@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type")
trait Thing

@JsonTypeName("car")
case class Car(wheels: Int) extends Thing
@JsonTypeName("house")
case class House(rooms: Int) extends Thing
@JsonTypeName("user")
case class User(name: String) extends Thing

///////////////////////////////////////////////////////////////////////////////

class AModule extends ExampleModule with GuiceScalaModule {
  var user: User = _

  override def jacksonModules(): List[JacksonModule] = {
    List(new JacksonSimpleModule("Module A").registerSubtypes(classOf[Car]))
  }
  override def configure() = {
    bind[User].annotatedWithName("son").toInstance(user.copy(s"${user.name}'s son"))
  }

  @Inject
  def setName(user: User) = {
    this.user = user
  }

}

///////////////////////////////////////////////////////////////////////////////

case class Tool(name: String)

class BModule extends ExampleModule {
  override def jacksonModules(): List[JacksonModule] = {
    List(new JacksonSimpleModule("Module B").registerSubtypes(classOf[House]))
  }
  override def configure() = {}

  @Provides
  def providesTool(@Named("n") n: Int): Tool = Tool(n + " wrenchs")
}

///////////////////////////////////////////////////////////////////////////////

object Main extends App {

  val mapper = new ObjectMapper() with ScalaObjectMapper
  mapper.registerModule(DefaultScalaModule)

  // create a root injector that is using a classic Guice Module and inject into the other modules
  val defaultGuiceModule = new GuiceScalaModule {
    override def configure() = {
      bind[Int].annotatedWithName("n").toInstance(5)
      bind[User].toInstance(User("john"))
    }
  }
  val rootInj = Guice.createInjector(defaultGuiceModule)
  val guiceModules = List(new AModule(), new BModule()) // other modules we'll load up in another injector
  guiceModules.foreach(rootInj.injectMembers(_)) // they already have access to the root bindings

  // then, we create another injector with them and register their jackson modules (they are our special type of modules)
  val inj = rootInj.createChildInjector(guiceModules.asJava)
  guiceModules.flatMap(_.jacksonModules()).foreach(mapper.registerModule)

  // we test our mapper is working with the Jackson modules the Guice modules added
  val car = mapper.readValue[Car]("""{ "type": "car", "wheels": 4 }""")
  val house = mapper.readValue[House]("""{ "type": "house", "rooms": 3 }""")
  // we test the injection
  // Tool is @Provides by a module that depends on the value of the root injector
  val tool = inj.instance[Tool]
  // this user is provided by a bind[User] in the other module, still dependant of some root injector value
  val user = inj.getInstance(Key.get(classOf[User], Names.named("son")))

  println(car)
  println(house)
  println(tool)
  println(user)
  /*
  Car(4)
  House(3)
  Tool(5 wrenchs)
  User(john's son)
  */
}
// libraryDependencies += "com.google.inject.extensions" % "guice-multibindings" % "4.1.0"

// In Scala, you want to bind to the scala's Set
// and it also provide scala's shortcuts (no need of classOf[...] as in java, generic classes available)
// libraryDependencies += "net.codingwell" %% "scala-guice" % "4.1.0"

// Multibinder (and other like MapBinder, OptionBinder (scala)) are useful when multiple modules fulfill some collection that will be injected

  trait Item
  case class Car(wheels: Int) extends Item
  case class House @Inject()(@Named("address") address: String) extends Item

  val inj = Guice.createInjector(new Module {
    override def configure(binder: Binder): Unit = {
      binder.bindConstant().annotatedWith(Names.named("address")).to("bind me")
      val multi = ScalaMultibinder.newSetBinder[Item](binder)
      multi.addBinding.toInstance(Car(3))
      multi.addBinding.to[House]
    }
  }, new Module {
    override def configure(binder: Binder): Unit = {
      val anothermulti = ScalaMultibinder.newSetBinder[Item](binder)
      anothermulti.addBinding.toInstance(Car(8))
    }
  }))

  case class Container @Inject() (items: Set[Item])
  
  import net.codingwell.scalaguice.InjectorExtensions._ // instance[] , existingBinding[]
  println(inj.instance[Container])
  println(inj.existingBinding[Container])
  // Container(Set(Car(3), House(bind me), Car(8)))
  // Some(ConstructorBinding{key=Key[type=Main$Container, annotation=[none]], source=class Main$Container, scope=Scopes.NO_SCOPE})
  
// Careful of the @Inject() position, it matters

  class Container {
    @Inject() def this(@Named("conf") value: String) = {
      this()
      println(s"value is $value")
    }
  }
  inj.getInstance(classOf[Container])


  case class CContainer @Inject() (@Named("conf") value: String)
  println(inj.getInstance(classOf[CContainer]))
  val inj = Guice.createInjector(new Module {
    override def configure(binder: Binder): Unit = {
      binder.bindConstant().annotatedWith(Names.named("conf")).to("bind me")
    }
  })

  class Container {
    @Inject @Named("conf") var value: String = _
    override def toString: String = s"value:$value"
  }
  val c = new Container
  println(c)
  // value:null
  inj.injectMembers(c)
  println(c)
  // value:bind me
  val inj = Guice.createInjector(new Module {
    override def configure(binder: Binder): Unit = {
      binder.bindConstant().annotatedWith(Names.named("conf")).to("bind me")
    }
  })

  println(inj.getInstance(Key.get(classOf[String], Names.named("conf"))))
  // "bind me"