JavaRush /Курсы /Модуль 5. Spring /Конфигурация Pointcut и создание кастомных аспектов

Конфигурация Pointcut и создание кастомных аспектов

Модуль 5. Spring
3 уровень , 5 лекция
Открыта

Pointcut — это выражение, которое отвечает на вопрос "где именно в коде должен сработать аспект?". По сути, это фильтр, который выбирает нужные методы из всего приложения. Например, "все методы в сервисном слое" или "методы с определённой аннотацией".

В Spring AOP для описания поинткатов используется синтаксис AspectJ. Да, поначалу он может выглядеть как код с RSA-шифрованием, но на самом деле всё довольно логично. Давайте разберём.

Встроенные выражения Pointcut

Вот самые базовые выражения, которые вам понадобятся:

Выражение Описание
execution Подходит для методов. Используется в 99% случаев
within Указывает область (например, конкретный класс или пакет)
args Подходит для методов с определёнными аргументами
this и target Указывает на объект прокси (this) или реальный целевой объект (target)
bean Подходит для бинов с определённым именем

Пример простого Pointcut-выражения:


@Pointcut("execution(* com.example.service.*.*(..))")
public void serviceLayerMethods() {
    // Этот метод просто маркер для Pointcut
}

Давайте разберём это "заклинание":

  • execution — мы нацеливаемся на методы.
  • * — любой возвращаемый тип (void, String, int, etc).
  • com.example.service.*.* — любой класс в пакете com.example.service и любой метод.
  • (..) — любой набор параметров.

Немного о Wildcards

Wildcards (подстановочные символы) в выражениях Pointcut позволяют нам быть гибкими. Вот короткое руководство:

  • * — означает "любой". Пример: execution(* com.example..*Controller.*(..)) — любой метод в любом классе, содержащем Controller в имени.
  • .. — означает "любой уровень вложенности". Например, com.example..service включает все подпакеты от com.example.

Пример: Углубляемся в Pointcut

Давайте попробуем что-нибудь посложнее. Представим, что мы хотим добавить логирование для всех методов класса, которые начинаются с save.


@Aspect
@Component
public class LoggingAspect {

    @Pointcut("execution(* com.example..*Service.save*(..))")
    public void saveMethodsPointcut() {
        // Pointcut для методов, начинающихся с save
    }

    @Before("saveMethodsPointcut()")
    public void beforeSaveAdvice(JoinPoint joinPoint) {
        System.out.println("Вызов метода: " + joinPoint.getSignature().getName());
    }
}

Здесь мы:

  • определяем точку соединения для всех методов, начинающихся с save в любом классе из подпакетов com.example, название которых заканчивается на Service.
  • Перед вызовом таких методов мы логируем имя метода.

Композиция Pointcut-выражений

Мы уже упоминали, что иногда нужно объединить несколько Pointcut’ов. Например, мы хотим воздействовать только на методы из определённого пакета, и чтобы они ещё начинались с определённого слова.

В AspectJ есть логические операторы для композиций:

  • && — логическое "И".
  • || — логическое "ИЛИ".
  • ! — логическое "НЕ".

Вот пример:


@Pointcut("within(com.example..*)")
public void withinExamplePackage() {}

@Pointcut("execution(* save*(..))")
public void saveMethods() {}

@Pointcut("withinExamplePackage() && saveMethods()")
public void saveMethodsInExamplePackage() {}

Таким образом, saveMethodsInExamplePackage() сработает только на методах, которые начинаются с save, и лежат в пакетах com.example.


Создание кастомного аспекта

Теперь применим полученные знания на практике. Допустим, в нашем приложении есть сервис, который отвечает за управление пользователями (UserService). Мы хотим:

  1. Логировать вызовы методов, начинающихся с find.
  2. Вызывать дополнительную логику только тогда, когда метод возвращает String.

Реализация


@Aspect
@Component
public class CustomAspect {

    // Логирование всех методов, начинающихся с find
    @Pointcut("execution(* com.example.service.UserService.find*(..))")
    public void findMethods() {}

    // Выполнение действия перед вызовом метода
    @Before("findMethods()")
    public void logBeforeFind(JoinPoint joinPoint) {
        System.out.println("Вызов метода: " + joinPoint.getSignature().getName());
    }

    // Реакция на успешное выполнение метода, возвращающего String
    @AfterReturning(
        pointcut = "findMethods()",
        returning = "result"
    )
    public void afterReturningFind(JoinPoint joinPoint, Object result) {
        if (result instanceof String) {
            System.out.println("Метод " + joinPoint.getSignature().getName() +
                               " успешно вернул значение: " + result);
        }
    }
}

Что происходит:

  1. С помощью @Pointcut мы выбираем только методы в классе UserService, чьи названия начинаются с find.
  2. Используем @Before для логирования вызова.
  3. Используем @AfterReturning, чтобы дополнительно обработать возвращаемое значение, если оно является строкой.

Практика: создание сложного кастомного аспекта

Попробуем усложнить задачу. Допустим, нам нужно логировать все методы контроллеров, которые аннотированы @GetMapping.


@Aspect
@Component
public class ControllerLoggingAspect {

    // Pointcut для методов с аннотацией @GetMapping
    @Pointcut("@annotation(org.springframework.web.bind.annotation.GetMapping)")
    public void getMappingMethods() {}

    // Логирование до выполнения метода
    @Before("getMappingMethods()")
    public void logBeforeGetMapping(JoinPoint joinPoint) {
        System.out.println("Вызов метода контроллера: " + joinPoint.getSignature().getName());
    }
}

Мы используем аннотацию @annotation, чтобы чётко определить методы, на которые нацеливаемся (в данном случае методы с @GetMapping). Вы видите, насколько мощным становится ваш инструмент?


Отладка и типичные ошибки

AOP — не без своего набора головоломок. Вот несколько вещей, о которых стоит помнить:

  1. Если ваш аспект не работает, убедитесь, что класс аспекта помечен аннотацией @Component и зарегистрирован в контексте Spring.
  2. Проверьте, что вы правильно указываете путь пакетов в execution. Пропущенный уровень вложенности или опечатка могут свести все усилия на нет.
  3. Обратите внимание на прокси: если вы вызываете метод класса внутри самого же класса, аспект может не сработать.

Ручная проверка Pointcut-выражений

Если вам интересно, какие именно методы попадают под ваше Pointcut-выражение, вы всегда можете это проверить вручную:


@Before("execution(* com.example.service.*.*(..))")
public void debugPointcut(JoinPoint joinPoint) {
    System.out.println("Попал в Pointcut: " + joinPoint.getSignature().getName());
}

Вы сможете увидеть в логах список всех методов, которые соответствуют выражению.


Вы готовы создавать аспекты, которые смогут управлять бизнес-логикой вашего приложения! На следующем этапе мы углубимся ещё больше в прокси-объекты и посмотрим на их скрытые секреты.

Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ