Если вы используете IoC-контейнер Spring (ApplicationContext
или BeanFactory
) для своих бизнес-объектов (а вам следует это делать!), ваша задача - использовать одну из реализаций FactoryBean
из Spring AOP. (Помните, что бин-фабрика вводит уровень косвенной адресации, что позволяет создавать объекты другого типа).
Основной способ создания прокси АОП в Spring – это использование org.springframework.aop.framework.ProxyFactoryBean
. Он дает полный контроль над срезами, любыми советами, которые применяются, и их упорядочиванием. Однако есть и более простые варианты, которые являются более предпочтительными, если вам не требуется такой контроль.
Основы
ProxyFactoryBean
, как и другие реализации FactoryBean
из Spring, вводит уровень косвенности. Если вы определяете ProxyFactoryBean
с именем foo
, объекты, ссылающиеся на foo
, видят не сам экземпляр ProxyFactoryBean
, а объект, созданный реализацией метода getObject()
в ProxyFactoryBean
. Данный метод создает прокси АОП, который обертывает целевой объект.
Одним из наиболее важных преимуществ использования ProxyFactoryBean
или другого IoC-совместимого класса для создания прокси АОП является то, что советам и срезами также можно управлять посредством IoC. Это эффективная особенность, позволяющая применять определенные подходы, которые трудно реализовать с помощью других АОП-фреймворков. Например, совет может сам ссылаться на объекты приложения (помимо цели, которая должна находиться в любом АОП-фреймворке), используя все возможности модульности, которые привносит внедрение зависимостей.
Свойства JavaBean
Как и большинство реализаций FactoryBean
, предоставляемых Spring, класс ProxyFactoryBean
сам является классом JavaBean. Его свойства используются для:
-
Задания цели, которую нужно проксировать.
-
Задания необходимости использовать CGLIB (описано далее, см. также раздел "прокси на основе JDK и CGLIB").
Некоторые ключевые свойства наследуются от org.springframework.aop.framework.ProxyConfig
(суперкласс для всех фабрик прокси АОП в Spring). Эти ключевые свойства состоят из:
-
proxyTargetClass
: являетсяtrue
, если будет проксирован целевой класс, а не интерфейсы целевого класса. Если значение этого свойства установлено вtrue
, то создаются CGLIB-прокси. -
optimize
: Управляет тем, применяются ли агрессивные способы оптимизации к прокси, созданным с помощью CGLIB. Не стоит легкомысленно использовать этот параметр, если нет понимания, как соответствующий прокси АОП управляет оптимизацией. В настоящее время используется только для CGLIB-прокси. Параметр не влияет на динамические JDK-прокси. -
frozen
: Если конфигурация прокси имеет свойствоfrozen
, то изменения в ней больше не допускаются. Это можно использовать как для легкой оптимизации, так и в тех случаях, если не требуется, чтобы вызывающие подпрограммы имели возможность манипулировать прокси (через интерфейсAdvised
) после его создания. По умолчанию значение этого свойства равноfalse
, поэтому изменения (например, добавление дополнительных советов) разрешены. -
exposeProxy
: Определяет, должен ли текущий прокси быть открыт вThreadLocal,
чтобы к нему могла получить доступ цель. Если цели необходимо получить прокси, а свойствоexposeProxy
установлено вtrue
, цель может использовать методAopContext.currentProxy()
.
Другими свойствами, специфичными для ProxyFactoryBean
, являются:
-
proxyInterfaces
: МассивString
имен интерфейсов. Если данное свойство не указано, то используется CGLIB-прокси для целевого класса. -
interceptorNames
:String
массив именAdvisor
, перехватчиков или других советов для применения. Упорядочивание играет важную роль, все происходит в порядке очерёдности. Таким образом, первый перехватчик в списке первым сможет перехватить вызов.Имена – это имена бинов в текущей фабрике, включая имена бинов из родительских фабрик. В данном случае нельзя упоминать ссылки на бины, поскольку это приведет к тому, что
ProxyFactoryBean
будет игнорировать параметр объекта-одиночки в совете.Можно дополнить имя перехватчика звездочкой (
*
). Это приведет к тому, что будут применены все бины-советники с именами, начинающимися с части перед звездочкой. -
singleton: Должна ли фабрика возвращать одиночный объект, независимо от того, как часто вызывается метод
getObject()
. Несколько реализацийFactoryBean
обеспечивают такой метод. Значение по умолчанию -true
. Если нужно использовать совет, сохраняющий состояние, – например, для примесей, сохраняющих состояние, - используйте советы-прототипы вместе со значением свойства singleton, равнымfalse
.
Прокси на основе JDK и CGLIB
Данный раздел служит в качестве предписывающей документации касательно того, каким образом ProxyFactoryBean
выбирает между созданием прокси на основе JDK и на основе CGLIB для определенного целевого объекта (который должен быть проксирован).
ProxyFactoryBean
в отношении создания прокси на основе JDK или CGLIB изменилась между версиями 1.2.x и 2.0 Spring. ProxyFactoryBean
теперь имеет ту же семантику в отношении автоматического определения интерфейсов, что и класс TransactionProxyFactoryBean
.Если класс целевого объекта, который должен быть проксирован (далее просто называется целевым классом), не реализует никаких интерфейсов, создается прокси на основе CGLIB. Это самый простой сценарий, поскольку JDK-прокси основаны на интерфейсах, а отсутствие интерфейсов означает, что JDK-проксирование вообще невозможно. Можно подключить целевой бин и указать список перехватчиков, установив свойство interceptorNames
. Обратите внимание, что прокси на основе CGLIB создается, даже если свойство proxyTargetClass
в ProxyFactoryBean
было установлено в false
. (Делать это бессмысленно и лучше убрать это свойство из определения бина, потому что оно, в лучшем случае, избыточно, а в худшем - сбивает с толку).
Если целевой класс реализует один (или несколько) интерфейсов, то тип создаваемого прокси зависит от конфигурации ProxyFactoryBean
.
Если свойство proxyTargetClass
в ProxyFactoryBean
было установлено в true
, то создается прокси на основе CGLIB. Это логично и соответствует принципу наименьшего удивления. Даже если свойство proxyInterfaces
в ProxyFactoryBean
было установлено для одного или нескольких полностью уточненных имен интерфейсов, тот факт, что свойство proxyTargetClass
установлено в true
, приводит к осуществлению проксирования на основе CGLIB.
Если свойство proxyInterfaces
в ProxyFactoryBean
было установлено для одного или нескольких полностью уточненных имен интерфейсов, создается прокси на основе JDK. Созданный прокси реализует все интерфейсы, которые были заданы в свойстве proxyInterfaces
. Если целевой класс реализует гораздо больше интерфейсов, чем задано в свойстве proxyInterfaces
, это замечательно, но эти дополнительные интерфейсы не будут реализованы возвращаемым прокси.
Если свойство proxyInterfaces
в ProxyFactoryBean
не было задано, но целевой класс реализует один (или более) интерфейсов, ProxyFactoryBean
автоматически определяет тот факт, что целевой класс действительно реализует по крайней мере один интерфейс, и создается прокси на основе JDK. Интерфейсы, которые фактически проксируются, – это все интерфейсы, которые реализует целевой класс. По сути, это то же самое, что указать список всех и каждого интерфейса, который реализует целевой класс, для свойства proxyInterfaces
. Тем не менее, так значительно меньше трудозатрат и меньше вероятность опечаток.
Проксирование интерфейсов
Рассмотрим простой пример ProxyFactoryBean
в действии. Этот пример включает в себя:
-
Целевой проксируемый бин. Это определение бина
personTarget
в примере. -
Advisor
иInterceptor
, используемые для снабжения советом. -
Определение прокси-бина АОП для указания целевого объекта (бин
personTarget
), интерфейсов для прокси и советов, которые будут применены.
В следующем листинге показан пример:
<bean id="personTarget" class="com.mycompany.PersonImpl">
<property name="name" value="Tony"/>
<property name="age" value="51"/>
</bean>
<bean id="myAdvisor" class="com.mycompany.MyAdvisor">
<property name="someProperty" value="Custom string property value"/>
</bean>
<bean id="debugInterceptor" class="org.springframework.aop.interceptor.DebugInterceptor">
</bean>
<bean id="person"
class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="proxyInterfaces" value="com.mycompany.Person"/>
<property name="target" ref="personTarget"/>
<property name="interceptorNames">
<list>
<value>myAdvisor</value>
<value>debugInterceptor</value>
</list>
</property>
</bean>
Обратите внимание, что свойство interceptorNames
принимает список String
, который содержит имена бинов перехватчиков или советников в текущей фабрике. Можно использовать советники, перехватчики, объекты с советами "перед", "после возврата" и "генерация исключения". Упорядочивание советников имеет большое значение.
ProxyFactoryBean
установлено в false
, он должен иметь возможность возвращать независимые экземпляры прокси. Если какой-либо из советников сам является прототипом, то потребуется возвращать независимый экземпляр, поэтому необходимо иметь возможность получить экземпляр прототипа из фабрики. Одной ссылки недостаточно.Определение бина person
, показанное ранее, может быть использовано вместо реализации Person
следующим образом:
Person person = (Person) factory.getBean("person");
val person = factory.getBean("person") as Person;
Другие бины в том же IoC-контексте могут выражать сильно типизированную зависимость от него, как в случае с обычным объектом Java. В следующем примере показано, как это сделать:
<bean id="personUser" class="com.mycompany.PersonUser">
<property name="person"><ref bean="person"/></property>
</bean>
Класс PersonUser
в этом примере открывает свойство типа Person
. Что касается прокси АОП, то его можно использовать прозрачным образом вместо реализации "настоящего" типа person. Однако его класс будет динамическим. Можно было бы привести его к интерфейсу Advised
(о нем речь пойдет позже).
Вы можете скрыть различие между целью и прокси, используя анонимный внутренний бин. Отличается только определение ProxyFactoryBean
. Советы приведены только для полноты картины. В следующем примере показано, как использовать анонимный внутренний бин:
<bean id="myAdvisor" class="com.mycompany.MyAdvisor">
<property name="someProperty" value="Custom string property value"/>
</bean>
<bean id="debugInterceptor" class="org.springframework.aop.interceptor.DebugInterceptor"/>
<bean id="person" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="proxyInterfaces" value="com.mycompany.Person"/>
<!-- Используйте внутренний бин, а не локальную ссылку на цель -->
<property name="target">
<bean class="com.mycompany.PersonImpl">
<property name="name" value="Tony"/>
<property name="age" value="51"/>
</bean>
</property>
<property name="interceptorNames">
<list>
<value>myAdvisor</value>
<value>debugInterceptor</value>
</list>
</property>
</bean>
Преимуществом использования анонимного внутреннего бина является то, что существовать будет только один объект типа Person
. Это целесообразно, если мы хотим предотвратить получение пользователями контекста приложения ссылки на объект, который не снабжен советом, или если нужно избежать двусмысленности при автоматическом обнаружении и связывании при IoC в Spring. Также, вероятно, преимущество заключается еще в том, что определение ProxyFactoryBean
является самодостаточным. Тем не менее, бывают случаи, когда возможность получить из фабрики цель, которая не была снабжена советом, может оказаться преимуществом (например, в определенных сценариях тестирования).
Проксирование классов
Что если нужно проксировать класс, а не один или несколько интерфейсов?
Представьте, что в нашем предыдущем примере не было интерфейса Person
. Нам нужно было снабдить советом класс под названием Person
, который не реализовывал никакого бизнес-интерфейса. В этом случае можно настроить Spring на использование CGLIB-проксирования, а не динамических прокси. Для этого установите свойство proxyTargetClass
для ProxyFactoryBean
, продемонстрированного ранее, в true
. Хотя лучше всего программировать на интерфейсах, а не на классах, возможность снабжать советом классы, не реализующие интерфейсы, может быть полезна при работе с устаревшим кодом. (В целом, Spring не является прескриптивным. Хотя это и облегчает применение передового опыта, так можно избежать принуждения к определенному подходу).
Если хотите, то можете принудительно использовать CGLIB в любом случае, даже если у вас есть интерфейсы.
CGLIB-проксирование работает путем генерации подкласса целевого класса во время выполнения программы. Spring конфигурирует этот сгенерированный подкласс таким образом, чтобы он делегировал вызовов методов исходной цели. Подкласс используется для реализации шаблона "Декоратор (Decorator)", связывая с ним советы.
CGLIB-проксирование, как правило, должно быть прозрачным для пользователей. Однако есть некоторые моменты, которые необходимо учитывать:
-
Final
методы нельзя снабдить советом, так как их нельзя переопределить. -
Нет необходимости добавлять CGLIB в ваш путь классов. Начиная с версии Spring 3.2, CGLIB перепакован и включен в JAR модуля ядра spring-core. Другими словами, АОП на основе CGLIB работает "из коробки", как и динамические JDK-прокси.
Разница в производительности между CGLIB-проксированием и динамическим проксированием незначительна. Производительность не должна быть решающим фактором в этом случае.
Использование "глобальных" советников
При добавлении звездочки к имени перехватчика, все советники с именами бинов, которые соответствуют части, находящейся перед звездочкой, добавляются в цепочку советников. Это может пригодиться, если нужно добавить стандартный набор "глобальных" советников. В следующем примере определены два глобальных советника:
<bean id="proxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="target" ref="service"/>
<property name="interceptorNames">
<list>
<value>global*</value>
</list>
</property>
</bean>
<bean id="global_debug" class="org.springframework.aop.interceptor.DebugInterceptor"/>
<bean id="global_performance" class="org.springframework.aop.interceptor.PerformanceMonitorInterceptor"/>
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ