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
) впроваджений за допомогою посилання на бін (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>
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ