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>
  1. Строка, определяющая прокси.

Чтобы создать такой прокси, вам нужно вставить дочерний элемент <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>