Экземпляр, внедренный Guice, сохраняется между запросами в Play 2.5.2

Давным-давно жил-был молодой джентльмен, который прочитал несколько руководств, часть документации, немало вопросов по StackOverflow и, может быть, даже задал один или два. После того, как он это сделал, он подумал, что у него есть четкое представление о том, как все работает, и начал создавать нужные ему функции, и это сработало! Пока не появилась злая ведьма и не попробовала с другого браузера без волшебных файлов cookie, и он не понял, что UserIdentity сохранялся между пользователями... это было нехорошо.

Из спектакля! документы (документы Guice говорят аналогично)

Новые экземпляры создаются каждый раз, когда требуется компонент. Если компонент используется более одного раза, то по умолчанию будет создано несколько экземпляров компонента. Если вам нужен только один экземпляр компонента, вам нужно пометить его как синглтон.

Вершина моего класса IndexController (я предполагаю, что каждый запрос вызывает действие index один раз и, следовательно, запрашивает новый UserIdentity — это неправильно?)

@Singleton
class IndexController @Inject() (contentModel: ContentModel, site: Site, injector: Injector) extends Controller {

  def index(url: String, page: Int = 1, sort: String, dir: Int) = Action.async { implicit request =>
    implicit val queryString : QueryString = request.queryString
    val userIdentityClass = injector.getInstance(classOf[UserIdentity])
    implicit val userIdentity : Future[UserIdentity] = userIdentityClass.getUserIdentity
    doContent(url, page, sort, dir) fallbackTo doAlias(url) fallbackTo do404(url)
  }

Затем userIdentity неявно передается в недра машины.

Метод getUserIdentity вызывает метод getUser, выполняющий аутентификацию, и устанавливает свойство user : UserModel. Все это передается в будущем, поэтому, когда мы используем userIdentity позже в приложении, мы можем отобразить его, чтобы мы знали, что аутентификация завершена.

Вот метод получения пользователя и println, который вызывается для пользователя, не прошедшего проверку подлинности.

private def getUser(implicit request:Request[AnyContent]) : Future[UserModel] = {
  if (user != null) {
    println("User already set")
    Future { user }
  } else {
    //Go and find the user from cookie/some other auth stuff

Обратите внимание, что var user : UserModel = null относится к классу UserIdentity, поэтому в новом экземпляре он должен быть null.


person Nathan Edwards    schedule 10.05.2016    source источник


Ответы (3)


Чтобы получить экземпляр по запросу, а не при создании экземпляра класса, вы будете использовать такой провайдер:

class MyController @Inject() (userIdProv: Provider[UserIdentity])

Дополнительные примечания к вашему коду:

  • Если вы можете этого избежать, вводите не инжектор, а фактический класс (или его поставщика), который вам нужен. С одной стороны, вы создаете зависимость от самого guice. Во-вторых, что бы вы добавили в модульный тест? Макет инжектора? Однако важнее всего то, что внедрение компонента, от которого вы напрямую зависите, просто делает ваш код более понятным. Подумайте об этом с точки зрения моделирования: у вас есть одна вещь, которая зависит от этой другой вещи для выполнения своей задачи, а не: у меня есть одна вещь, которая зависит от инжектора (очень техническое, неконкретное).

  • Почему ваш контроллер синглтон? Во-первых, игровые контроллеры в любом случае являются синглтонами (в документах это не указано, поэтому вы, вероятно, не можете полагаться на то, что это всегда так), а во-вторых, почему вы хотите, чтобы они были синглтонами? Обратите внимание, что наличие синглтона всегда вводит какое-то состояние, чего вам следует избегать, если только в этом нет особой необходимости.

  • Не делайте Future { user }. Это создаст и исполнит будущее. Что вам действительно нужно, так это Future.successful(user), который в основном просто обертывает пользователя правильным типом.
person rethab    schedule 11.05.2016
comment
Спасибо за это. Что касается того, почему мой контроллер является синглтоном, я полагаю, что следовал примеру проекта воспроизведения или даже вступительному видео. Я как бы предполагал, что так и должно быть Play! нужно, чтобы это было. - person Nathan Edwards; 11.05.2016

Вы правы, что он каждый раз запрашивает новый экземпляр, но я думаю, что guice повторно использует один и тот же экземпляр внутри.

Я был бы рад опубликовать определение вашего модуля, чтобы быть уверенным, но я думаю, что вы могли бы избежать неприятностей с чем-то вроде этого, чтобы убедиться, что каждый раз создается новый экземпляр:

bind(classOf[UserIdentity]).toProvider(new Provider[UserIdentity]{
  override def get(): UserIdentity = new UserIdentity()
})
person Tompey    schedule 10.05.2016
comment
Привет Томпи, спасибо за ваш ответ. Это бы не сработало, потому что мне нужно сделать инъекцию в мой UserIdentity. Однако это заставило меня заглянуть в мой файл модулей и найти эту мошенническую строку bind(classOf[UserIdentity]).asEagerSingleton() - person Nathan Edwards; 10.05.2016

Благодаря ответу Томпи я посмотрел в нужном месте.

Мораль этой истории в том, чтобы проверить свой Guice Module. Я связывал UserIdentity как Eager Singleton. Итак, удалив следующую строку, я смог исправить свою проблему.

bind(classOf[UserIdentity]).asEagerSingleton()

Очевидно, злая ведьма положила его туда.

person Nathan Edwards    schedule 10.05.2016
comment
Тем не менее, вы не должны вводить инжектор. Получите экземпляр UserIdentity через провайдера, как было предложено Tompey (не уверен, нужна ли вам эта привязка или она работает, просто запросив провайдера «uiProv: Provider [UserIdentity]» в вашем контроллере) - person rethab; 10.05.2016
comment
@rethab, в других местах мне сказали ввести инжектор. Поскольку UserIdentity имеет следующие параметры, все они вводятся class UserIdentity @Inject() (userModel: UserModel, ws: WSClient, site: config.Site, cacheApi: CacheApi) { - person Nathan Edwards; 10.05.2016
comment
@rethab Можете ли вы добавить ответ. И скажите, почему закачивать инжектор - плохая идея? - person Nathan Edwards; 10.05.2016
comment
Я добавил ответ. - person rethab; 11.05.2016