Spring bean scopes

beans scopes указывают Sping, как ему управлять жизненным циклом бина. Из коробки Spring поддерживает два базовых scope: Singleton и Prototype. По умолчанию каждый бин имеет scope Singleton, если не указано обратное.

Отличие этих двух scope в том, что Singleton bean создаётся в единственном экземпляре на весь контекст и ссылка на этот экземпляр подсовывается каждому другому бину, который просит эту зависимость. А вот Prototype bean напротив, создаётся отдельный для каждого другого бина, который просит его в зависимость.

Лучше всего это продемонстрировать в коде:

Кода много 🙂 У нас есть два бина, каждый из которых имеет какое-то хранимое состояние. На один из бинов навешена аннотация @Scope("prototype"), которая объявляет спрингу, что этот бин имеет scope Prototype, очевидно.

От бинов с разным scope зависят два бина потребителя, каждый из которых меняет внутреннее состояние. Какой результат будет после запуска?

Видно, что внутреннее состояние SingletonBean общее и для FirstConsumer и для SecondConsumer, а вот внутреннее состояние PrototypeBean и первый и второй бины-потребители видят разные. То есть подтверждается, что SingletonBean создан в единственном экземпляре, а PrototypeBean в двух экземплярах.

В этом месте многие руководства начинают рекомендовать использовать бины с scope singleton для stateless бинов, а бины со scope prototype для stateful бинов. С одной стороны, они конечно правы,так как Spring гарантирует, что данные scope prototype бинов не будут разделены между разными бинами, вызывая конфликты при обращении. С другой стороны, scope prototype не гарантирует потокобезопасности и не гарантирует, что один и тот же экземпляр бина со scope prototype будет вызван не более чем в одном потоке, со всеми вытекающими последствями.

Задание scope

Как показано выше, аннотация @Scope  задаёт scope бину. Эта же аннотация, только приложенная к методу, задаёт scope бину при использовании java config:

С xml тоже всё просто: в описание бина добавляется ещё один параметр:

Аналогично и с groovy configuration:

Дополнительные scopes

Кроме двух базовых scopes, о которых речь шла выше, Spring предоставляет ещё три scope, которые доступны только при использовании web специфичных ApplicationContext:

  • request — бин создаётся для каждого HTTP request и уничтожается после завершения обработки.
  • session — бин создаётся для HTTP сессии и уничтожается, после её завершения.
  • globalSession — редкий зверь, используемый с портлетами. Бин создаётся на время существования глобальной сессии всего приложения с портлетами.

Внедрение бинов и иерархия scope

У человека, внимательно прочитавшего про web специфичные scopes может возникнуть вопрос — а что если я возьму и сделаю какой-нибудь singleton bean зависящим от request bean? Первый существует всегда и он один, второй существует от случая к случаю, зато может существовать в нескольких экземплярах. Ответ на этот вопрос простой — ошибка будет 🙂 Бины с разными scope не совсем между собой совместимы.

Singleton bean может быть зависимостью для бина с любым scope. Сам он может зависеть от бина с singleton scope и prototype scope, причём для каждого singleton бина будет создан свой собственный уникальный экземпляр prototype бина. Кстати, получить в singleton бине новый экземпляр prototype бина так просто не получится, надо вручную обращаться к ApplicationContext и просить создать новый бин.

Дальнейшая иерархия сохраняется: prototype bean может быть зависимостью для бина с любым scope.  Сам он может зависеть от бина с singleton scope и prototype scope.

globalSession бин может быть зависимостью для бинов с scope globalSession, session и request. Сам он может зависеть от бина с singleton scope, prototype scope и globalSession scope.

session бин, очевидно, может быть зависимостью для бинов с session и request. Сам он может зависеть от бина с singleton scope, prototype scope, globalSession и session scope.

Наконец request бин может быть зависимостью только для других request бинов, зато сам он может зависет от бина с любым scope.

Scoped proxies

На самом деле в разделе выше написана неправда. И request и session и globalSession могут быть зависимостями для любых бинов, но не напрямую. Мы можем попросить spring создать специальный прокси, который с одной стороны будет иметь (внутри себя) scope singleton и, таким образом, быть совместимым со всеми scopes, с другой стороны этот прокси будет определять требуемый в данном контексте исполнения бин с меньшим временем жизни (например session бин) и передавать все вызовы ему.

Попросить spring создать прокси можно разными путями. Во-первых в аннотацию @Scope можно передать proxyMode=INTERFACES или proxyMode=TARGET_CLASS, который создадут для класса proxy, основанный на интерфейсах, которые реализует класс или сгенерирует специальный отдельный класс, во втором случае.

Для тех, кто предпочитает xml конфигурацию, можно включить генерацию прокси для конкретного класса:

Либо для всех классов:

Код примера доступен на github.