Якщо ви надаєте перевагу формату на основі 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>
Порада має бути оголошена для отримання зібраного контексту точки з'єднання шляхом включення параметрів із відповідними іменами, як показано нижче:
public void monitor(Object service) {
// ...
}
fun monitor(service: Any) {
// ...
}
При об'єднанні виразів зрізів використовувати &amp;
у XML-документі незручно, тому
замість &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
. Наприклад, не можна оголосити
сигнатуру методу так:
public void doAccessCheck(Object retVal) {...
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
. Наприклад, сигнатуру
методу можна оголосити так:
public void doRecoveryActions(DataAccessException dataAccessEx) {...
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 (за
винятком анотації, звичайно), що і показано в наступному прикладі:
public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable {
// запускаємо секундомір
Object retVal = pjp.proceed();
// Зупиняємо секундомір
return retVal;
}
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, демонструє деякі поради, що використовуються в поєднанні з низкою сильно типізованих параметрів:
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);
}
}
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
, що показано
в наступному прикладі:
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());
}
}
}
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>
Розглянемо наступний скрипт драйвера:
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);
}
}
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
, буде містити наступний метод:
public void recordUsage(UsageTracked usageTracked) {
usageTracked.incrementUseCount();
}
fun recordUsage(usageTracked: UsageTracked) {
usageTracked.incrementUseCount()
}
Інтерфейс, який має бути реалізований, визначається атрибутом implement-interface
. Значенням
атрибуту types-matching
є шаблон типу AspectJ. Будь-який бін відповідного типу реалізує інтерфейс
UsageTracked
. Зверни увагу, що в пораді "перед" у попередньому приклад службові біни можна
безпосередньо використати як реалізацію інтерфейсу UsageTracked
. Щоб отримати доступ до біна програмно,
можна написати наступне:
UsageTracked usageTracked = (UsageTracked) context.getBean("myService");
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, що використовує засоби
підтримки схем):
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;
}
}
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
, повторюємо спробу, якщо не були вичерпані всі спроби
повторення.
Відповідна конфігурація 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
та використовуючи її для анотування реалізації службових операцій, як показано в наступному
прикладі:
@Retention(RetentionPolicy.RUNTIME)
public @interface Idempotent {
// анотація маркера
}
@Retention(AnnotationRetention.RUNTIME)
annotation class Idempotent {
// анотація маркера
}
Зміна аспекту для повторного виконання тільки ідемпотентних операцій включає в себе уточнення виразу зрізу
таким чином, щоб збігалися тільки операції @Idempotent
, як показано нижче:
<aop:pointcut id="idempotentOperation"
expression="execution(* com.xyz.myapp.service." *.*(..)) and
@annotation(com.xyz.myapp.service.Idempotent)"/>
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ