1. Обзор
В этом кратком руководстве вы узнаете о различных типах областей действия bean-компонентов в среде Spring.
Область действия компонента определяет жизненный цикл и видимость этого компонента в контексте, в котором он используется.
Последняя версия Spring Framework определяет 6 типов областей:
-
синглтон
-
прототип
-
запрос
-
сессия
-
приложение
-
веб-розетка
Последние четыре упомянутые области действия request, session, application и _websocket _ доступны только в веб-приложениях.
2. Singleton Scope
Определение bean-компонента с помощью singleton scope означает, что контейнер создает отдельный экземпляр этого bean-компонента, и все запросы на это имя 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 используемых компонентов:
<?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.baeldung.scopes.Person" scope="singleton"/>
</beans>
3. Объем прототипа
Бин с prototype scope будет возвращать новый экземпляр каждый раз, когда его запрашивают из контейнера. Это определяется путем установки значения prototype в аннотацию @ Scope в определении компонента:
@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 для компонента с областью действия prototype :
<bean id="personPrototype" class="org.baeldung.scopes.Person" scope="prototype"/>
4. Области веб-осведомленности
Как уже упоминалось, есть четыре дополнительных области, которые доступны только в контексте веб-приложения. Они реже используются на практике.
Область request создает экземпляр компонента для одного HTTP-запроса, а область s _ession _ создает для сеанса HTTP.
__Application scope создает экземпляр компонента для жизненного цикла, ServletContext , а websocket scope создает его для определенной WebSocket __session.
Давайте создадим класс, который будет использоваться для создания экземпляров bean-компонентов:
public class HelloMessageGenerator {
private String message;
//standard getter and setter
}
4.1. Объем запроса
Мы можем определить bean-компонент с помощью request scope, используя аннотацию @ Scope :
@Bean
@Scope(value = WebApplicationContext.SCOPE__REQUEST, proxyMode = ScopedProxyMode.TARGET__CLASS)
public HelloMessageGenerator requestScopedBean() {
return new HelloMessageGenerator();
}
Атрибут proxyMode необходим, потому что в момент создания экземпляра контекста веб-приложения активный запрос отсутствует. Spring создаст прокси для внедрения в качестве зависимости и создаст экземпляр целевого компонента, когда это необходимо в запросе.
Затем мы можем определить контроллер, который имеет вставленную ссылку на requestScopedBean . Нам нужно получить доступ к одному и тому же запросу дважды, чтобы протестировать определенные веб-области.
Если мы отображаем message каждый раз, когда выполняется запрос, мы можем видеть, что значение сбрасывается до 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. Session Scope
Мы можем определить bean-компонент с помощью session scope аналогичным образом:
@Bean
@Scope(value = WebApplicationContext.SCOPE__SESSION, proxyMode = ScopedProxyMode.TARGET__CLASS)
public HelloMessageGenerator sessionScopedBean() {
return new HelloMessageGenerator();
}
Далее мы определяем контроллер со ссылкой на sessionScopedBean . Опять же, нам нужно выполнить два запроса, чтобы показать, что значение поля message одинаково для сеанса.
В этом случае, когда запрос выполняется впервые, значение message равно _null. Но один раз оно изменяется, затем это значение _ сохраняется для последующих запросов, так как один и тот же экземпляр компонента возвращается для всего сеанса.
@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. Область применения
_Application scope создает экземпляр компонента для жизненного цикла ServletContext._
Это похоже на одноэлементную область, но есть очень важное различие в отношении объема компонента.
Когда бины находятся в области применения application , один и тот же экземпляр бина совместно используется несколькими сервлетными приложениями, работающими в одном и том же ServletContext , в то время как бины с одиночной областью действия ограничиваются только одним контекстом приложения.
Давайте создадим компонент с областью действия application :
@Bean
@Scope(value = WebApplicationContext.SCOPE__APPLICATION, proxyMode = ScopedProxyMode.TARGET__CLASS)
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";
}
}
В этом случае значение message , однажды заданное в _applicationScopedBean , будет сохранено для всех последующих запросов, сеансов и даже для другого приложения сервлета, которое получит доступ к этому бину, при условии, что оно работает в том же ServletContext._
4.4. Область применения WebSocket
Наконец, давайте создадим компонент с помощью __websocket __scope:
@Bean
@Scope(scopeName = "websocket", proxyMode = ScopedProxyMode.TARGET__CLASS)
public HelloMessageGenerator websocketScopedBean() {
return new HelloMessageGenerator();
}
Bean-объекты в области WebSocket при первом обращении хранятся в атрибутах сеанса WebSocket . Затем тот же экземпляр компонента возвращается всякий раз, когда к этому компоненту обращаются в течение всего сеанса WebSocket .
Можно также сказать, что он демонстрирует одноэлементное поведение, но ограничивается только сеансом WebSocket .
5. Заключение
Мы продемонстрировали различные области применения бинов, предоставляемые Spring, и их предполагаемое использование.
Реализацию этого руководства можно найти по адресу the проект github - это проект на основе Eclipse, поэтому его легко импортировать и запускать как есть.