简体   繁体   中英

How to inject parameters into a class/trait method in Scala

I have a code in my Play Scala (2.5x, 2.11.11) app which has been running just fine so far (it is based on the following link: https://fizzylogic.nl/2016/11/27/authorize-access-to-your-play-application-using-action-builders-and-action-functions/ ). But now I need to pass another class instance to ApplicationAuthorizationHandler class (NOTE: throughout my code I am using Guice DI for injecting parameters into class constructors).

Current code:

class ApplicationAuthorizationHandler
   extends AuthorizationHandler {
...
}

trait AuthorizationHandler {
...
}

trait AuthorizationCheck {
   def authorizationHandler: AuthorizationHandler = new ApplicationAuthorizationHandler

   object AuthenticatedAction extends ActionBuilder[RequestWithPrincipal] {
      override def invokeBlock[A](request: Request[A], block: (RequestWithPrincipal[A]) => Future[Result]): Future[Result] = {
         def unauthorizedAction = authorizationHandler.unauthorized(RequestWithOptionalPrincipal(None, request))
         def authorizedAction(principal: Principal) = block(RequestWithPrincipal(principal, request))

         authorizationHandler.principal(request).fold(unauthorizedAction)(authorizedAction)
      }
  }
}

//Example controller using this trait AuthorizationCheck
class MyController @Inject() extends Controller with AuthorizationCheck {
    def myAction = AuthenticatedAction { implicit request =>
...
}

Desired code:

class ApplicationAuthorizationHandler @Inject() (userService: UserService)
   extends AuthorizationHandler {
   ...
   // userService is used here
}

But since the instance of ApplicationAuthorizationHandler is instantiated inside trait AuthorizationCheck I can't inject UserService instance into it. I am Mixin this trait with all controllers so would like to keep the same way unless there is a better way (and there must be). First, is there a way to inject directly into class/trait method ? Alternatively, is there a way where I don't instantiate ApplicationAuthorizationHandler in trait AuthorizationCheck and pass it during run-time inside the controller ? Or any other way ?

A trait does not need to provide an implementation, so you can have something like:

trait AuthorizationHandler {
  ...
}

class ApplicationAuthorizationHandler extends AuthorizationHandler {
  ...
}

trait AuthorizationCheck {

  // just declaring that implementations needs to provide a 
  def authorizationHandler: AuthorizationHandler 

  object AuthenticatedAction extends ActionBuilder[RequestWithPrincipal] {
    override def invokeBlock[A](request: Request[A], block: (RequestWithPrincipal[A]) => Future[Result]): Future[Result] = {
      def unauthorizedAction = authorizationHandler.unauthorized(RequestWithOptionalPrincipal(None, request))
      def authorizedAction(principal: Principal) = block(RequestWithPrincipal(principal, request))

      authorizationHandler.principal(request).fold(unauthorizedAction)(authorizedAction)
    }
  }
}

// So, now this controller needs to provide a concrete implementation 
// of "authorizationHandler" as declared by "AuthorizationCheck".
// You can do it by injecting a "AuthorizationHandler" as a val with
// name authorizationHandler.
class MyController @Inject()(val authorizationHandler: AuthorizationHandler) extends Controller with AuthorizationCheck {

   def myAction = AuthenticatedAction { implicit request =>
     ...
   }
}

And of course, you need to provide a module to bind AuthorizationHandler to ApplicationAuthorizationHandler :

import play.api.inject._

class AuthorizationHandlerModule extends SimpleModule(
  bind[AuthorizationHandler].to[ApplicationAuthorizationHandler]
)

Of course, ApplicationAuthorizationHandler can have its own dependencies injected. You can see more details at our docs .

There are many cases when you cannot use the @Inject approach of guice. This is true when dependencies are needed inside of trait and also actors.

The approach I use in these cases is that I put my injector in a object

object Injector {
  val injector = Guice.createInjector(new ProjectModule())
}

since the above is inside of an object, you can access it from anywhere. (its like a singleton).

Now inside your trait or an actor when you need the user service do

trait Foo {
   lazy val userService = Injector.injector.getInstance(classOf[UserService])
}

Don't forget to make the variable lazy, because you want the instance to be created as late as possible when the injector has already been created.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM