Pointcut — це вираз, який відповідає на питання "де саме в коді має спрацювати аспект?". По суті, це фільтр, який вибирає потрібні методи з усього додатку. Наприклад, "усі методи в сервісному шарі" або "методи з певною анотацією".
В Spring AOP для опису pointcut-ів використовується синтаксис AspectJ. Так, спочатку він може виглядати як код з RSA-шифруванням, але насправді все досить логічно. Давайте розберемо.
Вбудовані вирази Pointcut
Ось найпоширеніші вирази, які вам знадобляться:
| Вираз | Опис |
|---|---|
execution |
Підходить для методів. Використовується в 99% випадків |
within |
Вказує область (наприклад, конкретний клас або пакет) |
args |
Підходить для методів з певними аргументами |
this і target |
Вказує на об'єкт проксі (this) або реальний цільовий об'єкт (target) |
bean |
Підходить для бінів з певною назвою |
Приклад простого Pointcut-виразу:
@Pointcut("execution(* com.example.service.*.*(..))")
public void serviceLayerMethods() {
// Цей метод просто маркер для Pointcut
}
Давайте розберемо це "заклинання":
execution— ми націлюємося на методи.*— будь-який тип повернення (void,String,int, etc).com.example.service.*.*— будь-який клас у пакетіcom.example.serviceта будь-який метод.(..)— будь-який набір параметрів.
Трохи про Wildcards
Wildcards (підстановчі символи) у виразах Pointcut дозволяють нам бути гнучкими. Ось коротке керівництво:
*— означає "будь-який". Приклад:execution(* com.example..*Controller.*(..))— будь-який метод в будь-якому класі, що міститьControllerв імені...— означає "будь-який рівень вкладеності". Наприклад,com.example..serviceвключає всі підпакети відcom.example.
Приклад: заглиблюємося в Pointcut
Давайте спробуємо щось трохи складніше. Уявімо, що ми хочемо додати логування для всіх методів класу, які починаються з save.
@Aspect
@Component
public class LoggingAspect {
@Pointcut("execution(* com.example..*Service.save*(..))")
public void saveMethodsPointcut() {
// Pointcut для методів, які починаються з save
}
@Before("saveMethodsPointcut()")
public void beforeSaveAdvice(JoinPoint joinPoint) {
System.out.println("Виклик методу: " + joinPoint.getSignature().getName());
}
}
Тут ми:
- визначаємо точку з'єднання для всіх методів, що починаються з
saveв будь-якому класі з підпакетівcom.example, назви яких закінчуються наService. - Перед викликом таких методів ми логруємо ім'я методу.
Композиція Pointcut-виразів
Ми вже згадували, що іноді потрібно об'єднати кілька Pointcut-ів. Наприклад, ми хочемо впливати тільки на методи з певного пакету, і щоб вони ще й починалися з певного слова.
В AspectJ є логічні оператори для композицій:
&&— логічне "І".||— логічне "АБО".!— логічне "НЕ".
Ось приклад:
@Pointcut("within(com.example..*)")
public void withinExamplePackage() {}
@Pointcut("execution(* save*(..))")
public void saveMethods() {}
@Pointcut("withinExamplePackage() && saveMethods()")
public void saveMethodsInExamplePackage() {}
Таким чином, saveMethodsInExamplePackage() спрацює тільки на методах, які починаються з save, і лежать у пакетах com.example.
Створення кастомного аспекту
Тепер застосуємо отримані знання на практиці. Припустимо, у нашому додатку є сервіс, який відповідає за управління користувачами (UserService). Ми хочемо:
- Логувати виклики методів, що починаються з
find. - Викликати додаткову логіку лише тоді, коли метод повертає
String.
Реалізація
@Aspect
@Component
public class CustomAspect {
// Логування всіх методів, які починаються з find
@Pointcut("execution(* com.example.service.UserService.find*(..))")
public void findMethods() {}
// Виконання дії перед викликом методу
@Before("findMethods()")
public void logBeforeFind(JoinPoint joinPoint) {
System.out.println("Виклик методу: " + joinPoint.getSignature().getName());
}
// Реакція на успішне виконання методу, який повертає String
@AfterReturning(
pointcut = "findMethods()",
returning = "result"
)
public void afterReturningFind(JoinPoint joinPoint, Object result) {
if (result instanceof String) {
System.out.println("Метод " + joinPoint.getSignature().getName() +
" успішно повернув значення: " + result);
}
}
}
Що відбувається:
- За допомогою
@Pointcutми вибираємо тільки методи в класіUserService, імена яких починаються зfind. - Використовуємо
@Beforeдля логування виклику. - Використовуємо
@AfterReturning, щоб додатково обробити повернуте значення, якщо воно є рядком.
Практика: створення складного кастомного аспекту
Спробуємо ускладнити завдання. Припустимо, нам потрібно логувати всі методи контролерів, які анотовані @GetMapping.
@Aspect
@Component
public class ControllerLoggingAspect {
// Pointcut для методів з анотацією @GetMapping
@Pointcut("@annotation(org.springframework.web.bind.annotation.GetMapping)")
public void getMappingMethods() {}
// Логування до виконання методу
@Before("getMappingMethods()")
public void logBeforeGetMapping(JoinPoint joinPoint) {
System.out.println("Виклик методу контролера: " + joinPoint.getSignature().getName());
}
}
Ми використовуємо анотацію @annotation, щоб чітко визначити методи, на які націлюємося (у даному випадку методи з @GetMapping). Бачиш, наскільки потужним стає твій інструмент?
Налагодження й типові помилки
AOP — не позбавлений своїх загадок. Ось кілька речей, про які варто пам'ятати:
- Якщо твій аспект не працює, переконайся, що клас аспекту позначений анотацією
@Componentі зареєстрований у контексті Spring. - Перевір, що ти правильно вказуєш шлях пакетів у
execution. Пропущений рівень вкладеності або опечатка можуть звести всі зусилля нанівець. - Зверни увагу на проксі: якщо ти викликаєш метод класу всередині того ж самого класу, аспект може не спрацювати.
Ручна перевірка Pointcut-виразів
Якщо цікаво, які саме методи потрапляють під твій Pointcut-вираз, ти завжди можеш це перевірити вручну:
@Before("execution(* com.example.service.*.*(..))")
public void debugPointcut(JoinPoint joinPoint) {
System.out.println("Потрапив у Pointcut: " + joinPoint.getSignature().getName());
}
Ти зможеш побачити в логах список усіх методів, які відповідають виразу.
Ти готовий створювати аспекти, які зможуть керувати бізнес-логікою твого додатка! На наступному етапі ми заглибимось ще більше в проксі-об'єкти і подивимося на їх приховані секрети.
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ