Сквозная функциональность
Логирование, транзакции, безопасность, кэширование, метрики пронизывают множество классов и не относятся к их бизнес-логике. AOP (аспектно-ориентированное программирование) позволяет вынести такую сквозную (cross-cutting) логику в отдельные модули — аспекты — и применять декларативно, не дублируя в каждом методе.
Ключевые понятия
Aspect — модуль сквозной логики. Advice — что и когда выполнить: before, afterReturning, afterThrowing, after (finally) и around (оборачивает вызов, может изменить аргументы/результат и решить, вызывать ли целевой метод). Pointcut — выражение-выборка «где применять» (например, execution(...)). Join point — конкретная точка выполнения; в Spring AOP это всегда ВЫЗОВ метода (не доступ к полю).
around — самый мощный advice: он получает ProceedingJoinPoint и сам решает, вызвать ли pjp.proceed().
@Aspect @Component
class TimingAspect {
@Around("execution(* com.app.service..*(..))")
Object time(ProceedingJoinPoint pjp) throws Throwable {
long t = System.nanoTime();
try { return pjp.proceed(); }
finally { metrics.record(pjp.getSignature(), System.nanoTime() - t); }
}
}

JDK proxy против CGLIB
Spring AOP — proxy-based: бин оборачивается в прокси-объект. В «чистом» Spring логика такая: если у бина есть интерфейс, по умолчанию создаётся JDK dynamic proxy (реализует интерфейс); если интерфейса нет — CGLIB-прокси (наследует класс подклассом).
Важная деталь для собеседования: Spring Boot c 2.0 переопределяет этот дефолт — он включает spring.aop.proxy-target-class=true, поэтому на практике в Boot-приложении почти всегда CGLIB, даже когда у бина есть интерфейс. Так аспект не «теряется», если внедрять бин по классу, а не по интерфейсу.
Отсюда ограничения. Приватные методы прокси не перехватывает никогда. JDK-прокси видит только методы интерфейса; CGLIB-прокси с Spring 6 перехватывает public, protected и package-private методы, но не может проксировать final-классы и final-методы — их некому переопределить. Понимание «прокси = обёртка» объясняет всю «магию» декларативных аннотаций.
Self-invocation
Раз advice живёт в прокси, он срабатывает только на вызовах ЧЕРЕЗ прокси. Вызов метода изнутри того же объекта через this идёт мимо прокси, и аспект не применяется — ровно та же ловушка, что у @Transactional.
Обходы: вынести метод в отдельный бин, внедрить self-прокси или перейти на AspectJ weaving (он вплетает аспекты в байткод и не зависит от прокси, перехватывая в том числе внутренние вызовы).
Что построено на AOP
AOP — не экзотика, а фундамент Spring: @Transactional, @Async, @Cacheable, @PreAuthorize/@Secured, @Retryable, @Timed — всё это аспекты, навешиваемые BeanPostProcessor'ом при создании бина (см. урок про бины).
Поэтому, столкнувшись с «почему аннотация не сработала», первым делом проверяют: вызов идёт через прокси (не self-invocation), метод public, бин действительно управляется Spring.
Как отвечать на собеседовании
Определите cross-cutting concerns и термины (aspect/advice с видами/pointcut/join point=вызов метода; around с proceed). Объясните proxy-based модель (JDK по интерфейсу vs CGLIB подклассом; только public, не final) и self-invocation. Подчеркните, что @Transactional/@Async/@Cacheable/@PreAuthorize — это AOP через BeanPostProcessor, отсюда и их общие ограничения.