In Play 2.4 with DI, how to use a service class in

2019-06-18 08:11发布

问题:

Here's an authorisation example from Play Documentation (version 2.0.4; I tried to find a newer version of this document but couldn't):

trait Secured {

  def username(request: RequestHeader) = request.session.get(Security.username)

  def onUnauthorized(request: RequestHeader) = Results.Redirect(routes.Auth.login)

  def withAuth(f: => String => Request[AnyContent] => Result) = {
    Security.Authenticated(username, onUnauthorized) { user =>
      Action(request => f(user)(request))
    }
  }

  def withUser(f: User => Request[AnyContent] => Result) = withAuth { username => implicit request =>
    UserDAO.findOneByUsername(username).map { user =>
      f(user)(request)
    }.getOrElse(onUnauthorized(request))
  }
}

Overall this is pretty straightforward, and I'd like to go with something like this.

Now, in Play 2.4 the recommended way is not to use singletons anymore (like UserDAO above), but classes and runtime DI instead (see migration guide, or DI docs).

For example, my service and repository classes are defined like this:

class AuthService @Inject()(accountRepo: AccountRepository) { }

class AccountRepository { }

With Play 2.4 and DI in use, what is the recommended / "correct" / simplest way to get hold of a service or DAO (like AuthService in my case, or UserDAO in the doc example) in a trait like Secured?

Or are you nowadays supposed to implement authorisation for controllers in a whole another way than using such trait?


I can make it work along these lines:

trait Secured {
  val authService = GuiceUtils.inject[AuthService]    
  // ...
}

Using a helper like this:

object GuiceUtils {
  lazy val injector = new GuiceApplicationBuilder().injector()    
  def inject[T: ClassTag]: T = injector.instanceOf[T]
}

But according to an answer in a related question:

In Play you could use the injector directly as long as the Application trait is in scope. But this isn't considered good practice in production code.

If that is true, what is considered good practice in this use case?

回答1:

I think the simplest approach would be to declare the authService in your trait but keep it abstract, then have the controller which extends it handle the injection (I believe this is how MessagesApi/I18nSupport injection works). So you could do:

trait Secured {
  val authService: AuthService
  ...
}

controller Application @Inject()(override val authService: AuthService) extends Controller with Secured {
  ...
}