Якщо ви надаєте перевагу формату на основі XML, Spring також забезпечує підтримку визначення аспектів за допомогою тегів простору імен aop. Підтримуються такі самі вирази зрізу і типи порад, як і при використанні стилю @AspectJ. Отже, в цьому розділі ми зосередимося на цьому синтаксисі і відішлемо читача до опису в попередньому розділі для ознайомлення з особливостями написання виразів зрізу та прив'язки параметрів ради.

Щоб використовувати теги простору імен АОП, які описано в цьому розділі, необхідно імпортувати схему spring-aop.

У конфігураціях Spring всі елементи аспектів та радників (advisors) потрібно помістити в елемент <aop:config> (можна мати більше одного елемента <aop:config> у конфігурації контексту програми). Елемент <aop:config> може містити елементи зрізів, радників та аспектів (зверни увагу, що вони мають бути оголошені саме в такому порядку).

Стиль конфігурації <aop:config> активно використовує механізм автопроксування Spring. Це може призвести до проблем (наприклад, порада не буде пов'язана), якщо ти вже використовуєш явне автопроксування за допомогою BeanNameAutoProxyCreator або чогось подібного. Рекомендується використовувати або лише стиль <aop:config>, або лише стиль AutoProxyCreator і ніколи не змішувати їх.

Оголошення аспекту

Якщо ти використовуєш підтримку схем, аспект — це звичайний об'єкт Java, визначений як бін в контексті твого додатка Spring. Стан та логіка роботи фіксуються в полях та методах об'єкта, а інформація про зрізи та поради — в XML.

Ти можеш оголосити аспект, використовуючи елемент <aop:aspect>, та послатися на базовий бін за допомогою атрибуту ref, як показано в наступному прикладі:


<aop:config>
    <aop:aspect id="myAspect" ref="aBean">
        ...
    </aop:aspect>
</aop:config>
<bean id="aBean" class="...">
    ...
</bean>

Бін, що підтримує аспект (в даному випадку aBean), звісно, можна конфігурувати і впровадити з залежностями, як і будь-який інший бін Spring.

Оголошення зрізу

Можна оголосити іменований зріз усередині елемента <aop:config>, що дозволить спільно використовувати визначення зрізу в кількох аспектах і радниках.

Зріз, який представляє виконання будь-якої бізнес-служби на рівні служб, може бути визначений таким чином:


<aop:config>
    <aop:pointcut id="businessService"
        expression="execution(* com.xyz.myapp.service.*.*(..))"/>
</aop:config>

Зверни увагу, що саме вираз зрізу використовує ту ж мову виразів зрізів AspectJ. Якщо ти використовуєш стиль оголошення на основі схем, можна посилатися на іменовані зрізи, визначені в типах (@Aspects) у виразі зрізу. Інший спосіб визначення вищезгаданого зрізу міг би виглядати так:


<aop:config>
    <aop:pointcut id="businessService"
        expression="com.xyz.myapp.CommonPointcuts.businessService()"/>
</aop:config>

Припустимо, що у тебе є аспект CommonPointcuts.

Оголошення зрізу всередині аспекту дуже схоже на оголошення зрізу верхнього рівня, як показано в наступному прикладі:


<aop:config>
    <aop:aspect id="myAspect" ref="aBean">
        <aop:pointcut id="businessService"
            expression="execution(* com.xyz.myapp.service.*.*(..))"/>
        ...
    </aop:aspect>
</aop:config>

Точно так само, як і аспект @AspectJ, зрізи, оголошені за допомогою стилю визначення на основі схем, можуть здійснювати збір контексту точки з'єднання. Наприклад, наступний зріз здійснює збір об'єкта this як контекст точки з'єднання і передає його в пораду:


<aop:config>
    <aop:aspect id="myAspect" ref="aBean">
        <aop:pointcut id="businessService"
            expression="execution(* com.xyz.myapp.service.*.*(..)) && this(service)"/>
        <aop:before pointcut-ref="businessService" method="monitor"/>
        ...
    </aop:aspect>
</aop:config>

Порада має бути оголошена для отримання зібраного контексту точки з'єднання шляхом включення параметрів із відповідними іменами, як показано нижче:

Java
public void monitor(Object service) {
    // ...
}
Kotlin
fun monitor(service: Any) {
    // ...
}

При об'єднанні виразів зрізів використовувати &amp;amp; у XML-документі незручно, тому замість &amp;amp;, || та ! можна використовувати ключові слова and, or та not відповідно. Наприклад, попередній зріз можна записати так:


<aop:config>
    <aop:aspect id="myAspect" ref="aBean">
        <aop:pointcut id="businessService"
            expression="execution(* com.xyz.myapp.service.*.*(..)) and this(service)"/>
        <aop:before pointcut-ref="businessService" method="monitor"/>
        ...
    </aop:aspect>
</aop:config>

Зверни увагу, що зрізи, визначені таким чином, посилаються на свій XML id і не можуть використовуватись як іменовані зрізи для компонування складових зрізів. Таким чином, підтримка іменованих зрізів у стилі визначення на основі схем більш обмежена, ніж у стилі @AspectJ.

Оголошення порад

Для підтримки АОП на основі схем використовуються ті ж п'ять видів порад, що і для стилю @AspectJ, і вони мають таку саму семантику.

Порада Before

Порада "перед" виконується перед виконанням відповідного методу. Він оголошується всередині <aop:aspect> за допомогою елемента <aop:before>, як показано в наступному прикладі:


<aop:aspect id="beforeExample" ref="aBean">
    <aop:before
        pointcut-ref="dataAccessOperation"
        method="doAccessCheck"/>
    ...
</aop:aspect>

Тут dataAccessOperation – це id зрізу, визначеного на верхньому (<aop:config>) рівні. Щоб визначити вбудовування зрізу, заміни атрибут pointcut-ref на атрибут pointcut, як показано нижче:


<aop:aspect id="beforeExample" ref="aBean">
    <aop:before
        pointcut="execution(* com.xyz.myapp.dao.*.*(..))"
        method="doAccessCheck"/>
    ...
</aop:aspect>

Як уже зазначалося при розгляді стилю @AspectJ, використання іменованих зрізів може значно підвищити читабельність коду.

Атрибут method ідентифікує метод (doAccessCheck), який надає тіло поради. Цей метод потрібно визначити для біна, який посилається елемент аспекту, що містить пораду. Перед виконанням операції доступу до даних (точка з'єднання виконання методу, що відповідає виразу зрізу) викликається метод doAccessCheck для бина, з аспектом.

Порада After Returning

Порада "після повернення (after returning)" виконується, коли виконання методу, що збігся, завершується в нормальному порядку. Він оголошується всередині <aop:aspect> так само, як і попередня порада. У цьому прикладі показано, як її оголосити:


<aop:aspect id="afterReturningExample" ref="aBean">
    <aop:after-returning
        pointcut-ref="dataAccessOperation"
        method="doAccessCheck"/>
    ...
</aop:aspect>

Як і в стилі @AspectJ, можна отримати значення, що повертається в тілі поради. Для цього використовуй атрибут returning, щоб вказати ім'я параметра, якому має бути передано значення, що повертається, як показано в наступному прикладі:


<aop:aspect id="afterReturningExample" ref="aBean">
    <aop:after-returning
        pointcut-ref="dataAccessOperation"
        returning="retVal"
        method="doAccessCheck"/>
    ...
 </aop:aspect>

Метод doAccessCheck повинен оголосити параметр з ім'ям retVal. Тип цього параметра обмежує порівняльний пошук так само, як описано для @AfterReturning. Наприклад, не можна оголосити сигнатуру методу так:

Java
public void doAccessCheck(Object retVal) {...
Kotlin
fun doAccessCheck(retVal: Any) {...

Порада After Throwing

Рада "після генерації виключення (after throwing)" виконується, коли виконання відповідного методу завершується генерацією виключення. Він оголошується всередині <aop:aspect> за допомогою елемента after-throwing, як показано в наступному прикладі:


<aop:aspect id="afterThrowingExample" ref="aBean">
    <aop:after-throwing
        pointcut-ref="dataAccessOperation"
        method="doRecoveryActions"/>
    ...
</aop:aspect>

Як і в стилі @AspectJ, можна отримати згенерований виняток у тілі поради. Для цього використовуй атрибут throwing для встановлення імені параметра, в який має бути передано виняток, як показано в наступному прикладі:


<aop:aspect id="afterThrowingExample" ref="aBean">
    <aop:after-throwing
        pointcut-ref="dataAccessOperation"
        throwing="dataAccessEx"
        method="doRecoveryActions"/>
    ...
</aop:aspect>

Метод doRecoveryActions повинен оголосити параметр з ім'ям dataAccessEx. Тип цього параметра обмежує порівняльний пошук так само, як описано для @AfterThrowing. Наприклад, сигнатуру методу можна оголосити так:

Java
public void doRecoveryActions(DataAccessException dataAccessEx) {...
Kotlin
fun doRecoveryActions(dataAccessEx: DataAccessException) {...

Порада After (Finally)

Порада "після (остаточно)" виконується незалежно від того, як завершується виконання зіставленого методу. Оголосити його можна за допомогою елемента after, як показано в наведеному нижче прикладі:


<aop:aspect id="afterFinallyExample" ref="aBean">
    <aop:after
        pointcut-ref="dataAccessOperation"
        method="doReleaseLock"/>
    ...
</aop:aspect>

Порада Around

Останній вид порад — це порада "замість" . Порада "замість" замінює виконання зіставленого методу. Вона може виконуватися як до, так і після запуску методу і визначати, коли, як і навіть чи буде метод взагалі запущений. Порада "замість" часто використовується, якщо потрібно розділити стан "перед" і "після" виконання методу потокобезпечним чином — наприклад, як для запуску та зупинки таймера.

Завжди використовуй найменш впливову форму поради, яка відповідає твоїм вимогам.

ЗНаприклад, не використовуй пораду "замість", якщо для твоїх потреб достатньо поради"перед".

Оголосити пораду "замість" можна за допомогою елемента aop:around. Метод поради повинен оголосити Object як тип, що повертається, а перший параметр методу повинен бути типу ProceedingJoinPoint. У тілі методу поради потрібно викликати proceed() для ProceedingJoinPoint, щоб запустити основний метод. Виклик proceed() без аргументів призведе до того, що вихідні аргументи коду, що викликає, будуть передані базовому методу під час його виклику. Для просунутих випадків використання існує перевантажений варіант методу proceed(), який приймає масив аргументів(Object[]). Значення в масиві будуть використані як аргументи базового методу під час його виклику.


<aop:aspect id="aroundExample" ref="aBean">
    <aop:around
        pointcut-ref="businessService"
        method="doBasicProfiling"/>
...
</aop:aspect>

Реалізація ради doBasicProfiling може бути такою ж, як у прикладі для @ AspectJ (за винятком анотації, звичайно), що і показано в наступному прикладі:

Java

public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable {
        // запускаємо секундомір
        Object retVal = pjp.proceed();
        // Зупиняємо секундомір
        return retVal;
}
Kotlin

fun doBasicProfiling(pjp: ProceedingJoinPoint): Any {
    // запускаємо секундомір
    val retVal = pjp.proceed()
    // зупиняємо секундомір
    return pjp.proceed()
}

Параметри порад

Стиль оголошення на основі схем підтримує повністю типізовані поради так само, як це описано для підтримки у випадку з @AspectJ — шляхом зіставлення параметрів зрізу на ім'я з параметрами методу поради. Якщо тобі потрібно явно вказати імена аргументів для методів поради (не використовуючи стратегії виявлення, описані раніше), можна здійснити це за допомогою атрибуту arg-names елемента поради, який обробляється так само, як атрибут argNames в анотації поради. У цьому прикладі показано, як вказати ім'я аргументу в XML:


<aop:before
    pointcut="com.xyz.lib.Pointcuts.anyPublicMethod() and @annotation(auditable)"
    method="audit"
    arg-names="auditable"/>

Атрибут arg-names приймає список імен параметрів, розділених комами.

Наступний, трохи складніший приклад підходу, заснованого на XSD, демонструє деякі поради, що використовуються в поєднанні з низкою сильно типізованих параметрів:

Java

package x.y.service;
public interface PersonService {
    Person getPerson(String personName, int age);
}
public class DefaultPersonService implements PersonService {
    public Person getPerson(String name, int age) {
        return new Person(name, age);
    }
}
Kotlin

package x.y.service
interface PersonService {
    fun getPerson(personName: String, age: Int) : Person
}
class DefaultPersonService : PersonService {
    fun getPerson(name: String, age: Int): Person {
        return Person(name, age)
    }
}

Далі йде аспект. Зверни увагу на те, що метод profile(..) приймає декілька типизованих параметрів, перший з яких є точкою з'єднання, яка використовується для продовження виклику методу. Наявність цього параметра вказує на те, що profile(..) має використовуватися як порада around, що показано в наступному прикладі:

Java

package x.y;
import org.aspectj.lang.ProceedingJoinPoint;
import org.springframework.util.StopWatch;
public class SimpleProfiler {
    public Object profile(ProceedingJoinPoint call, String name, int age) throws Throwable {
        StopWatch clock = new StopWatch("Profiling for '" + name + "' and '" + age + "'");
        try {
            clock.start(call.toShortString());
            return call.proceed();
        } finally {
            clock.stop();
            System.out.println(clock.prettyPrint());
        }
    }
}
Kotlin

import org.aspectj.lang.ProceedingJoinPoint
import org.springframework.util.StopWatch
class SimpleProfiler {
    fun profile(call: ProceedingJoinPoint, name: String, age: Int): Any {
        val clock = StopWatch("Profiling for '$name' and '$age'")
        try {
            clock.start(call.toShortString())
            return call.proceed()
        } finally {
            clock.stop()
            println(clock.prettyPrint())
        }
    }
}

Нарешті, наступний приклад конфігурації XML впливає на виконання попередньої поради для конкретної точки з'єднання:


<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">
    <!-- this is the object that will be proxied by the Spring AOP framework -->
    <bean id="personService" class="x.y.service.DefaultPersonService"/>
    <!-- this is actually the advice itself -->
    <bean id="profiler" class="x.y.SimpleProfiler"/>
    <aop:config>
        <aop:aspect ref="profiler">
        <aop:pointcut id="theExecutionOfSomePersonServiceMethod"
            expression="execution(* x.y.service.PersonService.getPerson(String,int))
            and args(name, age)"/>
        <aop:around pointcut-ref="theExecutionOfSomePersonServiceMethod"
            method="profile"/>
        </aop:aspect>
    </aop:config>
</beans>

Розглянемо наступний скрипт драйвера:

Java

import org.springframework.beans.factory.BeanFactory;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import x.y.service.PersonService;
public final class Boot {
    public static void main(final String[] args) throws Exception {
        BeanFactory ctx = new ClassPathXmlApplicationContext("x/y/plain.xml");
        PersonService person = (PersonService) ctx.getBean("personService");
        person.getPerson("Pengo", 12);
    }
}
Kotlin

fun main() {
    val ctx = ClassPathXmlApplicationContext("x/y/plain.xml")
    val person = ctx.getBean("personService") as PersonService
    person.getPerson("Pengo", 12)
}

З таким класом Boot ми отримали б результат виконання, аналогічний до стандартних вихідних даних:

StopWatch 'Profiling for 'Pengo' and '12': running time (millis) = 0
-----------------------------------------
ms     %     Task name
-----------------------------------------
00000  ?  execution(getFoo)

Упорядкування порад

Якщо кілька порад потрібно виконувати в одній точці з'єднання (виконуючий метод), правила упорядкування описані в розділі "Упорядкування порад". Старшинство між аспектами визначається через атрибут order в елементі <aop:aspect> або шляхом додавання анотації @Order до біна, що підтримує аспект, або ж шляхом реалізації біном інтерфейсу Ordered.

На відміну від правил старшинства для методів поради, визначених в одному класі, анотованому @Aspect, якщо дві поради, визначені в одному елементі <aop:aspect>, потрібно виконувати в одній і тій же точці з'єднання, старшинство визначається порядком оголошення елементів ради в об'ємному елементі <aop:aspect> від найвищого до нижчого рівня старшинства.

Наприклад, за наявності поради around та поради before, визначених в тому самому елементі <aop:aspect>, які застосовуються до однієї і тієї ж точки з'єднання, якщо потрібно, щоб порада around гарантовано мала більше високий рівень старшинства, ніж рада before, елемент <aop:around> має бути оголошений перед елементом <aop:before>.

Слідуючи практичному правилу, якщо виявлено, що існує кілька порад, визначених в одному елементі <aop:aspect>, які застосовуються до однієї і тієї ж точки з'єднання, подумай про те, щоб об'єднати такі методи поради в один метод поради для кожної точки з'єднання в кожному елементі <aop:aspect> або здійснити рефакторинг порад в окремі елементи <aop:aspect>, які можна буде впорядкувати на рівні аспекту.

Впровадження

Впровадження (відомі в AspectJ як міжтипові оголошення) дозволяють аспекту оголосити, що об'єкти, забезпечені порадою, реалізують зазначений інтерфейс, і надати реалізацію цього інтерфейсу від імені цих об'єктів.

Ти можеш здійснювати впровадження за допомогою елемента aop:declare-parents всередині aop:aspect. Ти можеш використовувати елемент aop:declare-parents, щоб оголосити, що типи мають новий батьківський тип (звідси й назва). Наприклад, якщо взяти інтерфейс UsageTracked та реалізацію цього інтерфейсу DefaultUsageTracked, наступний аспект оголошує, що всі реалізатори службових інтерфейсів також реалізують інтерфейс UsageTracked. (Наприклад, для відкриття статистики через JMX).


<aop:aspect id="usageTrackerAspect" ref="usageTracking">
    <aop:declare-parents
            types-matching="com.xzy.myapp.service.*+"
            implement-interface="com.xyz.myapp.service.tracking.UsageTracked"
            default-impl="com.xyz.myapp.service.tracking.DefaultUsageTracked"/>
    <aop:before
            pointcut="com.xyz.myapp.CommonPointcuts.businessService()
            and this(usageTracked)"
            method="recordUsage"/>
</aop:aspect>

Клас, який є основою для біна UseTracking, буде містити наступний метод:

Java

public void recordUsage(UsageTracked usageTracked) {
    usageTracked.incrementUseCount();
}
Kotlin

fun recordUsage(usageTracked: UsageTracked) {
    usageTracked.incrementUseCount()
} 

Інтерфейс, який має бути реалізований, визначається атрибутом implement-interface. Значенням атрибуту types-matching є шаблон типу AspectJ. Будь-який бін відповідного типу реалізує інтерфейс UsageTracked. Зверни увагу, що в пораді "перед" у попередньому приклад службові біни можна безпосередньо використати як реалізацію інтерфейсу UsageTracked. Щоб отримати доступ до біна програмно, можна написати наступне:

Java
UsageTracked usageTracked = (UsageTracked) context.getBean("myService");
Kotlin
val usageTracked = context.getBean("myService") as UsageTracked

Моделі створення екземпляра аспекту

Єдиною підтримуваною моделлю створення екземплярів для аспектів, що визначаються схемою, є модель створення одиночних екземплярів. Підтримка інших моделей створення екземплярів може бути додана в майбутніх випусках. AspectJ.

Радники

Поняття "радників (advisors)" походить із засобів підтримки АОП, визначених у Spring, і не має прямого еквівалента в AspectJ. Радник — це як невеликий самодостатній аспект, який має єдину пораду. Сама порада представлена біном і має реалізовувати один з інтерфейсів ради. Радники можуть використовувати переваги виразів зрізу з AspectJ.

Spring підтримує поняття радника за допомогою елемента <aop:advisor>. Найчастіше його використовують у поєднанні з транзакційними порадами, які також мають власний простір імен у Spring. У цьому прикладі показано радника:


<aop:config>
    <aop:pointcut id="businessService"
                  expression="execution(* com.xyz.myapp.service.*.*(..))"/>
    <aop:advisor
            pointcut-ref="businessService"
            advice-ref="tx-advice"/>
</aop:config>
<tx:advice id="tx-advice">
    <tx:attributes>
        <tx:method name="*" propagation="REQUIRED"/>
    </tx:attributes>
</tx:advice>

Окрім атрибуту pointcut-ref, використаного в попередньому прикладі, також можна використовувати атрибут pointcut для визначення вбудовування виразу зрізу.

Для визначення старшинства радника, щоб порада могла брати участь у впорядкуванні, використовуй атрибут order для визначення значення Ordered радника.

Приклад схеми АОП

У цьому розділі показано, як виглядає приклад повторної спроби відмови одночасного блокування з An AOP Example, якщо переписати його за допомогою підтримки схем.

Іноді виконання бізнес-служб може завершитися помилкою через проблеми з паралелізмом (наприклад, через взаємоблокування). Якщо операцію повторити, швидше за все, вона буде успішною за наступної спроби. У випадку з бізнес-службами, з якими доречно повторювати операцію за таких умов (ідемпотентні операції, за яких не потрібно повертатися до користувача для вирішення конфлікту), ми хочемо доступним способом повторити таку операцію, щоб клієнтська частина не побачила PessimisticLockingFailureException. Ця вимога, яка очевидно охоплює кілька служб на рівні служб і, отже, ідеально підходить для реалізації через аспект.

Оскільки нам потрібно повторити операцію, необхідно використовувати пораду "замість", щоб можна було викликати метод proceed кілька разів. У наступному лістингу показана базова реалізація аспекту (яка є звичайним класом Java, що використовує засоби підтримки схем):

Java

public class ConcurrentOperationExecutor implements Ordered {
    private static final int DEFAULT_MAX_RETRIES = 2;
    private int maxRetries = DEFAULT_MAX_RETRIES;
    private int order = 1;
    public void setMaxRetries(int maxRetries) {
        this.maxRetries = maxRetries;
    }
    public int getOrder() {
        return this.order;
    }
    public void setOrder(int order) {
        this.order = order;
    }
    public Object doConcurrentOperation(ProceedingJoinPoint pjp) throws Throwable {
        int numAttempts = 0;
        PessimisticLockingFailureException lockFailureException;
        do {
            numAttempts++;
            try {
                return pjp.proceed();
            }
            catch(PessimisticLockingFailureException ex) {
                lockFailureException = ex;
            }
        } while(numAttempts <= this.maxRetries);
        throw lockFailureException;
    }
}
Kotlin

class ConcurrentOperationExecutor : Ordered {
    private val DEFAULT_MAX_RETRIES = 2
    private var maxRetries = DEFAULT_MAX_RETRIES
    private var order = 1
    fun setMaxRetries(maxRetries: Int) {
        this.maxRetries = maxRetries
    }
    override fun getOrder(): Int {
        return this.order
    }
    fun setOrder(order: Int) {
        this.order = order
    }
    fun doConcurrentOperation(pjp: ProceedingJoinPoint): Any {
        var numAttempts = 0
        var lockFailureException: PessimisticLockingFailureException
        do {
            numAttempts++
            try {
                return pjp.proceed()
            } catch (ex: PessimisticLockingFailureException) {
                lockFailureException = ex
            }
        } while (numAttempts <= this.maxRetries)
        throw lockFailureException
    }
}

Зверни увагу, що аспект реалізує інтерфейс Ordered, щоб можна було зазначити рівень старшинства аспекту вище, ніж поради транзакції (нам потрібно щоб при кожній повторній спробі транзакція була новою). Властивості maxRetries та order налаштовуються Spring. Основна дія відбувається в методі doConcurrentOperation поради "замість". Спробуємо продовжити виконання. У разі помилки з генерацією винятку PessimisticLockingFailureException, повторюємо спробу, якщо не були вичерпані всі спроби повторення.

Цей клас ідентичний тому, який використовувався у прикладі для @AspectJ, але з видаленими анотаціями.

Відповідна конфігурація Spring виглядає таким чином:


<aop:config>
    <aop:aspect id="concurrentOperationRetry" ref="concurrentOperationExecutor">
        <aop:pointcut id="idempotentOperation"
                      expression="execution(* com.xyz.myapp.service.*.*(..))"/>
        <aop:around
                pointcut-ref="idempotentOperation"
                method="doConcurrentOperation"/>
    </aop:aspect>
</aop:config>
<bean id="concurrentOperationExecutor"
      class="com.xyz.myapp.service.impl.ConcurrentOperationExecutor">
        <property name="maxRetries" value="3"/>
        <property name="order" value="100"/>
</bean>

Зверни увагу, що в цьому моменті ми припускаємо, що всі бізнес-служби є ідемпотентними. Якщо це не так, то можна доопрацювати аспект таким чином, щоб він повторював лише істинно ідемпотентні операції, ввівши анотацію Idempotent та використовуючи її для анотування реалізації службових операцій, як показано в наступному прикладі:

Java

@Retention(RetentionPolicy.RUNTIME)
public @interface Idempotent {
    // анотація маркера
}
Kotlin

@Retention(AnnotationRetention.RUNTIME)
annotation class Idempotent {
    // анотація маркера
}

Зміна аспекту для повторного виконання тільки ідемпотентних операцій включає в себе уточнення виразу зрізу таким чином, щоб збігалися тільки операції @Idempotent, як показано нижче:


<aop:pointcut id="idempotentOperation"
        expression="execution(* com.xyz.myapp.service." *.*(..)) and
        @annotation(com.xyz.myapp.service.Idempotent)"/>