Что такое аспект, 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. Что такое прокси в Spring]]
- [[17. Что делает аннотация @Transactional]]
- [[18. Почему @Transactional не работает при self-invocation]]