Is there a nice Play2 way of injecting instances in Play Plugins with Guice - scala

I'm trying to figure out how to inject my classes with Google Guice into a play.api.Plugin.
I have implemented Guice to work with my controllers and it works great.
I use:
"com.google.inject" % "guice" % "4.0-beta",
"com.tzavellas" % "sse-guice" % "0.7.1"
When a Controller instance is needed the getControllerInstance method in Global will load the appropriate implementation thanks to the injector.
Global:
object Global extends GlobalSettings {
/**
* Currently we only want to load a different module when test.
*/
private lazy val injector = {
Logger.info("Is Test: "+Play.isTest)
Play.isTest match {
case true => Guice.createInjector(new TestModule)
case false => Guice.createInjector(new CommonModule)
}
}
override def onStart(app: Application) {
Logger.info("Application has started")
}
override def onStop(app: Application) {
Logger.info("Application shutdown...")
}
override def getControllerInstance[A](clazz: Class[A]) = {
Logger.info("getControllerInstance")
injector.getInstance(clazz)
}
}
Common:
package modules
import com.tzavellas.sse.guice.ScalaModule
import services.{CallServiceImpl, CallService}
/**
* User: jakob
* Date: 11/5/13
* Time: 10:04 AM
*/
class CommonModule extends ScalaModule {
def configure() {
bind[CallService].to[CallServiceImpl]
}
}
class TestModule extends ScalaModule {
def configure() {
// Test modules!
}
}
Controller:
#Singleton
class StatsController #Inject()(callService: CallService) extends Controller with securesocial.core.SecureSocial with ProvidesHeader {
def doSomething = {
callService.call()
}
}
Now I would like to inject the same service into my Plugin, but I can't make use of the Global implementation since the plugins do not load with the getControllerInstance
class CallerPlugin (application: Application) extends Plugin {
val secondsToWait = {
import scala.concurrent.duration._
10 seconds
}
val defaultInterval = 60
val intervalKey = "csv.job.interval"
val csvParserEnabled = "csv.job.enabled"
val newDir = "csv.job.new.file.path"
val doneDir = "csv.job.done.file.path"
var cancellable: Option[Cancellable] = None
override def onStop() {
cancellable.map(_.cancel())
}
override def onStart() {
// do some cool injection of callService here!!!
import scala.concurrent.duration._
import play.api.libs.concurrent.Execution.Implicits._
val i = current.configuration.getInt(intervalKey).getOrElse(defaultInterval)
cancellable = if (current.configuration.getBoolean(csvParserEnabled).getOrElse(false)) {
Some(
Akka.system.scheduler.schedule(0 seconds, i minutes) {
callService.call()
})
} else None
}
}
I guess there should be a way of implementing the injection in the onStart method somehow and there is probably some nice easy way of doing this but I can't figure it out.
Thank you!

If I understood your question correctly, you're wondering how to instantiate and use a Guice injector. Well it's really simple:
val injector = Guice.createInjector(new CommonModule)
val callService = injector.getInstance(classOf[CallService])
And like that you have an instance of CallServiceImpl. If you look at your Global.scala, this is exactly what you do there. I haven't used Play plugins, so I'm not sure how you instantiate them, but I think a more idiomatic way would be, instead of putting it in plugin's onStart, to inject this CallService as a parameter to CallerPlugin (like you do for the controller). That way you could pass the responsibility of dependency injection down the tree, so that ideally you would end up with only one injector (probably in Global).

From what I found so far the best way to achieve this is to set plugin's dependency in Global.onStart().
public class Global extends GlobalSettings{
public Injector injector = createInjector();
private Injector createInjector(){
return Guice.createInjector(new SomeGuiceModule());
}
#Override
public void onStart(Application application) {
CallerPlugin plugin = application.plugin(CallerPlugin.class);
plugin.setCallService(injector.getInstance(CallService.class));
}
}
Make sure that plugin number is lower that 10000. Global has 10000, so, your plugin will be started before Global.onStart() gets called.

Related

Creating a AbstractModule to inject a dependency for a 3rd party library

I have a 3rd party library that I am trying to inject the configuration into the constructor.
This is what I need to do:
class MyModule(configuration: Configuration) extends AbstractModule {
override def configure(): Unit = {
bind(classOf[TwitterApi])
.to(classOf[MyTwitterApi])
.asEagerSingleton
}
}
The constructor of MyTwitterApi doesn't take a Play.api.Configuration but a typesafe.config.Config
class MyTwitterApi(config: Config) ...
So I need to do pass configuration.underlying to my constructor, how is this possible using DI in this AbstractModule?
I need this instance to be a singleton also.
You can use provider to setup your module with eagerSingleton
import com.google.inject.{AbstractModule, Provider}
class MyModule(configuration: Configuration) extends AbstractModule {
override def configure(): Unit = {
val twitterApiProvider: Provider[TwitterApi] =
() => new MyTwitterApi(configuration.underlying)
bind(classOf[TwitterApi])
.toProvider(twitterApiProvider)
.asEagerSingleton
}
}
You can find a working example with sample classes at - https://scastie.scala-lang.org/sarveshseri/ujwvJJNnTpiWDqdkBJQoFw/2
I think you want something like this:
class MyModule(configuration: Configuration) extends AbstractModule {
override def configure(): Unit = {
val myTwitterApiInstance = new MyTwitterApi(configuration.underlying)
bind(classOf[TwitterApi])
.toInstance(myTwitterApiInstance)
}
}
Or another approach would be to provide a binding for Config but if your MyTwitterApi doesn't have #Inject annotation this won't help.

Dependancy Injection of Google guice doesnt work in Scala

I want to use DI google guice which works perfectly fine in Java but doesnt work in case of scala. Here is my code:
Module:
class ConfigModule extends AbstractModule{
override def configure(): Unit = {
}
#Provides
#Named("config")
def getConfig(): Config = {
new Config
}
}
Config
class Config {
val config1: String = "Sample"
}
Service
class Service(#Named("config") config:Config) {
def read(): Unit = {
println("Reading")
}
}
Main Class
object SampleJob {
def main(args: Array[String]): Unit = {
val injector = Guice.createInjector(new ConfigModule)
val service = injector.getInstance(classOf[Service])
service.read()
}
}
Error:
1) Could not find a suitable constructor in com.job.Service. Classes must have either one (and only one) constructor annotated with #Inject or a zero-argument constructor that is not private.
at com.job.Service.class(Service.scala:7)
while locating com.job.Service
Where am I mistaken?
UPDATE:
class Service(#Inject #Named("config") config:Config) {
def read(): Unit = {
println("Reading")
}
}
This also returns the same error
Could not find a suitable constructor in com.job.Service. Classes must have either one (and only one) constructor annotated with #Inject or a zero-argument constructor that is not private.
at com.job.Service.class(Service.scala:8)
while locating com.job.Service
The error tells you what happened:
Classes must have either one (and only one) constructor annotated with #Inject or a zero-argument constructor that is not private
So you need to add #Inject annotation... on your constructor like in tutorial.
Instead of
class Service(#Inject #Named("config") config:Config) {
def read(): Unit = {
println("Reading")
}
}
it should be
class Service #Inject() (#Named("config") config:Config) {
def read(): Unit = {
println("Reading")
}
}

Guice dependency requiring references to Singleton inside AbstractModule

I have an external library (scala-redis) that requires an implicit ActorSystem when initializing the client. I would like to have my RedisClient as a Singleton inside my Play (2.6) Application because it would make sense having it as a Singleton.
class CustomAppModule(environment: Environment,
configuration: Configuration) extends AbstractModule {
def configure() = {
//val system = getProvider(classOf[ActorSystem]).get()
//val system = ActorSystem()
//bind(classOf[ActorSystem]).toInstance(system)
val redis = RedisClient(configuration.get[String]("redis.host"))(system)
bind(classOf[RedisClient]).toInstance(redis)
}
}
First system fails because of "Provider cannot be used until the Injector has been created", and second system fails because Play Framework initializes the ActorSystem itself when the application starts, and second system fails because of "binding to akka.actor.ActorSystem was already configured at play.api.inject.BuiltinModule".
So what would be the idiomatic way with Guice/DI to proceed with this kind of situation? Do I need a wrapper Singleton that has the RedisClient as a value, and where the ActorSystem can be injected?
I think the provides method will solve your problem. Write your module as
class MyModule extends AbstractModule {
def configure() = {
}
#Provides
#Singleton
def givePrecious() : MyClass = {
new MyClass()
}
}
Here my Class looks like
#Singleton
class MyClass(a: String) {
def this() = {
this("a")
println("constructor called")
}
}
Now I try to create 3 instances of this class
val injector = Guice.createInjector(new MyModule())
val precious1 = injector.getInstance(classOf[MyClass])
val precious2 = injector.getInstance(classOf[MyClass])
val precious3 = injector.getInstance(classOf[MyClass])
You will see that the string "constructor called" is printed only once.
For sake of simplicity I have make a as a string. you can try to make it an instance of ActorSystem.

Inject configuration value in play framework using Guice

I have play web application with conf/application.conf (nothing unusual). Guice is used for dependency injection. How can property value be injected in class constructor? The code is below.
class MyController #Inject() (private val foo: Foo) extends Controller {
...
}
#ImplementedBy(classOf[FooImpl])
trait Foo {
def bar: String
}
class FooImpl extends Foo {
override val bar = current.configuration.getString("my.bar").get
...
}
In the current configuration FooImpl can't be tested without running application. I want to be able instantiate FooImpl in unit tests. The perfect solution [from my point of view] should look like that:
class FooImpl #Inject() (#Named("my.bar") override val bar: String) extends Foo {
...
}
Unfortunately, this code doesn't work, because Guice doesn't have 'my.bar' binding:
No implementation for java.lang.String annotated with #com.google.inject.name.Named(value=my.bar) was bound.
The only solution that I came up with is writing my own module, which iterates through configuration properties and binds them as named dependencies (a variation of the example from this doc). But I believe that a better approach exists.
I implemented that using Java. I hope you can use it as reference for your Scala implementation.
At first, I created a Module:
public class MainModule extends AbstractModule {
public final static String TIMEOUT_IN_MILLISECONDS_ANNOTATION = "timeout-promise";
private final Configuration configuration;
public MainModule(#SuppressWarnings("unused") Environment environment, Configuration configuration) {
this.configuration = configuration;
}
#Override
protected void configure() {
long timeoutInMilliseconds = configuration.getLong("application.promise.timeout.in.milliseconds", 0L);
bindConstant().annotatedWith(Names.named(TIMEOUT_IN_MILLISECONDS_ANNOTATION)).to(timeoutInMilliseconds);
}
}
After that, I just used the annotation on different places:
class Service {
#Inject
#Named(MainModule.TIMEOUT_IN_MILLISECONDS_ANNOTATION)
protected long timeoutInMilliseconds;
}
Hope this helps.
Some time ago i was developed small guice extention for simple injection configuration variables mapped on Enum
guice-config
I encountered the same problem after about a year, and this time come up with the following solution (very similar to the one proposed by #stranger-in-the-q and #droidman):
class InjectionModule extends AbstractModule {
override def configure(): Unit = {
val config: Config = TypesafeConfigReader.config
config.entrySet().asScala.foreach { entry =>
val path = entry.getKey
entry.getValue.valueType() match {
case ConfigValueType.NUMBER =>
bind(classOf[Int])
.annotatedWith(Names.named(path))
.toInstance(config.getInt(path))
case ConfigValueType.BOOLEAN =>
bind(classOf[Boolean])
.annotatedWith(Names.named(path))
.toInstance(config.getBoolean(path))
case ConfigValueType.STRING =>
bind(classOf[String])
.annotatedWith(Names.named(path))
.toInstance(config.getString(path))
case _ =>
}
}
}
}
Also, this approach can be extended by appending prefixes to system properties (which key-value pairs are part of the loaded config):
private def getPrefix(configValue: ConfigValue): String = {
val description = configValue.origin().description()
if (description.contains("system properties")) {
"sys."
} else {
""
}
}
In this case instead of writing Names.named(path) one should use Names.named(getPrefix(entry.getValue) + path).
To inject multiple properties from a play configuration you could do this way. Create a map out of Play configuration and pass it as Properties to Guice binder.
public class Module extends AbstractModule {
private Environment environment;
private Configuration configuration;
public Module(Environment environment,Configuration configuration){
this.environment = environment;
this.configuration = configuration;
}
#Override
public void configure() {
Configuration helloConf = configuration.getConfig("myconfig");
Map<String, Object> map = helloConf.asMap();
Properties properties = new Properties();
properties.putAll(map);
Names.bindProperties(binder(), properties);
}
}

Inject in scala object

I'm using Play framework 2.5 and try to inject WSClient in a scala object used in my controllers.
import play.api.libs.concurrent.Execution.Implicits.defaultContext
object MyObject {
#Inject
var ws: WSClient = null
def doSomething() = { // use wsclient }
}
I use MyObject in several controllers and when calling doSomething() wsclient is null.
You should define MyObject as class and inject wsclient to it:
class MyObject #Inject()(ws: WSClient) {
def doSomething() = { /* use wsclient */ }
}