Spring IoC контейнер: области применения бинов, бины с областями применения как зависимости

Контейнер Spring IoC управляет не только созданием ваших объектов (компонентов), но и подключением сотрудников (collaborators) (или зависимостей). Если вы хотите внедрить (например) компонент с областью действия HTTP-запроса в другой компонент с более длительным сроком действия, вы можете внедрить прокси AOP (AOP proxy) вместо этого компонента. То есть вам нужно внедрить прокси-объект, который предоставляет тот же открытый интерфейс, что и объект области действия, но который также может извлечь реальный целевой объект из соответствующей области (например, HTTP-запрос) и делегировать вызовы методов в реальный объект.

Вы также можете использовать <aop:scoped-proxy/> между bean-компонентами, которые определены как одноэлементные (singleton), причем ссылка затем проходит через промежуточный прокси, который сериализуем и поэтому может повторно получить целевой одноэлементный компонент при десериализации.

При объявлении <aop:scoped-proxy/> против bean-объекта области видимости прототипа (prototype) каждый вызов метода на общем прокси приводит к созданию нового целевого экземпляра, на который затем перенаправляется вызов.

Кроме того, прокси с областями видимости - не единственный способ получить доступ к бинам из более коротких областей безопасным способом. Вы также можете объявить свою точку внедрения (то есть аргумент конструктора или сеттера или поле с автосвязью (autowired)) как ObjectFactory<MyTargetBean>, что позволяет при вызове getObject() извлекать текущий экземпляр по требованию каждый раз, когда это необходимо - без удержания экземпляра или хранения его отдельно.

В качестве расширенного варианта вы можете объявить ObjectProvider<MyTargetBean>, который предоставляет несколько дополнительных вариантов доступа, включая getIfAvailable и getIfUnique.

Этот вариант JSR-330 называется Provider и используется с объявлением Provider<MyTargetBean> и соответствующим вызовом get() для каждой попытки поиска.

Конфигурация в следующем примере - это всего одна строка, но важно понимать "почему" и "как" за ней:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop
        https://www.springframework.org/schema/aop/spring-aop.xsd">

    <!-- bean-объект в области HTTP-сессии, представленный в качестве прокси -->
    <bean id="userPreferences" class="com.something.UserPreferences" scope="session">
        <!-- поручает контейнеру проксировать окружающий компонент -->
        <aop:scoped-proxy/>  <!-- строка, которая определяет прокси -->
    </bean>

    <!-- бин с singleton областью действия вводится через прокси вышеупомянутому бину -->
    <bean id="userService" class="com.something.SimpleUserService">
        <!-- ссылка на проксируемый компонент userPreferences -->
        <property name="userPreferences" ref="userPreferences"/>
    </bean>
</beans>

Чтобы создать такой прокси, вы вставляете дочерний элемент <aop:scoped-proxy/> в определение bean-объекта с областью применения. Почему для определения bean-объектов на уровнях запроса, сеанса (request, session) и настраиваемой (custom-scope) области требуется элемент <aop:scoped-proxy/>? Рассмотрите следующее определение одноэлементного (singleton) компонента и сопоставьте его с тем, что вам нужно определить для вышеупомянутых областей (обратите внимание, что следующее определение компонента userPreferences в его нынешнем виде неполно):

<bean id="userPreferences" class="com.something.UserPreferences" scope="session"/>

<bean id="userManager" class="com.something.UserManager">
    <property name="userPreferences" ref="userPreferences"/>
</bean>

В предыдущем примере одноэлементный (singleton) компонент (userManager) внедряется со ссылкой на компонент области HTTP сессии (userPreferences). Важным моментом здесь является то, что bean-компонент userManager является одноэлементным: он создается один раз для каждого контейнера, а его зависимости (в данном случае только один, bean-компонент userPreferences) также вводятся только один раз. Это означает, что bean-компонент userManager работает только с тем же объектом userPreferences (т. е. тем, с которым он был изначально внедрен).

Это не то поведение, которое требуется при внедрении бина с коротким сроком действия в бин с более долгим сроком действия (например, внедрение взаимодействующего бина с областью действия HTTP сессии в качестве зависимости в одноэлементный бин). Скорее, вам нужен один объект userManager, а для времени жизни сеанса HTTP вам нужен объект userPreferences, специфичный для сеанса HTTP. Таким образом, контейнер создает объект, который предоставляет тот же открытый интерфейс, что и класс UserPreferences (в идеале объект, который является экземпляром UserPreferences), который может извлечь реальный объект UserPreferences из механизма определения объема (HTTP request, Session и т. д.). Контейнер внедряет этот прокси-объект в бин userManager, который не знает, что эта ссылка UserPreferences является прокси. В этом примере, когда экземпляр UserManager вызывает метод для объекта UserPreferences с внедрением зависимостей, он фактически вызывает метод на прокси. Затем прокси извлекает реальный объект UserPreferences из (в данном случае) сеанса HTTP и делегирует вызов метода на извлеченный реальный объект UserPreferences.

Таким образом, вам потребуется следующая (правильная и полная) конфигурация при внедрении bean-объектов в рамках запроса и сеанса в взаимодействующие объекты, как показано в следующем примере:

<bean id="userPreferences" class="com.something.UserPreferences" scope="session">
    <aop:scoped-proxy/>
</bean>

<bean id="userManager" class="com.something.UserManager">
    <property name="userPreferences" ref="userPreferences"/>
</bean>


Читайте также:


Комментарии

Популярные сообщения из этого блога

Методы класса Object в Java

Как получить текущий timestamp в Java

Основные опции JVM для повышения производительности и отладки