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"