Краткое руководство по применению бобов

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, поэтому его легко импортировать и запускать как есть.