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

Что такое pointcut?

2.0 Middle🔥 161 комментариев
#SOLID и паттерны проектирования#Spring Boot и Spring Data

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

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

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

Pointcut в Spring AOP

Pointcut — это выражение или описание в Spring AOP (Aspect-Oriented Programming), которое определяет, к каким методам приложить аспект (aspect). Pointcut указывает на конкретные точки в коде, где должна выполняться дополнительная логика, например логирование, проверка прав доступа или управление транзакциями.

В переводе pointcut означает «точка среза» — место, где пересекаются основной код приложения и аспект.

Компоненты AOP

Для понимания pointcut важно знать его роль в контексте AOP:

Aspect — модуль, содержащий связанный код, который должен быть выполнен на разных местах приложения.

Joinpoint — конкретная точка выполнения в программе (например, вызов метода), где может быть применено поведение аспекта.

Advice — код аспекта, который выполняется в joinpoint (например, логирование перед методом).

Pointcut — предикат, который матчит joinpoints и определяет, когда выполняется advice.

Типы Pointcut выражений

Pointcuts в Spring можно определять различными способами:

execution() — самый часто используемый тип. Матчит методы по сигнатуре:

@Aspect
@Component
public class LoggingAspect {
    
    // Матчит все методы в классах пакета service
    @Before("execution(* com.example.service.*.*(..))") 
    public void logBeforeMethod(JoinPoint joinPoint) {
        System.out.println("Вызван метод: " + joinPoint.getSignature());
    }
    
    // Матчит только методы, возвращающие void
    @Before("execution(void com.example.service.*.*(..))") 
    public void logVoidMethods(JoinPoint joinPoint) {
        System.out.println("Выполнение: " + joinPoint.getSignature());
    }
    
    // Матчит методы, принимающие один String параметр
    @Before("execution(* com.example.service.*.*(java.lang.String))") 
    public void logStringMethods(JoinPoint joinPoint) {
        System.out.println("Метод со String: " + joinPoint.getSignature());
    }
}

within() — матчит методы в классах, принадлежащих определённому пакету:

@Aspect
@Component
public class SecurityAspect {
    
    // Матчит все методы всех классов в пакете service
    @Before("within(com.example.service.*)") 
    public void checkSecurity(JoinPoint joinPoint) {
        System.out.println("Проверка доступа для: " + joinPoint.getSignature());
    }
}

@annotation() — матчит методы, аннотированные определённой аннотацией:

// Создаём кастомную аннотацию
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface LogExecution {
}

// Используем в аспекте
@Aspect
@Component
public class AnnotationAspect {
    
    // Матчит все методы, аннотированные @LogExecution
    @Before("@annotation(com.example.LogExecution)")
    public void logAnnotatedMethods(JoinPoint joinPoint) {
        System.out.println("Вызван аннотированный метод: " + joinPoint.getSignature());
    }
}

@Service
public class UserService {
    
    @LogExecution
    public void createUser(String username) {
        System.out.println("Создание пользователя: " + username);
    }
}

call() — матчит вызовы методов (в BytecodeWeaving режиме):

@Before("call(* com.example.service.*.*(..))") 
public void trackMethodCalls(JoinPoint joinPoint) {
    System.out.println("Вызов: " + joinPoint.getSignature());
}

Синтаксис выражений execution()

Основной синтаксис: execution(модификаторы тип-возврата пакет.класс.метод(параметры))

execution(public * com.example.service.UserService.get*(..)))
         ^^^^^^ ^   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^
      модификатор | возвращаемый тип | пакет.класс.метод | параметры

* — любое значение для этой части
.. — любое количество параметров
+ — класс и все его подклассы

Примеры:

// Все методы класса UserService
@Before("execution(* com.example.service.UserService.*(..))") 

// Все методы, начинающиеся на get
@Before("execution(* com.example.service.*.get*(..))") 

// Методы с двумя параметрами (первый Long, второй String)
@Before("execution(* com.example.service.*.*(Long, String))") 

// Методы, возвращающие User
@Before("execution(com.example.User com.example.service.*.*(..))") 

Комбинирование Pointcuts

Pointcuts можно комбинировать логическими операторами:

@Aspect
@Component
public class CombinedAspect {
    
    // ИЛИ — матчит методы service ИЛИ controller
    @Before("execution(* com.example.service.*.*(..)) || " +
            "execution(* com.example.controller.*.*(..))")
    public void logAll(JoinPoint joinPoint) {
        System.out.println("Логирование: " + joinPoint.getSignature());
    }
    
    // И — матчит методы service И начинающиеся на save
    @Before("execution(* com.example.service.*.save*(..)) && " +
            "execution(public *.*(..))")
    public void auditSave(JoinPoint joinPoint) {
        System.out.println("Аудит сохранения: " + joinPoint.getSignature());
    }
    
    // НЕ — матчит методы, не начинающиеся на get
    @Before("execution(* com.example.service.*.*(..)) && " +
            "!execution(* com.example.service.*.get*(..))")
    public void logWrite(JoinPoint joinPoint) {
        System.out.println("Логирование записи: " + joinPoint.getSignature());
    }
}

Практический пример: Performance Monitoring

@Aspect
@Component
public class PerformanceAspect {
    
    // Определяем pointcut как метод для переиспользования
    @Pointcut("execution(* com.example.service.*.*(..))") 
    public void serviceLayer() {}
    
    @Pointcut("@annotation(com.example.Monitored)") 
    public void monitoredMethods() {}
    
    // Комбинируем pointcuts
    @Around("serviceLayer() && monitoredMethods()")
    public Object measureExecutionTime(ProceedingJoinPoint joinPoint) 
            throws Throwable {
        long startTime = System.currentTimeMillis();
        
        try {
            Object result = joinPoint.proceed();
            return result;
        } finally {
            long executionTime = System.currentTimeMillis() - startTime;
            System.out.println(joinPoint.getSignature() + 
                             " выполнен за " + executionTime + "ms");
        }
    }
}

Типы Advice в Pointcut

Dля каждого pointcut можно определить различные типы advice:

@Before("pointcut") // До выполнения метода
public void before(JoinPoint joinPoint) {}

@After("pointcut") // После выполнения (в любом случае)
public void after(JoinPoint joinPoint) {}

@AfterReturning("pointcut", returning="result") // После успешного возврата
public void afterReturning(JoinPoint joinPoint, Object result) {}

@AfterThrowing("pointcut", throwing="exception") // После исключения
public void afterThrowing(JoinPoint joinPoint, Exception exception) {}

@Around("pointcut") // Полный контроль над выполнением
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {}

Pointcuts — мощный механизм для отделения cross-cutting concerns (сквозных забот, таких как логирование, безопасность, кэширование) от основной бизнес-логики приложения, делая код чище и более maintainable.