Питання 16 · Розділ 5

Що таке аспект, advice, pointcut, join point?

4. @Order → порядок виконання 5. getArgs() → копія масиву, обережно 6. @Around → не забудьте proceed()

Мовні версії: English Russian Ukrainian

🟢 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

  1. Статичні Pointcut → швидше
  2. Вузькі Pointcut → менше оверхед. Чим точніший pointcut, менше бінів потрапить під проксіювання і менше перевірок при кожному виклику методу.
  3. Без стану в аспектах (Singleton!)
  4. @Order → порядок виконання
  5. getArgs() → копія масиву, обережно
  6. @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]]