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» в реальном сервисе.

Sources

Related topics