← Назад к вопросам

К чему приводит аспект, который запрашивает Pointcut на *.*

3.0 Senior🔥 131 комментариев
#Spring Framework

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Аспекты с Pointcut на . — опасность и последствия

Это серьёзная проблема в AOP (Aspect-Oriented Programming). Pointcut *.* перехватывает ВСЕ методы ВСЕХ классов, что приводит к критическим последствиям.

1. Перехват всех методов

Пoincut *.* означает "все методы всех классов". Это включает:

  • Методы вашего приложения
  • Методы Spring Framework
  • Методы библиотек
  • Методы JDK
  • Методы подсистем логирования, сериализации и т.д.
@Aspect
public class BadAspect {
    // ХУ ЖЕ ПЛОХО — перехватит ВСЕ методы
    @Around("execution(*.*(*))")
    public Object traceEverything(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("Before: " + joinPoint.getSignature());
        Object result = joinPoint.proceed();
        System.out.println("After: " + joinPoint.getSignature());
        return result;
    }
}

2. Катастрофическое падение производительности

Каждый вызов метода теперь оборачивается в логику аспекта. Это умножает нагрузку:

// Представь, что этот простой метод перехватывается аспектом
private int sum(int a, int b) {
    return a + b; // Миллиарды раз в секунду
}

// Аспект оборачивает каждый вызов:
public Object around(ProceedingJoinPoint jp) throws Throwable {
    long start = System.nanoTime();
    System.out.println("[TRACE] " + jp.getSignature()); // Системный вызов!
    Object result = jp.proceed(); // Вызов оригинального метода
    long duration = System.nanoTime() - start;
    System.out.println("[TRACE] Duration: " + duration); // Ещё системный вызов
    return result;
}

// Результат: приложение замедляется в 100-1000 раз

Без аспекта: 1 миллиард вызовов/сек С аспектом: 1-10 миллионов вызовов/сек (замедление в 100+ раз)

3. Рекурсивные перехваты и бесконечные циклы

Это самое коварное. Аспект может перехватить сам себя:

@Aspect
public class LoggingAspect {
    @Around("execution(*.*(*))")
    public Object log(ProceedingJoinPoint joinPoint) throws Throwable {
        logger.info("Method called: " + joinPoint.getSignature()); // ПЕРЕХВАТЫВАЕТСЯ!
        return joinPoint.proceed();
    }
}

// Что происходит:
// 1. Вызов userService.getUser()
// 2. Аспект перехватывает → вызывает logger.info()
// 3. logger.info() перехватывается аспектом → вызывает logger.info()
// 4. Бесконечная рекурсия → StackOverflowError

4. Нарушение инкапсуляции и контроля потока

Аспект может нарушить ожидаемое поведение:

@Aspect
public class TimingAspect {
    @Around("execution(*.*(*))")
    public Object measureTime(ProceedingJoinPoint joinPoint) throws Throwable {
        Thread.sleep(1000); // Добавляет 1 сек ко ВСЕМ методам!
        return joinPoint.proceed();
    }
}

// Внутренний метод базовой библиотеки теперь занимает 1 сек
// Это может вызвать timeout'ы в других частях системы

5. Проблемы с debugging

Stack trace становится нечитаемым:

at java.lang.Thread.run(Thread.java:745)
at com.sun.proxy.$Proxy0.getUser(Unknown Source)
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:193)
at BadAspect.traceEverything(BadAspect.java:45)
at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethod(AbstractAspectJAdvice.java:143)
at org.springframework.aop.aspectj.around.AspectJAroundAdvice.invoke(AspectJAroundAdvice.java:70)
at com.sun.proxy.$Proxy0.getUserId(Unknown Source)
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:193)
at BadAspect.traceEverything(BadAspect.java:45)
at org.springframework.aop.aspectj.around.AspectJAroundAdvice.invoke(AspectJAroundAdvice.java:70)

// Где нужный нам код? Потеряется в сотнях строк фреймворка

6. Конфликты с другими аспектами

Если несколько аспектов используют такой Pointcut, они переплетаются:

@Aspect
@Order(1)
public class FirstAspect {
    @Around("execution(*.*(*))")
    public Object first(ProceedingJoinPoint jp) throws Throwable {
        logger.info("First before");
        Object result = jp.proceed();
        logger.info("First after");
        return result;
    }
}

@Aspect
@Order(2)
public class SecondAspect {
    @Around("execution(*.*(*))")
    public Object second(ProceedingJoinPoint jp) throws Throwable {
        logger.info("Second before");
        Object result = jp.proceed();
        logger.info("Second after");
        return result;
    }
}

// Каждый вызов userService.getUser():
// First before
// Second before
// [Actual method]
// Second after
// First after

// Если аспектов 10 — это очень сложная для понимания цепь

7. Утечки памяти и проблемы с garbage collection

Aспекты могут удерживать ссылки на объекты:

@Aspect
public class BadAspect {
    private List<Long> allTimings = new ArrayList<>(); // Утечка памяти!
    
    @Around("execution(*.*(*))")
    public Object track(ProceedingJoinPoint jp) throws Throwable {
        long start = System.nanoTime();
        Object result = jp.proceed();
        long duration = System.nanoTime() - start;
        allTimings.add(duration); // Растёт бесконечно
        return result;
    }
}

// За 8 часов работы: миллиарды записей, OutOfMemoryError

Правильный подход

// ХОРОШО — конкретный Pointcut
@Aspect
public class ProperAspect {
    // Только методы сервисов
    @Around("execution(* com.example.service.*Service.*(..))")  
    public Object traceService(ProceedingJoinPoint joinPoint) throws Throwable {
        logger.debug("Executing: {}", joinPoint.getSignature());
        return joinPoint.proceed();
    }
    
    // Только методы с @Transactional
    @Around("@target(org.springframework.transaction.annotation.Transactional)")
    public Object traceTransactional(ProceedingJoinPoint joinPoint) throws Throwable {
        return joinPoint.proceed();
    }
    
    // Только методы контроллера
    @Around("@target(org.springframework.web.bind.annotation.RestController)")
    public Object traceControllers(ProceedingJoinPoint joinPoint) throws Throwable {
        return joinPoint.proceed();
    }
}

Типичные ошибки

// ❌ ПЛОХО
@Around("execution(*.*(*))")
@Around("execution(*.*(..))")
@Around("execution(public *.*(..))")

// ✅ ХОРОШО
@Around("execution(* com.example.service.*Service.*(..))") // Конкретный package
@Around("@annotation(com.example.Loggable)") // С аннотацией
@Around("@target(org.springframework.stereotype.Service)") // Сервисы
@Around("execution(* com.example.controller.*.*(..))") // Контроллеры

Выводы

Pointcut *.* приводит к:

  1. Катастрофическому падению производительности — 100-1000x замедление
  2. Бесконечным рекурсиям и StackOverflowError'ам
  3. Нарушению функциональности — методы работают не как ожидается
  4. Невозможности отладки — нечитаемые stack trace'ы
  5. Утечкам памяти — аспект удерживает данные
  6. Конфликтам между аспектами — сложная цепь перехватов

Правило золотое: Всегда пиши конкретные Pointcut'ы. Ограничивай перехват:

  • По package'у: com.example.service.*Service
  • По аннотации: @annotation(Logger)
  • По типу класса: @target(Service)
  • По сигнатуре метода: execution(public * get*(..))