Spring FrameworkMiddleTechnical
В чём разница между @Before, @After, @Around, @AfterReturning и @AfterThrowing advice?
@Before — до вызова; @After — после (всегда); @AfterReturning — при успешном возврате; @AfterThrowing — при исключении; @Around — полный контроль: вызывает proceed(), может менять аргументы и результат.
Типы advice и их контракт
Spring AOP реализует пять типов advice, каждый из которых привязан к конкретной точке жизненного цикла вызова метода.
- @Before — выполняется до вызова целевого метода. Не может предотвратить вызов (кроме выброса исключения). Подходит для проверки прав, логирования входящих параметров.
- @After (finally-advice) — выполняется после метода независимо от исхода (успех или исключение). Аналог блока
finally. - @AfterReturning — выполняется только при нормальном завершении. Через атрибут
returningполучает возвращаемое значение, но не может его изменить. - @AfterThrowing — выполняется только если метод выбросил исключение. Через атрибут
throwingполучает объект исключения. Не подавляет его. - @Around — самый мощный: оборачивает вызов целиком. Обязан вызвать
ProceedingJoinPoint.proceed(), иначе целевой метод не выполнится. Может изменить аргументы, перехватить возвращаемое значение, подавить исключение или вернуть другой результат.
Рабочий пример
@Aspect
@Component
public class AuditAspect {
// 1. @Before — логируем входящий аргумент
@Before("execution(* com.example.OrderService.place(..))")
public void logBefore(JoinPoint jp) {
System.out.println("[BEFORE] args: " + Arrays.toString(jp.getArgs()));
}
// 2. @AfterReturning — читаем результат без изменения
@AfterReturning(
pointcut = "execution(* com.example.OrderService.place(..))",
returning = "result"
)
public void logResult(Object result) {
System.out.println("[AFTER_RETURNING] orderId=" + result);
}
// 3. @AfterThrowing — фиксируем ошибку в мониторинге
@AfterThrowing(
pointcut = "execution(* com.example.OrderService.*(..))",
throwing = "ex"
)
public void logError(Exception ex) {
Metrics.counter("order.error", "type", ex.getClass().getSimpleName()).increment();
}
// 4. @After (finally) — всегда освобождаем ресурс
@After("execution(* com.example.OrderService.place(..))")
public void releaseContext() {
RequestContext.clear();
}
// 5. @Around — замер времени + возможность изменить результат
@Around("@annotation(com.example.Timed)")
public Object measureTime(ProceedingJoinPoint pjp) throws Throwable {
long start = System.currentTimeMillis();
try {
return pjp.proceed(); // обязателен!
} finally {
long elapsed = System.currentTimeMillis() - start;
System.out.println("[AROUND] " + pjp.getSignature() + " took " + elapsed + "ms");
}
}
}
Порядок выполнения при нормальном вызове: @Around(before proceed) → @Before → метод → @Around(after proceed) → @AfterReturning → @After. При исключении: @Around(before proceed) → @Before → метод (exception) → @AfterThrowing → @After.
Подводные камни
- Забытый
proceed()в @Around — целевой метод молча не вызывается; баг почти невозможно обнаружить по логам. - Self-invocation обходит прокси — если метод класса вызывает другой метод того же класса напрямую (
this.foo()), advice не сработает, потому что вызов идёт мимо Spring-прокси. - @AfterReturning не может изменить возвращаемое значение — для этого нужен @Around; попытка присвоить переменной
returningновый объект ни на что не влияет. - @AfterThrowing не подавляет исключение — если нужно проглотить ошибку и вернуть fallback, используйте @Around с try/catch вокруг
proceed(). - Порядок нескольких аспектов — без явного
@Orderпорядок advice от разных аспектов на одном методе не детерминирован; для транзакций и безопасности порядок критичен. - @After vs @AfterReturning при исключении — @After выполняется всегда (как finally), @AfterReturning — только при успехе; путаница приводит к дублированию кода очистки.
- Proxy-based ограничение — по умолчанию Spring AOP работает через JDK dynamic proxy (для интерфейсов) или CGLIB (для классов); аспекты не перехватывают
privateиfinalметоды. - Checked exceptions в @Around — сигнатура
proceed()бросаетThrowable; нельзя просто пойматьException, нужно либо пробрасыватьThrowable, либо явно обрабатывать оба типа.
Common mistakes
- Путать термин «aop advice types» с соседним механизмом Spring Framework.
- Не называть границу lifecycle, transaction, thread или request для «aop advice types».
- Игнорировать production-эффекты «aop advice types»: latency, SQL shape, memory, security или observability.
What the interviewer is testing
- Попросить объяснить механизм «aop advice types» на минимальном примере.
- Проверить, видит ли кандидат failure mode и диагностику для «aop advice types».
- Уточнить, какие настройки или API меняют «aop advice types» в реальном сервисе.