1. Обзор
В этом кратком руководстве мы узнаем о различных типах областей действия компонентов в среде 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-компонентов:
<?xml version="1.0" encoding="UTF-8"?>
<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 .