IoC-контейнер Spring управляет не только созданием экземпляров ваших объектов (бинов), но и связыванием взаимодействующих объектов (или зависимостей). Если вам нужно внедрить (например) бин, находящийся в зоне видимости HTTP-request в другой бин с более долговременной областью видимости, вы можете внедрить АОП-прокси вместо бина, находящегося в области видимости. То есть вам нужно внедрить прокси-объект, который раскрывает тот же публичный интерфейс, что и объект, находящийся в области видимости, но который также может извлекать реальный целевой объект из соответствующей области видимости (например, HTTP-request) и делегировать вызовы методов на реальный объект.
Вы также можете использовать <aop:scoped-proxy/>
между бинами, которые определены как singleton
, при этом ссылка проходит через промежуточный прокси, который является сериализуемым и поэтому способен повторно получить целевой бин-одиночку при десериализации.
При объявлении <aop:scoped-proxy/>
для бина области видимсоти prototype
, каждый вызов метода на общем прокси приводит к созданию нового целевого экземпляра, которому затем передается вызов.
Кроме того, прокси, находящиеся в области видимости, - не единственный способ получить доступ к бинам из более коротких областей видимости безопасным для жизненного цикла способом. Также можно объявить точку внедрения (то есть аргумент конструктора или сеттера или автоматически связанное поле) как 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">
<!-- HTTP Session-scoped bean, открытый как прокси -->
<bean id="userPreferences" class="com.something.UserPreferences" scope="session">
<!-- дает инструкцию контейнеру проксировать окружающий бин -->
<aop:scoped-proxy/>
</bean>
<!-- бин, находящийся в области видимости singleton, внедренный с помощью прокси к вышеупомянутому бину bean -->
<bean id="userService" class="com.something.SimpleUserService">
<!-- ссылка на проксированный бин userPreferences -->
<property name="userPreferences" ref="userPreferences"/>
</bean>
</beans>
- Строка, определяющая прокси.
Чтобы создать такой прокси, вам нужно вставить дочерний элемент <aop:scoped-proxy/>
в определение бина, находящегося в области видимости. Почему определения бинов, находящихся в области видимости на уровнях request
, session
или в специальной области видимости, требуют элемент <aop:scoped-proxy/>
? Рассмотрите следующее определение бина-одиночки и сравните его с тем, что вам нужно определить для вышеупомянутых областей видимости (обратите внимание, что следующее определение бина userPreferences
в его нынешнем виде является неполным):
<bean id="userPreferences" class="com.something.UserPreferences" scope="session"/>
<bean id="userManager" class="com.something.UserManager">
<property name="userPreferences" ref="userPreferences"/>
</bean>
В предыдущем примере бин-одиночка (userManager
) внедрен c помощью ссылки на бин (userPreferences
), находящийся в зоне видимости HTTP Session
. Важным моментом здесь является то, что бин userManager
является объектом-одиночкой: его экземпляр создается ровно один раз для каждого контейнера, а его зависимости (в данном случае только одна, бин userPreferences
) также внедряются только один раз. Это означает, что бин userManager
работает только с тем же самым объектом userPreferences
(то есть с тем, с которым он был первоначально внедрен).
Это не та форма поведения, которая подойдет при внедрении бина с более коротким жизненным циклом, находящегося в области видимости, в бин с более длинным жизненным циклом, находящийся в области видимости (например, внедрение взаимодействующего бина, находящегося в области видимости HTTP Session
, в качестве зависимости в бин-одиночку). Скорее, требуется один объект userManager
, а на время существования HTTP Session
- объект userPreferences
, специфичный для данной HTTP Session
. Таким образом, контейнер создает объект, который открывает точно такой же публичный интерфейс, как и класс UserPreferences
(в идеале объект, который является экземпляром UserPreferences
), который может получить реальный объект UserPreferences
из механизма определения (HTTP запрос, Session
и так далее). Контейнер внедряет этот прокси-объект в бин userManager
, который не знает, что эта ссылка UserPreferences
является прокси. В этом примере, когда экземпляр UserManager
инициирует метод на объекте UserPreferences
с внедренной зависимостью он фактически вызывает метод на прокси. Затем прокси выполняет выборку реального объекта UserPreferences
из (в данном случае) HTTP Session
и делегирует вызов метода на полученный реальный объект UserPreferences
.
Таким образом, необходима следующая (правильная и полная) конфигурация при внедрении бинов, находящихся в области видимости request
и session
, в взаимодействующие объекты, как показано в следующем примере:
<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>
Выбор типа создаваемого прокси
По умолчанию, когда контейнер Spring создает прокси для бина, размеченного с помощью элемента <aop:scoped-proxy/>
, создается прокси класса на основе CGLIB.
CGLIB-прокси перехватывают только вызовы публичных методов! Не вызывайте непубличные методы на таком прокси. Они не делегируются реальному целевому объекту, находящемуся в области видимости.
Или же можно сконфигурировать контейнер Spring на создание стандартных прокси на основе интерфейса JDK для таких бинов, входящих в область видимости, указав false
для значения атрибута proxy-target-class
элемента <aop:scoped-proxy/>
. Использование прокси на основе интерфейса JDK означает, что вам не потребуются дополнительные библиотеки в пути класса вашего приложения, чтобы повлиять на такое проксирование. Однако это также означает и то, что класс бина, входящего в область видимости, должен реализовать как минимум один интерфейс, и что все взаимодействующие объекты, в которые внедряется бин, входящий в область видимости, должны ссылаться на него через один из его интерфейсов. В следующем примере показан прокси на основе интерфейса:
<!-- DefaultUserPreferences реализует интерфейс UserPreferences -->
<bean id="userPreferences" class="com.stuff.DefaultUserPreferences" scope="session">
<aop:scoped-proxy proxy-target-class="false"/>
</bean>
<bean id="userManager" class="com.stuff.UserManager">
<property name="userPreferences" ref="userPreferences"/>
</bean>
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ