Использование SpringBoot АОП

Spring Boot

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

Также есть четкая логика, пусть бизнес-логика фокусируется на самом бизнесе, не заботясь о других вещах, например транзакциях.

АОП Spring реализуется через динамический прокси JDK и CGLIB.

Во-первых, терминология АОП:

В aop есть куча терминов, которые очень трудно понять, так что позвольте мне просто сказать это.

  • Уведомление (в некоторых местах называемое улучшением) (Совет)

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

  • Точка присоединения

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

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

    По сути, это отфильтрованная точка подключения, все методы в классе являются точками подключения, но не все из них нужны, некоторые из них будут выбраны в качестве точек подключения в качестве точек отсечки. Если совет определяет действие или время выполнения аспекта, то точка определяет место выполнения.

  • Аспект

    По сути, это комбинация уведомления и pointcut.Notification и pointcut вместе определяют все содержимое аспекта, что он делает, когда и где его выполнять.

  • Введение

    Без изменения кода существующего класса добавление свойств и методов к этому классу позволяет существующим классам иметь новое поведение и состояния без их изменения. На самом деле аспект (то есть новый атрибут метода: определение уведомления) используется в целевом классе.

  • цель

    Объект для уведомления. То есть объекты, которым нужно добавить дополнительный код, то есть реальная бизнес-логика организована по аспектам.

  • Ткачество

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

    • Время компиляции: аспекты вплетаются во время компиляции целевого класса, что требует специального компилятора.
    • Время загрузки класса: Аспекты вплетаются, когда целевой класс загружается в JVM, для чего требуется специальный загрузчик классов, улучшающий байт-код целевого класса до того, как он будет введен в приложение.
    • Время выполнения: Аспекты вплетаются в определенное время, когда приложение работает. Как правило, когда аспект вплетается, контейнер АОП динамически создает прокси-объект для целевого объекта. Таким образом, Spring АОП вплетает аспект.

пример:

public class UserService{
    void save(){}
    List list(){}
    ....
}

Перед методом save() в UserService необходимо открыть транзакцию, закрыть транзакцию после метода и откатить транзакцию при возникновении исключения.

Тогда все методы в UserService — это JoinPoint, а метод save() — это Pointcut. Метод, который необходимо выполнить до и после метода save(), — это совет (Advice), а pointcut и совет вместе составляют аспект (Aspect). Метод save() является целью. Динамическое добавление кода, который вы хотите выполнить, до и после плетения метода save().

В некоторых случаях имеет смысл вызывать улучшения для уведомлений.Добавление других методов до и после бизнес-метода на самом деле является усовершенствованием этого метода.

2. Обычно используемые типы уведомлений AOP (расширенные)

  • до (предварительное уведомление): выполнить до того, как метод начнет выполняться
  • after (после уведомления): выполняется после выполнения метода
  • afterReturning: выполняется после возврата метода
  • afterThrowing (уведомление об исключении): выполняется при возникновении исключения
  • вокруг (объемное уведомление): будет выполняться до и после выполнения метода

3. Порядок исполнения

around > before > around > after > afterReturning

В-четвертых, давайте поговорим об очень властных и очень немногих функциях SpringAop — Введение.

  1. класс конфигурации
@Aspect
@Component
public class IntroductionAop {

    @DeclareParents(value = "com.jiuxian..service..*", defaultImpl = DoSthServiceImpl.class)
    public DoSthService doSthService;

}
  1. сервисный код
public interface DoSthService {

    void doSth();
}

@Service
public class DoSthServiceImpl implements DoSthService {

    @Override
    public void doSth() {
        System.out.println("do sth ....");
    }
    
}

public interface UserService {

    void testIntroduction();
}

@Service
public class UserServiceImpl implements UserService {

    @Override
    public void testIntroduction() {
        System.out.println("do testIntroduction");
    }
}
  1. тестовый код
@Test
public void testIntroduction() {
    userService.testIntroduction();
    //Aop 让UserService方法拥有 DoSthService的方法
    DoSthService doSthService = (DoSthService) userService;
    doSthService.doSth();
}
  1. результат
do testIntroduction
do sth ....

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

  1. класс конфигурации

(1) парный метод

@Aspect
@Component
public class TransactionAop {

    @Pointcut("execution(* com.jiuxian..service.*.*(..))")
    public void pointcut() {
    }

    @Before("pointcut()")
    public void beginTransaction() {
        System.out.println("before beginTransaction");
    }

    @After("pointcut()")
    public void commit() {
        System.out.println("after commit");
    }

    @AfterReturning("pointcut()", returning = "returnObject")
    public void afterReturning(JoinPoint joinPoint, Object returnObject) {
        System.out.println("afterReturning");
    }

    @AfterThrowing("pointcut()")
    public void afterThrowing() {
        System.out.println("afterThrowing afterThrowing  rollback");
    }

    @Around("pointcut()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        try {
            System.out.println("around");
            return joinPoint.proceed();
        } catch (Throwable e) {
            e.printStackTrace();
            throw e;
        } finally {
            System.out.println("around");
        }
    }
}

(2) Для аннотаций

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Log {
    String value() default "";
}
@Aspect
@Component
public class AnnotationAop {

    @Pointcut(value = "@annotation(log)", argNames = "log")
    public void pointcut(Log log) {
    }

    @Around(value = "pointcut(log)", argNames = "joinPoint,log")
    public Object around(ProceedingJoinPoint joinPoint, Log log) throws Throwable {
        try {
            System.out.println(log.value());
            System.out.println("around");
            return joinPoint.proceed();
        } catch (Throwable throwable) {
            throw throwable;
        } finally {
            System.out.println("around");
        }
    }
}

  @Before("@annotation(com.jiuxian.annotation.Log)")
    public void before(JoinPoint joinPoint) {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        Log log = method.getAnnotation(Log.class);
        System.out.println("注解式拦截 " + log.value());
    }
  1. реализация сервисного метода
public interface UserService {

    String save(String user);

    void testAnnotationAop();
}


@Service
public class UserServiceImpl implements UserService {

    @Override
    public String save(String user) {
        System.out.println("保存用户信息");
        if ("a".equals(user)) {
            throw new RuntimeException();
        }
        return user;
    }

    @Log(value = "test")
    @Override
    public void testAnnotationAop() {
        System.out.println("testAnnotationAop");
    }
}
  1. тестовый класс
@RunWith(SpringRunner.class)
@SpringBootTest
public class SpringbootAopApplicationTests {

    @Resource
    private UserService userService;

    @Test
    public void testAop1() {
        userService.save("张三");
        Assert.assertTrue(true);
    }

    @Test
    public void testAop2() {
        userService.save("a");
    }
    
    @Test
    public void testAop3() {
        userService.testAnnotationAop();
    }
}
  1. результат
  • При выполнении testAop1
around
before beginTransaction
保存用户信息
around
after commit
afterReturning :: 张三
  • При выполнении testAop2
around
before beginTransaction
保存用户信息
around
after commit
afterThrowing  rollback
  • При выполнении testAop3
test
around
testAnnotationAop
around
  1. файл pom
 <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
</dependency>

Шесть, наиболее часто используемое объяснение выполнения

Пример: выполнение(* com.jiuxian..service.*.*(..))

  • тело выражения выполнения
  • Первый * представляет произвольное возвращаемое значение
  • Имя пакета вырезано com.jiuxian aop
  • После пакета .. указывает текущий пакет и его подпакеты
  • Второй * представляет имя класса, представляющего все классы
  • .*(..) означает любой метод, круглые скобки представляют параметры .. означает любой параметр

Пример: выполнение (* com.jiuxian..service.*Service.add*(String))

Указывает: в пакете службы в пакете com.jiuxian и его подпакетах имя класса заканчивается на Service, метод начинается на add, а тип параметра — String.

7. Специальное использование

@Pointcut("execution(public * *(..))")
private void anyPublicOperation() {} 

@Pointcut("within(com.xyz.someapp.trading..*)")
private void inTrading() {} 

@Pointcut("anyPublicOperation() && inTrading()")
private void tradingOperation() {}

Pointcut можно определить с помощью операторов &&, ||, !

8. Более подробную информацию можно найти на официальном сайте.

Введение на официальный сайт SpringAOP

Девять, пример кода этой статьи

Источник GitHub

Приведенный выше код основан на Springboot 2.0.