I'm migrating a Play 2.3.x app to Play 2.5.x and am having some problems using dependency injection.
In 2.3 I had a trait HasRemoteActor
which a controller would mix-in to have a reference to some remote actor based on configuration. Since this required the application's config object, it's now required that this becomes a class so the config can be injected. Here's my attempt:
/*
Dummy controller that has environment and configuration manually injected.
*/
class ConfigurationController(env: play.api.Environment,
conf: play.api.Configuration) extends Controller {
}
/*
Dummy controller that has environment and configuration manually injected, but
sets up a remote client.
*/
class RemoteActorController(env: play.api.Environment, conf: play.api.Configuration)
extends ConfigurationController(env, conf) {
protected val remoteActorName = "foo"
private val remoteActorConf = conf.underlying.getConfig(remoteActorName)
private val system = ActorSystem("HttpServerSystem", ConfigFactory.load())
private val tcpInfo = remoteActorConf.getConfig("akka.remote.netty.tcp")
private val hostname = tcpInfo.getString("hostname")
private val port = tcpInfo.getString("port")
val path = s"akka.tcp://PubSubMember@$hostname:$port/system/receptionist"
private val initialContacts = Set(ActorPath.fromString(path))
protected val client = system.actorOf(
ClusterClient.props(ClusterClientSettings(system).withInitialContacts(
initialContacts)),
"ClusterClient"
)
}
/*
Actual controller whose actions correspond to endpoints in `conf/routes`.
*/
@Singleton
class BarController @Inject()(env: play.api.Environment,
conf: play.api.Configuration) extends
RemoteActorController(env, conf) {
// ...
}
However, when I start my application, I find that the actor system always fails to find to its port (even though nothing is listening on that port) irrespective of the port number.
play.api.UnexpectedException: Unexpected exception[ProvisionException: Unable to provision, see the following errors:
1) Error injecting constructor, org.jboss.netty.channel.ChannelException: Failed to bind to: /127.0.0.1:8888
There seems to be a problem with the timing of the injection, but I'm so new to DI that I'm having trouble debugging it.
I tried adding routesGenerator := InjectedRoutesGenerator
to my build.sbt
and prefixed my injected routes' associated controllers with @
, but still find the same runtime exceptions.
Does anyone have suggestions?
I would not use inheritance for this. Instead, I would go for something like this (I'm going to assume you're using guice):
@Singleton
class RemoteActorAdapter @Inject() (env: Environment, conf: Configuration) {
// all other initialization code
val client: ActorRef = ???
}
In the controller that wants to use these things:
class MyController @Inject() (remoteAdapterProvider: Provider[RemoteActorAdapter]) extends Controller {
def index = Action {
remoteAdapterProvider.get.client ! Hello
}
}
So the trick is that by using a provider, you're deferring the initialization of the binding etc. to the time until it is needed.