что такое прокси spring

Прицельный обзор: Зачем Spring’у Proxy объекты

В Spring’е есть возможность указать родной для первого бина метод, при вызове которого будет создаваться и возвращаться новый экземпляр связанного бина. Помимо этого есть возможность переопределять методы с помощью MethodReplacer ‘a. Подробнее здесь.

Эти фичи требуют создания proxy объектов бинов, а реализуется все это безобразие с помощью всем известной библиотеки CGLib. После того, как beanFactory определил все метаданные для создания бина и не нашел его в кеше, то он пытается его создать следующим образом:

Оба эти класса объявляены внутри SubclassCreator ‘а. Метод enhancer.create() конструирует класс и создает экземпляр объекта этого класса.

Стоит заметить, что beanDefintion и beanFactory хранятся в полях SubclassCreator ‘а, и объявленные в нем классы имеют доступ к ним. Эти поля помечены модификатором final, что гарантирует доступ только на чтение к значениям этих полей.

Как можно заметить, переопределяемые методы представляют собой MethodOverride объект, который определяет его имя, имя объекта, которому он принадлежит и другую мета информацию о методе. Но сам по себе этот класс абстрактный, а наследуют его два класса:

Определяя тип переопределяемого метода, определяется индекс обработчика. Индекс обработчика задан в виде константы.

При вызове обработчика LookupOverrideMethodInterceptor вызывается создание бина с помощью beanFactory текущего бина по имени, которое задано в LookupOverride. Это имя попадает туда из соответствующей директивы конфигурации бинов:

При вызове ReplaceOverrideMethodInterceptor получается указанный в ReplaceOverride MethodReplacer и вызывается его основной метод с логикой, куда передается информация о вызванном методе, аргументах и самом объекте. Объект MethodReplacer также определяется в конфигурации директивой:

Как видно из данного обзора, подход оперирования именами бинов во всех частях конфигурации в некоторых ситуациях может вызывать достаточно большой overhead при работе с proxy объектами.

Posted by Code insiders 00:01 17.07.2012 Sighting review, Spring Framework

Источник

Что такое прокси в Spring?

Но я смог создать пример, который показывает, что мое предположение неверно:

Случай 1:

Singleton:

Прототип

Main:

Выход:

Здесь мы видим две вещи:

Случай 2:

Позвольте мне исправить определение MyBeanB :

В этом случае вывод:

Здесь мы видим две вещи:

Не могли бы вы объяснить, что происходит? Как работает режим прокси?

Я прочитал документацию:

но мне это не понятно.

Обновление

Случай 3:

Я исследовал еще один случай, в котором я извлек интерфейс из MyBeanB :

и в этом случае вывод:

Здесь мы видим две вещи:

ОТВЕТЫ

Ответ 1

Если вы проиллюстрируете это, это будет выглядеть как

Прокси-сервер @Scope ведет себя по-разному. В документации говорится:

[. ] вам нужно внедрить прокси-объект, который выставляет тот же общедоступный интерфейс как объект области , но это также может извлечь реальный целевой объект из соответствующей области (например, HTTP-запрос) и делегировать вызовы метода на реальный объект.

Если вы проиллюстрируете это, это будет выглядеть как

Поскольку MyBeanB является прототипом компонента, контекст всегда будет возвращать новый экземпляр.

Для целей этого ответа предположим, что вы получили MyBeanB непосредственно с помощью

В вашем первом примере

Указывает, должен ли компонент быть настроен как прокси-сервер с областью действия и если да, то должен ли прокси быть основан на интерфейсе или Подкласс основе.

Таким образом, Spring не создает прокси с заданной областью для получаемого компонента. Вы получаете этот боб с помощью

вы объявляете прокси-сервер с областью действия, который реализуется через cglib. При запросе bean-компонента этого типа из Spring с помощью

Spring знает, что MyBeanB является прокси с областью действия, и поэтому возвращает прокси-объект, который удовлетворяет API MyBeanB (т.е. реализует все его открытые методы), который внутренне знает, как извлечь фактический компонент типа MyBeanB для каждого вызова метода.

Ваш третий пример по сути такой же, как ваш второй.

Источник

Что такое прокси spring

Spring AOP uses either JDK dynamic proxies or CGLIB to create the proxy for a given target object. (JDK dynamic proxies are preferred whenever you have a choice).

If the target object to be proxied implements at least one interface then a JDK dynamic proxy will be used. All of the interfaces implemented by the target type will be proxied. If the target object does not implement any interfaces then a CGLIB proxy will be created.

If you want to force the use of CGLIB proxying (for example, to proxy every method defined for the target object, not just those implemented by its interfaces) you can do so. However, there are some issues to consider:

final methods cannot be advised, as they cannot be overriden.

You will need the CGLIB 2 binaries on your classpath, whereas dynamic proxies are available with the JDK. Spring will automatically warn you when it needs CGLIB and the CGLIB library classes are not found on the classpath.

The constructor of your proxied object will be called twice. This is a natural consequence of the CGLIB proxy model whereby a subclass is generated for each proxied object. For each proxied instance, two objects are created: the actual proxied object and an instance of the subclass that implements the advice. This behavior is not exhibited when using JDK proxies. Usually, calling the constructor of the proxied type twice, is not an issue, as there are usually only assignments taking place and no real logic is implemented in the constructor.

To force the use of CGLIB proxies set the value of the proxy-target-class attribute of the element to true:

To force CGLIB proxying when using the @AspectJ autoproxy support, set the ‘proxy-target-class’ attribute of the element to true :

8.6.1 Understanding AOP proxies

Consider first the scenario where you have a plain-vanilla, un-proxied, nothing-special-about-it, straight object reference, as illustrated by the following code snippet.

If you invoke a method on an object reference, the method is invoked directly on that object reference, as can be seen below.

что такое прокси spring. Смотреть фото что такое прокси spring. Смотреть картинку что такое прокси spring. Картинка про что такое прокси spring. Фото что такое прокси spring

Things change slightly when the reference that client code has is a proxy. Consider the following diagram and code snippet.

что такое прокси spring. Смотреть фото что такое прокси spring. Смотреть картинку что такое прокси spring. Картинка про что такое прокси spring. Фото что такое прокси spring

Okay, so what is to be done about this? The best approach (the term best is used loosely here) is to refactor your code such that the self-invocation does not happen. For sure, this does entail some work on your part, but it is the best, least-invasive approach. The next approach is absolutely horrendous, and I am almost reticent to point it out precisely because it is so horrendous. You can (choke!) totally tie the logic within your class to Spring AOP by doing this:

This totally couples your code to Spring AOP, and it makes the class itself aware of the fact that it is being used in an AOP context, which flies in the face of AOP. It also requires some additional configuration when the proxy is being created:

Finally, it must be noted that AspectJ does not have this self-invocation issue because it is not a proxy-based AOP framework.

Источник

Spring AOP. Маленький вопросик с собеседования

Довелось мне тут на днях побывать на очередном собеседовании. И задали мне там вот такой вот вопрос. Что на самом деле выполнится (с точки зрения транзакций), если вызвать method1()?

Ну, мы же все умные, документацию читаем или, по крайней мере, видео выступлений Евгения Борисова смотрим. Соответственно и правильный* ответ знаем (правильный* — это такой, который от нас ожидает услышать тот, кто спрашивает). И звучать он должен примерно так.

«В связи с тем, что для поддержки транзакций через аннотации используется Spring AOP, в момент вызова method1() на самом деле вызывается метод прокси объекта. Создается новая транзакция и далее происходит вызов method1() класса MyServiceImpl. А когда из method1() вызовем method2(), обращения к прокси нет, вызывается уже сразу метод нашего класса и, соответственно, никаких новых транзакций создаваться не будет».

Но знаете, как это бывает, вроде и ответ правильный уже давно знаешь. И применяешь это знание регулярно. А вдруг раз… и неожиданно задумаешься: «Подождите-ка, ведь если мы используем Spring AOP, то там могут создаваться прокси и через JDK, а могут и с CGLIB; а еще возможно, что CTW или LTW подключили. И что такой ответ всегда будет верен?».

Ну что ж: интересно? Надо проверить.

На самом деле меня заинтересовало не то, как будут транзакции создаваться, а само утверждение, что Spring AOP всегда создает прокси-объекты, и эти прокси-объекты имеют описанное выше поведение. Очевидно, что если для создания оберток используется JDK dynamic proxy, то это утверждение должно быть верным. Ведь в этом случае объекты создаются на основе интерфейсов. Такой объект будет полностью соответствовать паттерну Proxy и все выглядит вполне логично. Но CGLib использует другой подход, он создает классы наследники. А тут уже и начинают закрадываться сомнения, будет ли поведение идентичным. Еще интереснее всё становиться, когда мы решаем использовать внешние инструменты для связывания, т.е. CTW (compile-time weaving) и LTW (load-time weaving).

Для начала добавлю сюда пару определений из документации по Spring AOP, которые наиболее интересны в контексте того, что мы рассматриваем (собственно все остальные определения можно найти в самой документации, например вот здесь)

Теперь создадим простейший проект, на котором и будем проводить эксперименты. Для этого используем Spring Boot. Поскольку для разработки я использую STS, то опишу шаги именно для этой IDE. Но, по большому счету, все будет примерно также и для других инструментов.

Запускаем визард создания Spring Boot проекта: File > New > Spring Starter Project. И заполняем форму:

На самом деле, в связи с тем, что мы используем milestone версию, код будет чуть больше, будут добавлены ссылки на репозитарии.

Также будет сгенерирован класс приложения.

Наша аннотация (поскольку, как я уже сказал, нам интересны не транзакции, а то, как будет обрабатываться такая ситуация, то мы создаём свою аннотацию):

Собственно создали аспект, который будет привязываться к методам, имеющим аннотацию @Annotation1. Перед исполнением таких методов в консоль будет выводиться текст “Aspect1”.
Обращу внимание, что сам класс также аннотирован как Component. Это необходимо для того, что бы Spring мог найти этот класс и создать на его основе бин.

А теперь уже можно добавить и наш класс.

С целями, задачами и инструментами определились. Можно приступать к экспериментам.

JDK dynamic proxy vs CGLib proxy

Spring AOP uses either JDK dynamic proxies or CGLIB to create the proxy for a given target object. (JDK dynamic proxies are preferred whenever you have a choice).
If the target object to be proxied implements at least one interface then a JDK dynamic proxy will be used. All of the interfaces implemented by the target type will be proxied. If the target object does not implement any interfaces then a CGLIB proxy will be created.
If you want to force the use of CGLIB proxying (for example, to proxy every method defined for the target object, not just those implemented by its interfaces) you can do so.
To force the use of CGLIB proxies set the value of the proxy-target-class attribute of the element to true

Т.е., согласно документации, для создания прокси объектов может использоваться как JDK так и CGLib, но предпочтение должно отдаваться JDK. И, если класс имеет хотя бы один интерфейс, то именно JDK dynamic proxy и будет использоваться (хотя это можно изменить, явно задав флаг proxy-target-class). При создании прокси объекта с помощью JDK на вход передаются все интерфейсы класса и метод для имплементации нового поведения. В результате получаем объект, который абсолютно точно реализует паттерн Proxy. Все это происходит на этапе создания бинов, поэтому, когда начинается внедрение зависимостей, то в реальности внедрен будет этот самый прокси-объект. И все обращения будут производиться именно к нему. Но выполнив свою часть функционала, он обратиться к объекту исходного класса и передаст ему управление. Если же этот объект сам обратиться к одному из своих методов, то это будет уже прямой вызов без всяких прокси. Собственно именно это поведение и есть то, которое ожидается согласно правильному* ответу.

С этим вроде все понятно, а что же с CGLib? Ведь он создает на самом деле не прокси объект, а наследника класса. И вот тут мой мозг уже просто кричит: СТОП! Ведь тут мы имеем ну просто пример из учебника по ООП.

Где SampleChild – это, по сути, наш прокси-объект. И вот тут я уже начинаю сомневаться даже в ООП (или в своих знаниях о нём). Ведь если мы имеем наследование, то перекрытый метод должен вызываться вместо родительского и тогда поведение будет отличаться от того что мы имеем при использовании JDK dynamic proxy.

Хотя есть еще один вариант, возможно, я неправильно понял, как создаются объекты с помощью CGLib и, на самом деле, они «не совсем наследники», а тоже «какие-будь прокси». И, конечно же, самый простой способ быть уверенным хоть в чем-нибудь – это проверить на простом примере. Вот и создадим еще один маленький проектик.

На этот раз нам Spring уже не нужен, просто создаем простейший maven-проект и добавляем в pom зависимость на CGLib (собственно это и есть всё содержание нашего pom-файла).

Добавим в созданный проект наши два Sample-класса (ну так, на всякий случай, что бы убедить себя, что всё-таки принципы ООП незыблемы) и собственно класс с main() методом, в котором и будем выполнять наши тесты.

Первой строчкой вызываем method1() у объекта SampleChild класса (как уже сказал, ну просто что бы быть уверенным…) и далее создаем Enchancer. В объекте Enchancer переопределяем поведение метода method2(). После чего, собственно, создается новый объект и уже у него вызываем method1(). И запускаем.

Фух… Можно выдохнуть. Если верить первым двум строкам вывода, то за последние 20 лет в ООП ничего не изменилось.

Последние две строчки говорят, что и с объектами, создаваемыми через CGLib, моё понимание было абсолютно верным. Это действительно наследование. И с этого момента мои сомнения по поводу того, что созданный таким образом объект будет работать абсолютно аналогично JDK dynamic proxy объекту, только усилились. Поэтому больше не откладываем, а запускаем наш проект, который мы создали для экспериментов. Для этого нам в классе приложения надо будет добавить runner, и наш класс приобретет следующий вид.

Все-таки нравится мне этот Spring Boot, все, что надо сделать, уже сделано за нас (уж извините за это маленькое лирическое отступление). Аннотация @SpringBootApplication включает в себя множество других аннотаций, которые пришлось бы писать, не используй мы Spring Boot. По умолчанию уже указано, что данный класс содержит конфигурацию, а также что необходимо сканировать пакеты на наличие в них определений бинов.

В тоже время прямо здесь мы создаем новый бин CommandLineRunner, который собственно и выполнит вызов метода method1() у нашего бина myService.

Хм… Такой вывод оказался для меня неожиданным. Да, он полностью соответствует ожиданиям, если Spring AOP использует JDK dynamic proxy. Т.е. при вызове method1() нашего сервиса сперва отработал аспект, после чего управление было передано объекту класса MyServiceImpl и дальнейшие вызовы будут производиться в пределах этого объекта.

Но мы ведь не указали у класса ни одного интерфейса. И я ожидал, что Spring AOP в данном случае будет использовать CGLib. Может Spring сам каким-то образом обходит это ограничение и, как и написано в документации, старается использовать JDK dynamic proxy в качестве основного варианта, если явно не указано обратного?

Немного посидев над стеком вызовов в момент поднятия приложения, т.е. на этапе создания бинов, нашел место, где собственно и производится выбор какую из библиотек использовать. Происходит это в классе DefaultAopProxyFactory, а именно в методе

В Javadocs к данному классу написано

Default AopProxyFactory implementation, creating either a CGLIB proxy or a JDK dynamic proxy.

Creates a CGLIB proxy if one the following is true for a given AdvisedSupport instance:

• the optimize flag is set
• the proxyTargetClass flag is set
• no proxy interfaces have been specified

In general, specify proxyTargetClass to enforce a CGLIB proxy, or specify one or more interfaces to use a JDK dynamic proxy.

И для того что бы нам убедиться, что никаких интерфейсов к нашему классу не появилось достаточно проверить условие hasNoUserSuppliedProxyInterfaces(config). В нашем случае оно возвращает true. И, как результат, вызывается создание прокси через CGLib.

Другими словами, Spring AOP не просто использует CGLib для создания наследников от классов бинов, а реализует на этом этапе полноценный прокси объект (т.е. объект соответствующий паттерну Proxy). Как именно он это делает, каждый желающий может сам посмотреть, пройдясь по шагам под отладкой в данном приложении. Куда более важным для меня был вывод, что абсолютно без разницы, какую библиотеку под капотом использует Spring. В любом случае поведение его будет одинаковым. В любом случае для организации сквозного программирования будет создан прокси объект, который собственно и обеспечит вызовы методов объекта реального класса. С другой стороны, если какие-либо методы вызываются из методов этого же класса, то перехватить (перекрыть) средствами Spring AOP их уже не получиться.

На этом можно было бы остановиться с поисками отличий в поведении прокси созданных через JDK и CGLib. Но мой пытливый ум продолжал свои попытки найти хоть какое-то несоответствие. И я решил добиться того, что прокси объект будет создан через JDK. Теоретически это должно быть просто и не занять много времени. Возвращаясь к документации можно вспомнить, что именно этот вариант должен использоваться по умолчанию, с единственной оговоркой: у объекта должны быть интерфейсы. Также флаг ProxyTargetClass должен быть сброшен (т.е. false).

Первое условие выполняется путём добавления в проект соответствующего интерфейса (не буду уже приводить этот код, думаю достаточно очевидно, как он будет выглядеть). Второе – путём добавления в конфигурацию соответствующей аннотации, т.е. как-то так

Но на деле всё оказалось не так просто. Обе проверки — config.isProxyTargetClass() и hasNoUserSuppliedProxyInterfaces(config) по-прежнему возвращали true. На этом я всё-таки решил остановиться. Я получил ответ на свой вопрос, а также сделал отметку в памяти, что (как минимум при использовании Spring 5), несмотря на утверждения документации, прокси объекты с большей вероятностью будут создаваться с помощью CGLib.

Кстати, если кто-то знает, как принудить Spring AOP использовать JDK, буду ждать ваших комментариев.

Compile-time weaving и AspectJ

Ну что ж, наша гипотеза, что поведение кода под аспектами будет зависеть от того, какая библиотека используется под капотом, потерпела неудачу. Тем не менее, это еще не все возможности, которые предоставляет Spring в плане АОП. Одной из самых интересных (по моему мнению) возможностей, которые Spring AOP нам предоставляет, это возможность использовать компилятор AspectJ. Т.е. фреймворк написан так, что если мы для описания аспектов используем аннотации @AspectJ, то нам не придется вносить никаких изменений, что бы перейти от runtime weaving к compile-time weaving. Кроме того (очередное достоинство Spring Boot) все необходимые зависимости уже включены. Нам остается только подключить плагин, который и выполнит компиляцию.

Для этого внесем изменения в наш pom’ник. Теперь секция build будет выглядеть следующим образом.

Я не буду останавливаться на параметрах конфигурации данного плагина, лишь поясню, почему закомментировал цель test-compile. При сборке проекта maven падал с ошибкой на этапе запуска тестов. Порывшись по интернету, я видел, что эта проблема известная, и есть способы ее решения. Но, поскольку, в нашем тестовом приложении тесты как бы и отсутствуют, то самым простым решением было просто отключить их вызов совсем (а заодно и вызов плагина на этапе их компиляции).

Собственно это все изменения, которые нам надо было сделать. Можем запускать наше приложение

В этот момент я осознал всю глубину своего непонимания аспектов. Я перепроверил весь код несколько раз. Попытался прокрутить в уме, где и что я сделал не так. Но так и не осознал, почему получил такой результат (нет, ну честно, ведь не очевидно, почему код аспекта вызывается дважды перед каждым вызовом метода нашего класса).

В конце концов, я все-таки догадался посмотреть логи, полученные на этапе сборки проекта и обнаружил там следующие 4 строки.

Всё встало на свои места. Связывание кода произошло не только в месте исполнения методов, но и в месте их вызовов. Дело в том, что Spring AOP имеет ограничение, которое позволяет связывать код только по месту исполнения. Возможности AspectJ в этом плане значительно шире. Убрать лишние вызовы аспектов достаточно просто, например можно вот так модифицировать код аспекта.

После чего наш вывод примет ожидаемый вид.

Ну что ж, здесь мы уже можем сказать, что наш правильный* ответ все-таки требует уточнения. Как минимум, если у вас на собеседовании спросят про ожидаемое поведение приведенного в начале кода, стоит уточнить: «А не используем ли мы компайл-тайм связывания? Ведь в коде класса это никак не отражается, а pom’ник нам не предоставили».

Что еще тут хотелось бы отметить. В документации по Spring AOP описывается, что все было сделано, что бы можно было легким движением руки (с) переключится с рантайм связывания на компайл-тайм связывание. И как мы видели, это действительно так. Код для обоих случаев использовался один и тот же (на самом деле работа, которая была сделана значительно больше, чем просто создание обработчиков для одинаковых аннотаций, но останавливаться на этом не буду, желающие могут почитать документацию и впечатлиться самостоятельно).

При всем при этом, в зависимости от выбранного варианта связывания, поведение может оказаться разным (и даже неожиданным). Кроме тех случаев, что уже рассмотрены, хочу обратить внимание, что для компилятора AspectJ не нужна аннотация Component. Т.е. если мы ее уберем, то Spring AOP не найдет такого бина и аспект не будет задействован. В то же время, если мы решим переключиться на AspectJ компиляцию, то этот аспект будет действующим, а вот поведение приложения – непредсказуемым.

Load-time weaving

Воодушевленный полученным результатом предыдущего этапа, я уже мысленно потирал руки. Ведь если при связывании кода во время компиляции мы получили поведение отличное, того которое мы имели для связывания во время исполнения, то вероятно примерно такой же результат стоит ожидать и в случае связывания во время загрузки (ну, я так думал). Быстренько ознакомившись с документацией по этому поводу, выяснил, что LTW в Spring доступно из коробки. Все что надо – просто в классе конфигурации добавить еще одну аннотацию: @EnableLoadTimeWeaving.

Ах, да. Не забываем удалить aspectj-maven-plugin из pom’ника, который мы чуть раньше добавили. Он нам больше не нужен.

И теперь можно запускать… Нет, на самом деле есть еще один нюанс.

Generic Java applications

When class instrumentation is required in environments that do not support or are not supported by the existing LoadTimeWeaver implementations, a JDK agent can be the only solution. For such cases, Spring provides InstrumentationLoadTimeWeaver, which requires a Spring-specific (but very general) VM agent,org.springframework.instrument-.jar (previously named spring-agent.jar).
To use it, you must start the virtual machine with the Spring agent, by supplying the following JVM options:
-javaagent:/path/to/org.springframework.instrument-.jar

Здесь в документации есть небольшая неточность, название библиотеки: spring-instrument-.jar. И эта библиотека уже на вашем компьютере (спасибо Spring Boot и Maven). В моем случае путь к ней выглядит вот так: c:\Users\\.m2\repository\org\springframework\spring-instrument\5.0.2.RELEASE\spring-instrument-5.0.2.RELEASE.jar. Если вы, как и я пользуетесь STS для разработки, выполняем следующие шаги. Открываем меню Run > Run Configurations… Находим там Spring Boot App и в нем конфигурацию запуска нашего приложения. Открываем закладочку Arguments. В поле VM arguments: добавляем параметр -javaagent:c:\Users\\\.m2\repository\org\springframework\spring-instrument\5.0.2.RELEASE\spring-instrument-5.0.2.RELEASE.jar. И теперь запускаем.

Ну вот, опять. Не такой я результат ожидал. Может все-таки не так просто подключить LTW, может еще что-то надо где-то настроить? И опять же самый простой способ убедиться, что наши настройки работают, запустить приложение под отладкой и посмотреть какой код исполняется. В приведенном выше фрагменте документации сказано, что в нашем случае должен использоваться класс InstrumentationLoadTimeWeaver. И есть в нем метод, который точно должен вызываться на этапе создания бинов.

Именно здесь и поставим точку прерывания.

Стартуем… Останавливаемся… DefaultAopProxyFactory.createAopProxy(). Сработала точка останова которую мы поставили ранее, когда разбирались JDK vs CGLib. Снова запускаем, и на этот раз уже останавливается именно там, где ожидали. Сомнений больше не осталось.
Ну что ж, и в этом случае Spring AOP создает все те же прокси объекты, что мы видели ранее. С тем лишь отличием, что связывание теперь будет производиться не на этапе исполнения, а уже на этапе загрузки классов. За деталями этого процесса прошу в код.

Заключение

Ну что ж, похоже, что наш правильный* ответ действительно является правильным, хоть и с оговоркой (см. главу «Compile-time weaving и AspectJ»).

Зависит ли поведение от того какой механизм проксирования выбран: JDK или CGLib? Фреймворк написан так, что бы то, что там под капотом никак не влияло на то, какой результат мы получим. И даже подключение LTW не должно никак на это влиять. И в рамках рассмотренного нами примера мы этих различий не наблюдали. И всё же в документации можно найти упоминание, что различия есть. JDK dynamic proxy могут перекрывать только public методы. CGLib proxy – кроме public, также и protected методы и package-visible. Соответственно, если мы явно не указали для среза (pointcut) ограничение «только для public методов», то потенциально можем получить неожиданное поведение. Ну а если есть какие-либо сомнения, можно принудительно включить использование CGLib для генерации прокси-объектов (похоже, в последних версиях Spring это уже сделали за нас).

Spring AOP является proxy-based фреймворком. Это значит, что всегда будут создаваться прокси-объекты на любой наш бин подпадающий под действие аспекта. И тот момент, что вызов одного метода класса другим не может быть перехвачен средствами Spring AOP – это не ошибка или недоработка разработчиков, а особенность паттерна положенного в основу реализации фреймворка. С другой стороны, если нам все-таки надо добиться, что бы в рассматриваемом случае код аспекта выполнялся, то надо учесть этот факт и писать код, так что бы обращения проходили через прокси-объект. В документации есть пример как это можно сделать, но даже там прямо написано, что это нерекомендуемое решение. Другой вариант – это «заинжектить» сервис сам в себя. В последних версиях Spring вот такое решение будет рабочим.

Правда, этот вариант применим только для бинов со scope равным «singleton». Если же поменять scope на «prototype», то приложение не сможет подняться из-за попытки бесконечного внедрения сервиса в самого себя. Выглядеть это будет так

На что еще хотелось бы обратить внимание, так это на тот оверхэд, который сопровождает нас при использовании Spring AOP. Повторюсь, что всегда будут создаваться прокси-объекты на любой наш бин подпадающий под действие аспекта. Причем их будет ровно столько, сколько инстансов бинов будет создано. В примере мы рассматривали singleton бин, соответственно только один прокси-объект был создан. Используем prototype – и количество прокси-объектов будет соответствовать количеству внедрений. Сам прокси объект не выполняет вызовов методов целевого объекта, он содержит цепочку интерсепторов, которые делают это. Не зависимо от того, попадает или нет каждый конкретный метод целевого объекта под действие аспекта, его вызов проходит через прокси-объект. Ну и плюс к этому будет создан как минимум один инстанс класса аспекта (в нашем примере он будет только один, но этим можно управлять).

Послесловие

Чувство когнитивного диссонанса меня так и не покинуло. Что-то в этом примере с транзакциями все-таки не так. Продублирую его код.

Хотя нет, кажется, понял. И дело не только и даже не столько в АОП. Мне не очевидно, зачем может понадобиться посередине одной транзакции создавать другую (исходя из того, что изначально предполагалось получить именно такой результат).

Если кто-то может привести пример с реальных проектов, когда это было необходимо, буду признателен увидеть это в комментариях.

Источник

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *