injecting service into actor gives NullPointerException

106 Views Asked by At

I'm not able to figure out how to inject a Service into an Actor.

I tried out several approaches and think this one should fit best.

I think the major problem is that I provide the websocket to the succeeding actor as an parameter. When I pull the Inject up to the class signature compilation doesn't perform because of signature conflict. I tried adding a currying implicit class declaration to inject the service, but this didn't work out either.

What shall I do to get it right?

Running this code results in a NullPointerException.

package actors

import akka.actor._
import com.google.inject.Inject
import play.api.libs.json._
import models._
import services.WeatherService

/**
  * Created by jbc on 28.12.16.
  */
class WeatherWSActor (out: ActorRef) extends Actor {

  @Inject() val weatherService: WeatherService = null

  def handleMessage(msg: JsValue): JsValue = {

    val req = msg.as[Request]

    req match {
      case Request("daily") => Json.toJson(weatherService.getFourDays())
      case Request("next") => Json.toJson(weatherService.getNextHour())
    }
  }

  override def receive: Receive = {
    case msg: JsValue => out ! handleMessage(msg)
    case _ => out ! "Bad Request"
  }

  @scala.throws[Exception](classOf[Exception])
  override def postStop(): Unit = {

  }
}

object WeatherWSActor {
  def props(out: ActorRef) = Props(new WeatherWSActor(out))
}

Controller Code:

class WeatherWSController @Inject() (implicit system: ActorSystem, materializer: Materializer) extends Controller {

  def socket = WebSocket.accept[JsValue, JsValue] { request =>
    ActorFlow.actorRef(out => WeatherWSActor.props(out));
  }

}

The service is set up with this Module Code

class Module extends AbstractModule {
    override def configure(): Unit = {
        bind(classOf[WeatherService]).to(classOf[WeatherServiceImpl]).asEagerSingleton()
    }
}
1

There are 1 best solutions below

0
On

This is covered in the documentation, but here's a summary.

If you want to inject your actors, you need to change a couple of things. First off, the injection point is the constructor, so

class WeatherWSActor (out: ActorRef) extends Actor {
  @Inject() val weatherService: WeatherService = null
  // ...
}

should become

class WeatherWSActor @Inject() (out: ActorRef, 
                                weatherService: WeatherService) extends Actor {
  // ...
}

In fact, assigning null to a Scala val is also going to give you issues.

Secondly, declare a binding for the actor instead of creating it yourself.

import com.google.inject.AbstractModule
import play.api.libs.concurrent.AkkaGuiceSupport

import actors.WeatherWSActor

class MyModule extends AbstractModule with AkkaGuiceSupport {
  def configure = {
    bindActor[WeatherWSActor]("weather-actor")
  }
}

On a side note, you'll also need to create a named binding for out and change the definition of WeatherWSActor to reflect this:

class WeatherWSActor @Inject() (@javax.inject.Named("out-actor") out: ActorRef, 
                                weatherService: WeatherService) extends Actor {
  // ...
}

out-actor is whichever class you have set up in the module, e.g.

class MyModule extends AbstractModule with AkkaGuiceSupport {
  def configure = {
    bindActor[WeatherWSActor]("weather-actor")
    bindActor[SomeOutActor]("out-actor")
  }
}

But...

Since you're getting out when a web socket is created, you're (presumably) going to have multiple WeatherWSActor instances. This is going to effect how you handle the dependency injection, so it make more sense to not inject outActor (and hence not create a binding for it), but rather have a singleton WeatherWSActor that broadcasts to all instances of OutActor addressed at a certain point in the actor system.