I would like to eagerly initialize a singleton actor. I currently do the below and then later in my app startup get the instance of the actor.
`bind[Actor].annotatedWith(Names.named(LockCoordinator.name)).to[LockCoordinator].in[Singleton]`
I have tried
bind[Actor].annotatedWith(Names.named(LockCoordinator.name)).to[LockCoordinator].asEagerSingleton()
but fails at runtime with
1) Error injecting constructor, akka.actor.ActorInitializationException: You cannot create an instance of [LockCoordinator] explicitly using the constructor (new). You have to use one of the 'actorOf' factory methods to create a new actor. See the documentation.
I do create an eagerSingleton already for the system, but couldn't figure out how to apply it for an Actor (not ActorRef)
class ActorSystemProvider #Inject() (val config: Config, val injector: Injector) extends Provider[ActorSystem] {
override def get() = {
val system = ActorSystem(config.getString("mysystem"), config)
GuiceAkkaExtension(system).initialize(injector)
system
}
}
Is there a boilerplate free way of achieving this? As I want to apply this to 3-4 other actors
Does this help?
https://gist.github.com/fancellu/e4e8acdc3d7fd3b9d749352f9d6c68e3
import actors.ActorBrowserActor
import com.google.inject.AbstractModule
import play.api.libs.concurrent.AkkaGuiceSupport
class Module extends AbstractModule with AkkaGuiceSupport{
def configure(): Unit = {
bindActor[ActorBrowserActor](ActorBrowserActor.NAME, _=>ActorBrowserActor.props)
}
}
here we inject the actor by name
class SampleController #Inject()(implicit system: ActorSystem, val messagesApi: MessagesApi, #Named("actor-browser-actor") actorBrowserActor: ActorRef)
extends Controller with I18nSupport{
}
Related
In my project, I have to use actors and schedulers. All my need can be accomplished by using one actor system only. But I need the schedulers and actors in more than one class. So my question, if I should inject the actor system in each class like below, will it create only one actor System or more than one? I don't want to create more than one actor system since it is not a recommended practice.
import akka.actor.ActorSystem
#Singleton
class someClass #Inject()(actorSystem: ActorSystem){....} // abstract class ActorSystem extends ActorRefFactory
#Singleton
class anotherClass #Inject()(actorSystem: ActorSystem){....}
Or I should create another object and declare an actor system within it and use it everywhere like this:
import akka.actor._
object actorSystemObject {
val system: ActorSystem = ActorSystem()
}
Which method will be better and standard?
Assuming you are using guice, try providing a singleton like so
#Provides
#Singleton
def getActorSystem: ActorSystem = ActorSystem()
for example
import akka.actor.ActorSystem
import com.google.inject.{AbstractModule, Guice, Inject, Injector, Provides, Singleton}
import scala.jdk.CollectionConverters._
class MyModule extends AbstractModule {
#Provides
#Singleton
def getActorSystem: ActorSystem = ActorSystem()
}
#Singleton
class SomeClass #Inject()(actorSystem: ActorSystem) {
println(actorSystem.hashCode())
}
#Singleton
class SomeOtherClass #Inject()(actorSystem: ActorSystem) {
println(actorSystem.hashCode())
}
object Hello extends App {
val injector: Injector = Guice.createInjector(List(new MyModule).asJava)
injector.getInstance(classOf[SomeClass])
injector.getInstance(classOf[SomeOtherClass])
}
which outputs something like
1731656333
1731656333
where we see the same ActorSystem is injected as evident by the same hashCode.
Say we remove #Singleton provider like so
#Provides
def getActorSystem: ActorSystem = ActorSystem()
then hashCodes differ, for example,
2050462663
1117871068
I am using Scala Play 2.6 and trying to use dependency injection to instantiate a service class based on request parameter. As below example code, the controller class get payment method from query string
package controllers
import com.google.inject.Inject
import play.api.mvc._
import scala.concurrent.ExecutionContext
class PaymentController #Inject()()
(implicit ec: ExecutionContext)
extends InjectedController {
def doPayment() = Action.async { implicit request =>
request.getQueryString("payment-method").getOrElse("") match {
case "paypal" => // Inject a PaypalPaymentService
val paymentService = Play.current.injector.instanceOf[PaypalPaymentService]
paymentService.processPayment()
case "creditcard" => // Inject a CreditCardPaymentService
val paymentService = Play.current.injector.instanceOf[CreditCardPaymentService]
paymentService.processPayment()
case _ => // Return error
}
}
}
And services class to process Paypal or CreditCard payment
package services
import scala.concurrent.Future
trait PaymentService {
def processPayment(): Future[Boolean]
}
package services
import com.google.inject.Inject
import scala.concurrent.{ExecutionContext, Future}
import play.api.libs.ws.WSClient
class PaypalPaymentService #Inject()(ws: WSClient)
(implicit ec: ExecutionContext)
extends PaymentService {
def processPayment(): Future[Boolean] = {
//Process paypal payment
}
}
class CreditCardPaymentService #Inject()(ws: WSClient)
(implicit ec: ExecutionContext)
extends PaymentService {
def processPayment(): Future[Boolean] = {
//Process credit card payment
}
}
For Play 2.5 onwards, Play.current and Play.application have been deprecated.
I have two questions:
Is the above example code a correct way to inject a class based on
request parameter? or is there some other better way to do so?
For Play 2.5/2.6, what is the way to get the application injector?
You have stated correctly that Play.current and Play.application have been deprecated and from 2.5 onwards the way to use them is indeed by injecting them.
I would change your controller definition so that you make use of DI to include the needed components. Something like:
class PaymentController #Inject()(configuration: Configuration)
(implicit ec: ExecutionContext) extends Controller {
// your code goes here
}
Now comes the tricky part. You might thing that it is possible just to inject application: play.Application but this is not entirely true because you are going to run into circular dependencies. This is normal because you want to inject the whole application while actually being in it. There is one hack for this and it is by injecting Provider[Application]. I call this a hack because normally you don't need/want to inject the whole application. In 99% of the cases you are interested only in specific parts - e.g. the Configuration, Environment, etc.
And here comes the solution. You can just inject your Injector
class PaymentController #Inject()(injector: Injector)
(implicit ec: ExecutionContext) extends Controller {
// your code goes here
}
From here the game is an easy one. Just use the Injector to get the needed services. Like this:
case "paypal" => // Inject a PaypalPaymentService
val paymentService = injector.instanceOf(classOf[PaypalPaymentService])
paymentService.processPayment()
Last words regarding the "correct way" of using this. I actually find your approach OK and would not necessarily change it. Just one idea in this direction is that you create a Module like this:
import com.google.inject.AbstractModule
import com.google.inject.name.Names
class PaymentModule extends AbstractModule {
def configure() = {
bind(classOf[PaymentService])
.annotatedWith(Names.named("paypal"))
.to(classOf[PaypalPaymentService])
bind(classOf[PaymentService])
.annotatedWith(Names.named("creditcard"))
.to(classOf[CreditCardPaymentService])
}
}
Having a common trait (as you do it) helps in this case and you can have multiple implementations, even mocked ones for your tests. The module will be registered automatically if it lies in the root package. Otherwise you should tell Play the location of it:
play.modules.enabled += "modules.PaymentModule"
I need access to the default actor system that Play Framework 2.5 uses from within my Module class.
I see that there is a method on ActorSystemProvider to get this:
#Singleton
class ActorSystemProvider #Inject()(environment: Environment, configuration: Configuration, applicationLifecycle: ApplicationLifecycle) extends Provider[ActorSystem] {
private val logger = Logger(classOf[ActorSystemProvider])
lazy val get: ActorSystem = {
val (system, stopHook) = ActorSystemProvider.start(environment.classLoader, configuration)
applicationLifecycle.addStopHook(stopHook)
system
}
}
But how do I get access to this class in my Module class?
For example:
class Module extends AbstractModule {
val playSystem: ActorSytem = ???
...
}
You can access actorSystem by simply injecting it into any of the component constructor. You will get access to the actorSystem created by play and you need not do any of the provider gymnastics.
For example, I need actor system to be accessible in my HomeController. So, I just inject into my HomeController constructor.
class HomeController #Inject() (actorSystem: ActorSystem) extends Controller {
def index = Ok("bye!")
}
I am trying to create a service that runs in the background of my app (reads and writes to a queue) that I want to build with the actor system. However I am getting an error when I try to inject an ActorSystem into my class:
play.api.UnexpectedException: Unexpected exception[CreationException: Unable to create injector, see the following errors:
1) Error injecting constructor, java.lang.IllegalArgumentException: no matching constructor found on class services.Indexer$IndexActor for arguments []
at services.Indexer.<init>(Indexer.scala:21)
at Module.configure(Module.scala:6) (via modules: com.google.inject.util.Modules$OverrideModule -> Module)
while locating services.Indexer
Here is my setup:
// Module.scala
class Module extends AbstractModule {
override def configure() = {
bind(classOf[Indexer]).asEagerSingleton() // I suspect this needs to change
}
}
// Indexer.scala
#Singleton
class Indexer #Inject() (appLifecycle: ApplicationLifecycle, system: ActorSystem) (implicit ec: ExecutionContext) { ... }
In the play documentation there is an example of injecting an actor system, but that only seems to work when you inject into a class which extends Controller:
#Singleton
class MyController #Inject()(system: ActorSystem)(implicit exec: ExecutionContext) extends Controller {
// This works
}
Found the problem:
In Module.scala I am using the com.google.inject package
import com.google.inject.AbstractModule // <-------
import services.Indexer
class Module extends AbstractModule {
override def configure() = {
bind(classOf[Indexer]).asEagerSingleton()
}
}
But in my Indexer.scala service I was using the javax.inject package.
I have switched all of my files to now:
import com.google.inject._
I have an Akka Actor in my Play app that accesses Play's configuration using a now deprecated method.
class MyActor (supervisor: ActorRef) extends Actor {
val loc = Play.current.configuration.getString("my.location").get
def receive = { case _ => }
}
if I do this:
import javax.inject._
class MyActor #Inject(configuration: play.api.Configuration) (supervisor: ActorRef) extends Actor {
My class won't compile and the compler returns: "classfile annotation arguments have to be supplied as named arguments"
I assume you can only DI the configuration within a controller class. So, is it possible to access the configuration from within an Akka Actore within a Play app? I could pass the configuration to the actor during construction or just have a separate config file for the actors, but both seem pretty hacky. Is there a preferred method using the Play api?
Thanks!
The answer by mana above points out the most elegant way to use DI in combination with actors in Play, but within any Actor you can find the configuration like:
context.system.settings.config
This is working in my project:
Module.scala:
class ExampleModule extends AbstractModule with AkkaGuiceSupport {
override def configure(): Unit = {
bindActor[ExampleActor]("example-actor-name")
}
}
Actor.scala:
object ExampleActor {
def props = Props[ExampleActor]
}
#Singleton
class ExampleActor #Inject()(/*some DI*/) extends Actor {
...
}
And you can then even inject that very actor into other Classes (the #Named() is optional if you have only one Actor configured) via DI:
SomeOtherClass.scala
#Singleton
class SomeOtherClass #Inject()(#Named("example-actor-name") exampleActor: ActorRef) {
...
}