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.