Перейти к основному содержимому

Краткое руководство по Spring Bean Scopes

· 7 мин. чтения

Задача: Медиана двух отсортированных массивов

Даны два отсортированных массива размерами n и m. Найдите медиану слияния этих двух массивов.
Временная сложность решения должна быть O(log(m + n)) ...

ANDROMEDA

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 .