Если вы предпочитаете формат на основе 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), конечно, можно сконфигурировать и внедрить c зависимостями, как и любой другой бин 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.*.*(..)) &amp;&amp; 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[]). Значения в массиве будут использованы в качестве аргументов базового метода при его вызове.

В следующем примере показано, как объявить совет "вместо" в XML:

<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">
    <!-- это объект, который будет проксироваться инфраструктурой Spring AOP -->
    <bean id="personService" class="x.y.service.DefaultPersonService"/>
    <!-- это собственно сам совет -->
    <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

Модели создания экземпляра аспекта

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

Советники

Понятие "советников (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)"/>