«1. Обзор

В этой статье мы обсудим различные типы АОП-рекомендаций, которые можно создавать в Spring.

Совет — это действие, предпринимаемое аспектом в определенной точке соединения. Различные типы советов включают советы «примерно», «до» и «после». Основная цель аспектов — поддержка сквозных функций, таких как ведение журнала, профилирование, кэширование и управление транзакциями.

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

2. Включение рекомендаций

В Spring вы можете объявить рекомендации с помощью аннотаций AspectJ, но вы должны сначала применить аннотацию @EnableAspectJAutoProxy к вашему классу конфигурации, что позволит поддерживать обработку компонентов, помеченных аннотацией AspectJ @Aspect.

@Configuration
@EnableAspectJAutoProxy
public class AopConfiguration {
    ...
}

2.1. Spring Boot

В проектах Spring Boot нам не нужно явно использовать @EnableAspectJAutoProxy. Существует специальный AopAutoConfiguration, который включает поддержку Spring AOP, если Aspect или Advice находятся в пути к классам.

3. Перед советом

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

Рассмотрим следующий аспект, который просто регистрирует имя метода перед его вызовом:

@Component
@Aspect
public class LoggingAspect {

    private Logger logger = Logger.getLogger(LoggingAspect.class.getName());

    @Pointcut("@target(org.springframework.stereotype.Repository)")
    public void repositoryMethods() {};

    @Before("repositoryMethods()")
    public void logMethodCall(JoinPoint jp) {
        String methodName = jp.getSignature().getName();
        logger.info("Before " + methodName);
    }
}

Совет logMethodCall будет выполняться перед любым методом репозитория, определенным в pointcut репозиторияMethods.

4. Совет After

Совет After, объявленный с помощью аннотации @After, выполняется после выполнения соответствующего метода, независимо от того, было ли выброшено исключение.

В чем-то он похож на блок finally. Если вам нужно, чтобы совет запускался только после нормального выполнения, вы должны использовать совет возврата, объявленный аннотацией @AfterReturning. Если вы хотите, чтобы ваш совет срабатывал только тогда, когда целевой метод генерирует исключение, вы должны использовать совет по бросанию, объявленный с помощью аннотации @AfterThrowing.

Предположим, что мы хотим уведомить некоторые компоненты приложения, когда создается новый экземпляр Foo. Мы могли бы опубликовать событие от FooDao, но это нарушило бы принцип единой ответственности.

Вместо этого мы можем добиться этого, определив следующий аспект:

@Component
@Aspect
public class PublishingAspect {

    private ApplicationEventPublisher eventPublisher;

    @Autowired
    public void setEventPublisher(ApplicationEventPublisher eventPublisher) {
        this.eventPublisher = eventPublisher;
    }

    @Pointcut("@target(org.springframework.stereotype.Repository)")
    public void repositoryMethods() {}

    @Pointcut("execution(* *..create*(Long,..))")
    public void firstLongParamMethods() {}

    @Pointcut("repositoryMethods() && firstLongParamMethods()")
    public void entityCreationMethods() {}

    @AfterReturning(value = "entityCreationMethods()", returning = "entity")
    public void logMethodCall(JoinPoint jp, Object entity) throws Throwable {
        eventPublisher.publishEvent(new FooCreationEvent(entity));
    }
}

Прежде всего обратите внимание, что с помощью аннотации @AfterReturning мы можем получить доступ к возвращаемому значению целевого метода. Во-вторых, объявив параметр типа JoinPoint, мы можем получить доступ к аргументам вызова целевого метода.

Затем мы создаем прослушиватель, который будет просто регистрировать событие:

@Component
public class FooCreationEventListener implements ApplicationListener<FooCreationEvent> {

    private Logger logger = Logger.getLogger(getClass().getName());

    @Override
    public void onApplicationEvent(FooCreationEvent event) {
        logger.info("Created foo instance: " + event.getSource().toString());
    }
}

5. Around Advice

Around Advice окружает точку соединения, такую ​​как вызов метода.

Это самый мощный совет. Совет вокруг может выполнять пользовательское поведение как до, так и после вызова метода. Он также отвечает за выбор, следует ли перейти к точке соединения или сократить выполнение рекомендуемого метода, предоставляя собственное возвращаемое значение или вызывая исключение.

Чтобы продемонстрировать его использование, предположим, что мы хотим измерить время выполнения метода. Давайте создадим для этого Аспект:

@Aspect
@Component
public class PerformanceAspect {

    private Logger logger = Logger.getLogger(getClass().getName());

    @Pointcut("within(@org.springframework.stereotype.Repository *)")
    public void repositoryClassMethods() {};

    @Around("repositoryClassMethods()")
    public Object measureMethodExecutionTime(ProceedingJoinPoint pjp) throws Throwable {
        long start = System.nanoTime();
        Object retval = pjp.proceed();
        long end = System.nanoTime();
        String methodName = pjp.getSignature().getName();
        logger.info("Execution of " + methodName + " took " + 
          TimeUnit.NANOSECONDS.toMillis(end - start) + " ms");
        return retval;
    }
}

Этот совет срабатывает, когда выполняется любая из точек соединения, совпавших с pointcut репозиторияClassMethods.

Этот совет принимает один параметр типа ProceedingJointPoint. Параметр дает нам возможность выполнить действие перед вызовом целевого метода. В этом случае мы просто сохраняем время запуска метода.

Во-вторых, тип возврата совета — Object, поскольку целевой метод может возвращать результат любого типа. Если целевой метод недействителен, будет возвращено значение null. После вызова целевого метода мы можем измерить время, зарегистрировать его и вернуть значение результата метода вызывающей стороне.

6. Обзор

В этой статье мы узнали о различных типах советов в Spring, их объявлениях и реализациях. Мы определили аспекты, используя подход на основе схемы и аннотации AspectJ. Мы также предоставили несколько возможных приложений для консультаций.

Реализацию всех этих примеров и фрагментов кода можно найти в моем проекте на GitHub.