Краткое руководство по Spring Bean Scopes
В этом кратком руководстве мы узнаем о различных типах областей действия компонентов в среде Spring.
Область действия bean-компонента определяет жизненный цикл и видимость этого bean-компонента в контекстах, в которых мы его используем.
Последняя версия фреймворка Spring определяет 6 типов областей видимости:
- синглтон
- прототип
- запрос
- сеанс
- заявление
- веб-сокет
Последние четыре упомянутые области, request, session, application и websocket , доступны только в веб-приложении.
2. Одноэлементный объем
Когда мы определяем bean-компонент с областью singleton , контейнер создает один экземпляр этого bean-компонента; все запросы для этого имени компонента будут возвращать один и тот же объект, который кэшируется. Любые изменения объекта будут отражены во всех ссылках на компонент. Эта область является значением по умолчанию, если не указана другая область.
Давайте создадим сущность Person , чтобы проиллюстрировать концепцию областей видимости:
public class Person private String name; // standard constructor, getters and setters >
После этого мы определяем bean-компонент с областью singleton , используя аннотацию @Scope :
@Bean @Scope("singleton") public Person personSingleton() return new Person(); >
Мы также можем использовать константу вместо значения String следующим образом:
@Scope(value = ConfigurableBeanFactory.SCOPE_SINGLETON)
Теперь мы можем приступить к написанию теста, который показывает, что два объекта, ссылающиеся на один и тот же компонент, будут иметь одинаковые значения, даже если только один из них изменит свое состояние, так как они оба ссылаются на один и тот же экземпляр компонента:
private static final String NAME = "John Smith"; @Test public void givenSingletonScope_whenSetName_thenEqualNames() ApplicationContext applicationContext = new ClassPathXmlApplicationContext("scopes.xml"); Person personSingletonA = (Person) applicationContext.getBean("personSingleton"); Person personSingletonB = (Person) applicationContext.getBean("personSingleton"); personSingletonA.setName(NAME); Assert.assertEquals(NAME, personSingletonB.getName()); ((AbstractApplicationContext) applicationContext).close(); >
Файл scopes.xml в этом примере должен содержать XML-определения используемых bean-компонентов:
beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> bean id="personSingleton" class="org.foreach.scopes.Person" scope="singleton"/> beans>
3. Объем прототипа
Бин с областью действия прототипа будет возвращать другой экземпляр каждый раз, когда он запрашивается из контейнера. Он определяется установкой прототипа значения в аннотацию @Scope в определении bean-компонента:
@Bean @Scope("prototype") public Person personPrototype() return new Person(); >
Мы также можем использовать константу, как мы делали для одноэлементной области видимости:
@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
Теперь мы напишем аналогичный тест, который показывает, как два объекта запрашивают одно и то же имя компонента с областью действия прототипа . У них будут разные состояния, поскольку они больше не относятся к одному и тому же экземпляру компонента:
private static final String NAME = "John Smith"; private static final String NAME_OTHER = "Anna Jones"; @Test public void givenPrototypeScope_whenSetNames_thenDifferentNames() ApplicationContext applicationContext = new ClassPathXmlApplicationContext("scopes.xml"); Person personPrototypeA = (Person) applicationContext.getBean("personPrototype"); Person personPrototypeB = (Person) applicationContext.getBean("personPrototype"); personPrototypeA.setName(NAME); personPrototypeB.setName(NAME_OTHER); Assert.assertEquals(NAME, personPrototypeA.getName()); Assert.assertEquals(NAME_OTHER, personPrototypeB.getName()); ((AbstractApplicationContext) applicationContext).close(); >
Файл scopes.xml аналогичен файлу, представленному в предыдущем разделе, но добавляет определение xml для bean-компонента с областью действия прототипа :
bean id="personPrototype" class="org.foreach.scopes.Person" scope="prototype"/>
4. Веб-области действия
Как упоминалось ранее, есть четыре дополнительных области, которые доступны только в контексте веб-приложения. Мы используем их реже на практике.
Область запроса создает экземпляр компонента для одного HTTP-запроса, а область сеанса создает экземпляр компонента для сеанса HTTP.
Область приложения создает экземпляр компонента для жизненного цикла ServletContext , а область веб- сокета создает его для определенного сеанса веб-сокета .
Давайте создадим класс для создания экземпляров bean-компонентов:
public class HelloMessageGenerator private String message; // standard getter and setter >
4.1. Объем запроса
Мы можем определить bean-компонент с областью запроса , используя аннотацию @Scope :
@Bean @Scope(value = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.TARGET_CLASS) public HelloMessageGenerator requestScopedBean() return new HelloMessageGenerator(); >
Атрибут proxyMode необходим, поскольку в момент создания экземпляра контекста веб-приложения нет активного запроса. Spring создает прокси для внедрения в качестве зависимости и создает экземпляр целевого компонента, когда это необходимо в запросе.
Мы также можем использовать составную аннотацию @RequestScope , которая действует как ярлык для приведенного выше определения:
@Bean @RequestScope public HelloMessageGenerator requestScopedBean() return new HelloMessageGenerator(); >
Далее мы можем определить контроллер, который имеет внедренную ссылку на requestScopedBean . Нам нужно дважды получить доступ к одному и тому же запросу, чтобы протестировать области, специфичные для Интернета.
Если мы будем отображать сообщение каждый раз, когда выполняется запрос, мы увидим, что значение сбрасывается на null , даже если позже оно было изменено в методе. Это связано с тем, что для каждого запроса возвращается другой экземпляр компонента.
@Controller public class ScopesController @Resource(name = "requestScopedBean") HelloMessageGenerator requestScopedBean; @RequestMapping("/scopes/request") public String getRequestScopeMessage(final Model model) model.addAttribute("previousMessage", requestScopedBean.getMessage()); requestScopedBean.setMessage("Good morning!"); model.addAttribute("currentMessage", requestScopedBean.getMessage()); return "scopesExample"; > >
4.2. Область сеанса
Аналогичным образом мы можем определить bean-компонент с областью действия сеанса :
@Bean @Scope(value = WebApplicationContext.SCOPE_SESSION, proxyMode = ScopedProxyMode.TARGET_CLASS) public HelloMessageGenerator sessionScopedBean() return new HelloMessageGenerator(); >
Также есть специальная составная аннотация, которую мы можем использовать для упрощения определения bean-компонента:
@Bean @SessionScope public HelloMessageGenerator sessionScopedBean() return new HelloMessageGenerator(); >
Далее мы определяем контроллер со ссылкой на sessionScopedBean . Опять же, нам нужно запустить два запроса, чтобы показать, что значение поля сообщения одинаково для сеанса.
В этом случае, когда запрос делается в первый раз, значение сообщения равно нулю. Однако после изменения это значение сохраняется для последующих запросов, поскольку один и тот же экземпляр компонента возвращается для всего сеанса.
@Controller public class ScopesController @Resource(name = "sessionScopedBean") HelloMessageGenerator sessionScopedBean; @RequestMapping("/scopes/session") public String getSessionScopeMessage(final Model model) model.addAttribute("previousMessage", sessionScopedBean.getMessage()); sessionScopedBean.setMessage("Good afternoon!"); model.addAttribute("currentMessage", sessionScopedBean.getMessage()); return "scopesExample"; > >
4.3. Область применения
Область приложения создает экземпляр компонента для жизненного цикла ServletContext.
Это похоже на область действия singleton , но есть очень важное отличие в отношении области действия bean-компонента.
Когда bean -компоненты ограничены приложением , один и тот же экземпляр bean-компонента совместно используется несколькими приложениями на основе сервлетов, работающими в одном и том же ServletContext , в то время как bean -компоненты с областью действия singleton ограничены только одним контекстом приложения.
Давайте создадим bean-компонент с областью применения :
@Bean @Scope( value = WebApplicationContext.SCOPE_APPLICATION, proxyMode = ScopedProxyMode.TARGET_CLASS) public HelloMessageGenerator applicationScopedBean() return new HelloMessageGenerator(); >
По аналогии с областями запроса и сеанса мы можем использовать более короткую версию:
@Bean @ApplicationScope public HelloMessageGenerator applicationScopedBean() return new HelloMessageGenerator(); >
Теперь давайте создадим контроллер, который ссылается на этот компонент:
@Controller public class ScopesController @Resource(name = "applicationScopedBean") HelloMessageGenerator applicationScopedBean; @RequestMapping("/scopes/application") public String getApplicationScopeMessage(final Model model) model.addAttribute("previousMessage", applicationScopedBean.getMessage()); applicationScopedBean.setMessage("Good afternoon!"); model.addAttribute("currentMessage", applicationScopedBean.getMessage()); return "scopesExample"; > >
В этом случае, однажды установленное в applicationScopedBean , сообщение со значением будет сохранено для всех последующих запросов, сеансов и даже для различных приложений сервлетов, которые будут обращаться к этому bean-компоненту, при условии, что он работает в том же ServletContext.
4.4. Область действия веб-сокета
Наконец, давайте создадим bean-компонент с областью действия websocket :
@Bean @Scope(scopeName = "websocket", proxyMode = ScopedProxyMode.TARGET_CLASS) public HelloMessageGenerator websocketScopedBean() return new HelloMessageGenerator(); >
При первом доступе bean-компоненты с областью действия WebSocket сохраняются в атрибутах сеанса WebSocket . Затем один и тот же экземпляр компонента возвращается при каждом доступе к этому компоненту в течение всего сеанса WebSocket .
Мы также можем сказать, что он демонстрирует одноэлементное поведение, но ограниченное только сеансом WebSocket .
5. Вывод
В этой статье мы обсудили различные области действия bean-компонентов, предоставляемые Spring, и их предполагаемое использование.
Реализацию этой статьи можно найти в проекте GitHub .