Що таке аспект, advice, pointcut, join point?
4. @Order → порядок виконання 5. getArgs() → копія масиву, обережно 6. @Around → не забудьте proceed()
🟢 Junior Level
Терміни AOP:
| Термін | Що означає | Приклад |
|---|---|---|
| Join Point | Конкретний метод, куди можна вклинитися | UserService.save() |
| Pointcut | Правило, ДЕ застосовувати | “Усі методи з @Transactional” |
| Advice | ЩО робити (логіка) | “Записати час виконання” |
| Aspect | Модуль, що об’єднує Pointcut + Advice | Клас з логуванням |
@Aspect
@Component
public class LoggingAspect { // ← Aspect
@Around("@annotation(LogTime)") // ← Pointcut
public Object logTime(ProceedingJoinPoint pjp) { // ← Advice
long start = System.currentTimeMillis();
try {
return pjp.proceed(); // Join Point виклик
} finally {
log.info("Виконано за " + (System.currentTimeMillis() - start) + "ms");
}
}
}
🟡 Middle Level
Types of Advice
@Before("pointcut")
public void before() { } // До методу
@After("pointcut")
public void after() { } // Після методу (завжди)
// @After = як finally, виконується ЗАВЖДИ (і при успіху, і при виключенні)
// @AfterReturning = тільки якщо метод завершився УСПІШНО (без виключень)
@AfterReturning(pointcut = "pc", returning = "result")
public void afterReturn(Object result) { } // Після успішного
@AfterThrowing(pointcut = "pc", throwing = "ex")
public void afterThrow(Exception ex) { } // При виключенні
@Around("pointcut")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
// До
Object result = pjp.proceed(); // Виклик методу
// Після
return result;
}
Static vs Dynamic Pointcuts
Static (швидкий):
→ Перевіряється один раз при створенні біна
→ За анотаціями, сигнатурою, ім'ям методу
Dynamic (повільний):
→ Перевіряється при КОЖНОМУ виклику
→ За значеннями аргументів
→ Оверхед на кожну перевірку
🔴 Senior Level
JoinPoint інформація
@Around("pointcut")
public Object log(ProceedingJoinPoint pjp) {
pjp.getSignature(); // Сигнатура методу
pjp.getArgs(); // Аргументи (копія масиву!)
pjp.getTarget(); // Оригінальний об'єкт
pjp.getThis(); // Проксі
// getArgs() створює копію масиву → GC pressure (помітно тільки при дуже частих викликах аспекту — сотні тисяч на секунду — або при великих масивах аргументів).
}
Aspect = Singleton
// Аспект — Singleton за замовчуванням!
// → НЕ зберігайте стан у полях аспекту!
@Aspect
public class MetricsAspect {
// ❌ Не потокобезпечно!
private int callCount = 0;
// ✅ ThreadLocal
private ThreadLocal<Long> startTime = new ThreadLocal<>();
}
Pipeline
ReflectiveMethodInvocation:
1. Знаходить усі Advisors
2. Фільтрує за Pointcut
3. Сортує за @Order
4. Створює List<MethodInterceptor>
5. Рекурсивно проходить по всіх
6. Викликає target.invoke()
Production Experience
Реальний сценарій: Around без proceed
@Around("@annotation(Cacheable)")
public Object cache(ProceedingJoinPoint pjp) {
if (cache.has(key)) return cache.get(key);
// Забули pjp.proceed()!
// → Метод НІКОЛИ не викликається!
}
Best Practices
- Статичні Pointcut → швидше
- Вузькі Pointcut → менше оверхед. Чим точніший pointcut, менше бінів потрапить під проксіювання і менше перевірок при кожному виклику методу.
- Без стану в аспектах (Singleton!)
- @Order → порядок виконання
- getArgs() → копія масиву, обережно
- @Around → не забудьте proceed()
Резюме для Senior
- Join Point = контекст виклику
- Pointcut = фільтр методів
- Advice = тип впровадження
- Aspect = логічний модуль
- Pipeline = ланцюжок інтерцепторів
- Aspect = Singleton → без стану
- proceed() → обов’язково в @Around
🎯 Шпаргалка для інтерв’ю
Обов’язково знати:
- Join Point = конкретний метод, Pointcut = фільтр (де), Advice = логіка (що), Aspect = модуль
- 5 типів Advice: @Before, @After, @AfterReturning, @AfterThrowing, @Around
- @After виконується ЗАВЖДИ (як finally), @AfterReturning — тільки при успіху
- Статичні Pointcut перевіряються один раз при створенні біна, динамічні — при кожному виклику
- Аспект = Singleton → НЕ зберігайте стан у полях, використовуйте ThreadLocal
- pjp.proceed() обов’язковий в @Around — інакше метод не викличеться
- Pipeline: ReflectiveMethodInvocation фільтрує Advisors за Pointcut, сортує за @Order, проходить рекурсивно
- getArgs() створює копію масиву → GC pressure при частих викликах
Часті уточнюючі запитання:
- @After vs @AfterThrowing — коли що використовувати? → @After = cleanup ресурсів завжди, @AfterThrowing = обробка помилок
- Чому аспект = Singleton? → Spring реєструє аспекти як звичайні біни (за замовчуванням Singleton)
- Статичний vs динамічний Pointcut — який обрати? → Статичний швидший, динамічний гнучкіший; віддавайте перевагу статичному
- Що буде, якщо забути pjp.proceed()? → Оригінальний метод не виконається взагалі
Червоні прапорці (НЕ говорити):
- «@After тільки при успіху» — це @AfterReturning
- «Аспект зберігає стан — нормально» — Singleton, не потокобезпечно
- «Динамічний Pointcut швидший» — навпаки, перевіряється щоразу
- «getArgs() повертає оригінальний масив» — створює копію
Пов’язані теми:
- [[15. Що таке AOP (Aspect-Oriented Programming)]]
- [[13. Що таке proxy в Spring]]
- [[17. Що робить анотація @Transactional]]
- [[18. Чому @Transactional не працює при self-invocation]]