Введение в Spring (10): объяснение использования Spring AOP

Spring

1. Что такое АОП?

АОП — это аббревиатура от Aspect Oriented Programming, что означает: аспектно-ориентированное программирование Это технология, которая реализует унифицированное обслуживание программных функций с помощью прекомпиляции и динамических агентов во время выполнения.

Можно считать, что АОП является дополнением к ООП (объектно-ориентированному программированию).Он в основном используется в таких сценариях, как ведение журнала, статистика производительности, контроль безопасности и т. д. Использование АОП может уменьшить связь между различными частями бизнес-логики и сосредоточиться только на на их соответствующей бизнес-логике реализована, чтобы улучшить читабельность и ремонтопригодность программы.

Например, нам нужно записать входные и выходные параметры всех внешних интерфейсов в проекте, чтобы найти причину при возникновении проблемы.Добавление кода в код каждого внешнего интерфейса для записи входных и выходных параметров, безусловно, может достичь цели, но такой жесткий способ кодирования очень недружественный и негибкий, а само ведение журнала не имеет ничего общего с основной функциональностью, которую пытается реализовать интерфейс.

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

Прежде чем объяснять конкретную реализацию, мы сначала разберемся с несколькими терминами АОП.

1.1 Совет

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

Весенние сокращения имеют 5 типов уведомлений, а именно:

  • Перед уведомлением (Before): вызовите функцию уведомления до вызова целевого метода.
  • После уведомления (After): уведомление о вызове после завершения целевого метода, не важно, каков результат метода в это время.
  • После возврата: вызовите уведомление после успешного выполнения целевого метода.
  • Уведомление об исключении (после выбрасывания): уведомление о вызове после того, как целевой метод выдает исключение
  • Совет вокруг (Around): совет обертывает уведомленный метод и выполняет настраиваемое поведение до и после вызова уведомленного метода.

1.2 Точка соединения

Соединение в процессе выполнения приложения может быть вставлено в раздел точки, эта точка может быть при вызове метода, возникновении исключения, изменении поля.

1.3 Точечная резка

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

1.4 Аспект

Аспекты представляют собой комбинацию советов и точечных сокращений. Вместе советы и подсказки определяют, что такое аспект: что это такое, когда и где выполнять свою функцию.

1.5 Введение

Мы не допускаем введения модификации существующего класса, добавляя новые методы в существующий класс или атрибут.

1.6 ткачество (ткачество)

Плетение — это процесс применения аспектов к целевым объектам и создания новых прокси-объектов.

Аспекты вплетаются в целевой объект в указанной точке соединения.В жизненном цикле целевого объекта могут быть вплетены следующие точки:

  • Время компиляции: Аспекты вплетаются во время компиляции целевого класса. Этот способ требует специального компилятора. Компиляторы AspectJ сплетают аспекты таким образом.
  • Время загрузки класса: Аспекты вплетаются, когда целевой класс загружается в JVM. Для этого подхода требуется специальный загрузчик классов (ClassLoader), который может улучшить байт-код целевого класса до того, как он будет введен в приложение.
  • Период выполнения: Отрезанный момент запуска приложения. Как правило, контейнер АОП динамически создает прокси-объект для целевого объекта при вплетении в поверхность разреза. Весенний АОП соткан таким образом.

2. Spring поддерживает АОП

2.1 Динамический прокси

Spring AOP построен на основе динамических прокси., то есть среда выполнения Spring динамически создает прокси-объект для целевого объекта.

Прокси-класс инкапсулирует целевой класс, перехватывает вызов уведомленного метода и перенаправляет вызов реальному целевому компоненту.

Когда прокси-класс перехватывает вызов метода, логика аспекта выполняется перед вызовом метода целевого компонента.

2.2 в разрезе

Оборачивая аспекты в прокси-классы, Spring вплетает аспекты в управляемые Spring bean-компоненты во время выполнения, то есть Spring не создает прокси-объекты до тех пор, пока приложению не потребуется, чтобы bean-компонент был проксирован.

Поскольку прокси-объект создается только во время выполнения Spring, нам не нужен специальный компилятор для объединения аспектов Spring AOP.

2.3 Ограничения по точкам подключения

Spring поддерживает только точки соединения на уровне метода.Если вам нужны точки соединения на уровне поля или конструктора, вы можете использовать AspectJ для дополнения функциональности Spring AOP.

3. Использование Spring АОП

Предположим, у нас есть интерфейс для живых выступлений Performance и его класс реализации SleepNoMore:

package chapter04.concert;

/**
 * 现场表演,如舞台剧,电影,音乐会
 */
public interface Performance {
    void perform();
}
package chapter04.concert;

import org.springframework.stereotype.Component;

/**
 * 戏剧:《不眠之夜Sleep No More》
 */
@Component
public class SleepNoMore implements Performance {
    @Override
    public void perform() {
        System.out.println("戏剧《不眠之夜Sleep No More》");
    }
}

Поскольку это спектакль, ему нужна публика. Предположим, что наши потребности таковы: перед просмотром спектакля зрители садятся и выключают звук в своих мобильных телефонах. После просмотра спектакля зрители аплодируют. вернуть их билеты.Конечно, мы можем прописать эту логику в методе Perform() выше, это не рекомендуется, потому что эта логика не имеет ничего общего с ядром представления.Даже если зрители не настраивают телефон на отключить звук или аплодировать после выступления, это не повлияет на исполнение выступления.

Для этого требования мы можем использовать AOP для достижения.

3.1 Определение аспектов

Сначала добавьте следующие зависимости в файл pom.xml:

<!--spring aop支持-->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aop</artifactId>
    <version>5.1.8.RELEASE</version>
</dependency>
<!--aspectj支持-->
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjrt</artifactId>
    <version>1.8.5</version>
</dependency>
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.8.9</version>
</dependency>

Затем определите аспект аудитории следующим образом:

package chapter04.concert;

import org.aspectj.lang.annotation.Aspect;

/**
 * 观众
 * 使用@Aspect注解定义为切面
 */
@Aspect
public class Audience {
}

Меры предосторожности:@AspectАннотация указывает, что класс Audience является аспектом.

3.2 Определение предварительных уведомлений

Аудитория определена в разделе предварительного уведомления следующим образом:

/**
 * 表演之前,观众就座
 */
@Before("execution(* chapter04.concert.Performance.perform(..))")
public void takeSeats() {
    System.out.println("Taking seats");
}

/**
 * 表演之前,将手机调至静音
 */
@Before("execution(* chapter04.concert.Performance.perform(..))")
public void silenceCellPhones() {
    System.out.println("Silencing cell phones");
}

Ключевой код здесь@Before("execution(* chapter04.concert.Performance.perform(..))"), который определяет 1 предварительное уведомление, гдеexecution(* chapter04.concert.Performance.perform(..))Выражение AspectJ называется точкой касания, каждая часть объясняется следующим образом:

  • @BEfore: эта аннотация используется для определения предварительного уведомления, а метод уведомления будет выполняться до того, как называется целевой метод.
  • исполнение: срабатывает при выполнении метода
  • *: Указывает, что нам не важен тип возвращаемого значения метода, т. е. это может быть любой тип
  • Chapter04.concert.performance.perform: Укажите, как добавить предварительное уведомление, используя полное имя класса и имя метода.
  • (..): список параметров метода использует (..), Указывая на то, что нам все равно, каковы входные параметры метода, то есть это может быть любого типа

3.3 Определение почтовых уведомлений

Определение пост-уведомления в аспекте Аудитория выглядит так:

/**
 * 表演结束,不管表演成功或者失败
 */
@After("execution(* chapter04.concert.Performance.perform(..))")
public void finish() {
    System.out.println("perform finish");
}

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

3.4 Определение уведомлений о возврате

Определите ответное уведомление в аспекте Аудитория следующим образом:

/**
 * 表演之后,鼓掌
 */
@AfterReturning("execution(* chapter04.concert.Performance.perform(..))")
public void applause() {
    System.out.println("CLAP CLAP CLAP!!!");
}

Примечание: аннотация @AfterReturning используется для определения уведомления о возврате, метод уведомления будет вызываться после возврата целевого метода.

3.5 Определение уведомлений об исключениях

Определение уведомлений об исключениях в аспекте «Аудитория» выглядит следующим образом:

/**
 * 表演失败之后,观众要求退款
 */
@AfterThrowing("execution(* chapter04.concert.Performance.perform(..))")
public void demandRefund() {
    System.out.println("Demanding a refund");
}

Примечание: аннотация @AfterThrowing используется для определения уведомления об исключении, метод уведомления будет вызываться после того, как целевой метод выдаст исключение.

3.6 Определение многоразовых выражений pointcut

Осторожно, вы можете обнаружить, что в 5 pointcut, которые мы определили выше, выражения pointcut одинаковы, что, очевидно, нехорошо, но, к счастью, мы можем использовать@Pointcutаннотация для определения многоразовых выражений pointcut:

/**
 * 可复用的切点
 */
@Pointcut("execution(* chapter04.concert.Performance.perform(..))")
public void perform() {
}

Затем пять указателей, определенных ранее, могут ссылаться на это выражение PointCut:

/**
 * 表演之前,观众就座
 */
@Before("perform()")
public void takeSeats() {
    System.out.println("Taking seats");
}

/**
 * 表演之前,将手机调至静音
 */
@Before("perform()")
public void silenceCellPhones() {
    System.out.println("Silencing cell phones");
}

/**
 * 表演结束,不管表演成功或者失败
 */
@After("perform()")
public void finish() {
    System.out.println("perform finish");
}

/**
 * 表演之后,鼓掌
 */
@AfterReturning("perform()")
public void applause() {
    System.out.println("CLAP CLAP CLAP!!!");
}

/**
 * 表演失败之后,观众要求退款
 */
@AfterThrowing("perform()")
public void demandRefund() {
    System.out.println("Demanding a refund");
}

3.7 модульный тест

Новый класс конфигурации ConcertConfig выглядит следующим образом:

package chapter04.concert;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

@Configuration
@EnableAspectJAutoProxy
@ComponentScan
public class ConcertConfig {
    @Bean
    public Audience audience() {
        return new Audience();
    }
}

Примечания: что отличается то, что мы используем@EnableAspectJAutoProxyАннотация, которая используется для включения функции автоматического прокси.

Создайте новый класс Main и добавьте следующий тестовый код в его метод main():

package chapter04.concert;

import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class Main {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ConcertConfig.class);

        Performance performance = context.getBean(Performance.class);
        performance.perform();

        context.close();
    }
}

Запустив код, вывод будет следующим:

Silencing cell phones

Taking seats

Драма "Больше не спи"

perform finish

CLAP CLAP CLAP!!!

Слегка измените метод Perform() класса SleepNoMore, чтобы он выдавал исключение:

@Override
public void perform() {
    int number = 3 / 0;
    System.out.println("戏剧《不眠之夜Sleep No More》");
}

Запустив код снова, вывод будет следующим:

Silencing cell phones

Taking seats

perform finish

Demanding a refund

Exception in thread "main" java.lang.ArithmeticException: / by zero

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

Стоит отметить, что использование@AspectАннотированный класс аспекта должен быть компонентом (независимо от того, как он объявлен), в противном случае аспект не вступит в силу, поскольку автопрокси AspectJ будет использоваться только для@AspectАннотированные компоненты создают прокси-классы.

То есть, если мы в конфигурации класса ConcertConfig удалим или закомментируем следующий код:

@Bean
public Audience audience() {
    return new Audience();
}

Результатом запуска станет:

Драма "Больше не спи"

3.8 Создание объемных уведомлений

мы можем использовать@AroundАннотация создает объемный совет, который позволяет настроить собственную логику до и после вызова целевого метода.

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

package chapter04.concert;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;

@Aspect
public class AroundAudience {
    /**
     * 可重用的切点
     */
    @Pointcut("execution(* chapter04.concert.Performance.perform(..))")
    public void perform() {
    }

    @Around("perform()")
    public void watchPerform(ProceedingJoinPoint joinPoint) {
        try {
            System.out.println("Taking seats");
            System.out.println("Silencing cell phones");

            joinPoint.proceed();

            System.out.println("CLAP CLAP CLAP!!!");
        } catch (Throwable throwable) {
            System.out.println("Demanding a refund");
        } finally {
            System.out.println("perform finish");
        }
    }
}

Здесь следует отметить, что метод имеет параметр подушечки типа, в способе можно вызвать, вызывая свой целевой метод продолжить () методом.

Затем измените код класса ConcertConfig:

package chapter04.concert;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

@Configuration
@EnableAspectJAutoProxy
@ComponentScan
public class ConcertConfig {
    /*@Bean
    public Audience audience() {
        return new Audience();
    }*/

    @Bean
    public AroundAudience aroundAudience() {
        return new AroundAudience();
    }
}

Результат запуска следующий:

Taking seats

Silencing cell phones

Драма "Больше не спи"

CLAP CLAP CLAP!!!

perform finish

4. Исходный код и ссылка

Адрес источника:GitHub.com/Где находится Ухань/SPR…, добро пожаловать на скачивание.

Весна в действии (4-е издание) Крейга Уоллса

АОП (аспектно-ориентированное программирование) ru.knowledgr.com