So how can I get a dependency-injected UserService into my custom Authenticator.
Well, it turns out the answer was already staring me right in the face. As a reminder, here's how the "legacy code" obtained a UserService reference:
lazy val userService:UserService =
current.injector.instanceOf[UserService]
And as I mentioned, that lazy was no mere Scala sugar - without it, the "injection" would invariably fail, as again, the DI process had not run yet.
And then it hit me - the lazy keyword was essentially allowing the resolution of a UserService instance to be deferred. So why not use Scala's preferred mechanism for dealing with asynchrony, the Future[T] to formally declare this need to wait for something to happen?
So here's what I did to my Authenticator:
class MyAuthn(fUserService:Future[UserService])
extends Authenticator[UsernamePasswordCredentials]
def validate(creds: UsernamePasswordCredentials,
ctx: WebContext):Unit = {
...
for {
userService <- fUserService
maybeUser <- userService.findByUsername(creds.getUsername)
} yield {
...
}
}
So it just comes down to one extra Future[T] to be resolved - and of course once fUserService does get successfully resolved, it's essentially instant after that. So that's the consumption of the Future[UserService] taken care of, but how do we actually produce it?
Well, it turns out that Module implementations get access to a whole load of methods to help them "listen" to the DI process - and then you've just got to implement some Google Guice interfaces to be notified about "provisioning" events, and away you go. Notice how I use a Promise[UserService] which is kinda the "chicken" and use the promise's .future method to produce the "egg":
override def configure(): Unit = {
...
val futuristicProvisionListener = new ProvisionListener {
private val thePromise = Promise[UserService]
val theFuture = thePromise.future
override def onProvision[T](provision: ProvisionInvocation[T]) = {
if (provision.getBinding.getKey.getTypeLiteral.getRawType
== classOf[UserService]) {
logger.info(s"**onProvision - ${provision.getBinding.getKey}")
val instance = provision.provision()
logger.info(s"UserService instance: $instance")
if (!thePromise.isCompleted) {
logger.info(s"Completing with UserService instance: $instance")
thePromise.success(instance.asInstanceOf[UserService])
}
}
}
}
// This hooks our listener into the Guice binding process
bindListener(Matchers.any(), futuristicProvisionListener)
// And finally, pass the (as-yet unresolved) future
// UserService to the authenticator:
val formClient = new FormClient(
baseUrl + "/login",
new MyAuthn(futuristicProvisionListener.theFuture)
)
...
}
Something that I noticed straight away via the log output was that Guice was creating a vast number of UserService instances - basically it was creating a new one for each place an injection was required. I mopped that up by adding the @Singleton annotation to my UserService, and everything was great. I could probably thus remove the .isCompleted check but it seemed like a good safety-net to leave in, just in case.
No comments:
Post a Comment
Comments welcome - spam is not. Spam will be detected, deleted and the source IP blocked.