Spring AOP — Введение в использование методов аннотации (подробное объяснение в длинной статье)

Java
Spring AOP — Введение в использование методов аннотации (подробное объяснение в длинной статье)

предисловие

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

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

Вот карта разума в первую очередь.

Spring AOP.png

Что такое АОП

АОП: аспектно-ориентированное программирование

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

Недавно в книге «Техническая архитектура больших веб-сайтов» Ли Чжижи автор упомянул, что разработка системы с низким уровнем связи является одной из конечных целей проектирования программного обеспечения. Такую концепцию воплощает аспектно-ориентированный подход к программированию АОП. Некоторые повторяющиеся функциональные коды (записи журналов, управление безопасностью и т. д.), которые не связаны с основной бизнес-логикой, извлекаются и инкапсулируются модульным образом, чтобы добиться разделения задач и разделения модулей, что упрощает работу со всей системой. поддерживать и управлять. .

Этот дизайн «разделяй и властвуй» заставляет меня чувствовать красоту.

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

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

понятие существительное

Как упоминалось ранее, Spring AOP расширяет концепции AspectJ и использует аннотации в пакете jar, предоставляемом AspectJ. То есть понятия и термины в Spring AOP не уникальны для Spring, а связаны с АОП.

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

срок концепция
Aspect срезPointcutиAdviceКоллекция , как правило, одна как класс.PointcutиAdviceвместе определяют все, что касается аспекта, этокогда, когда и гдеполная функция.
Joinpoint Это представляет собой аспект АОП, который можно подключить к вашему приложению. Также можно сказать, что это фактическое место в приложении, где выполняются действия с использованием среды Spring AOP.
Advice Это фактическое действие, предпринятое до или после выполнения метода. Это фактический фрагмент кода, который вызывается во время выполнения программы среды Spring AOP.
Pointcut Это набор из одного или нескольких точечных разрезов, на которых должны выполняться точечные разрезы.Advice. Вы можете задать точечные разрезы с помощью выражений или шаблонов, как описано в последующих примерах.
Introduction Ссылки позволяют нам добавлять новые методы или свойства в существующий класс.
Weaving Процесс создания улучшенного объекта. Это можно сделать во время компиляции (например, с помощью компилятора AspectJ) или во время выполнения. Spring, как и другие чистые среды Java AOP, выполняет переплетение во время выполнения.

PS: При разборе понятий у меня вопрос.Почему так много китайских статей в интернете совет переводят как "уведомление"? ? ? Имеет ли это смысл концептуально? ? ? Я предпочитаю переводить это как «улучшение» (одновременный китайский сайт ifeve.com также переводится как «улучшение»).

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

срок концепция
Before Выполнить расширение перед вызовом метода
After Выполнение расширения после вызова метода
After-returning Выполните расширение после успешного выполнения метода
After-throwing Выполнить расширение после того, как метод вызовет указанное исключение
Around Выполнение пользовательского расширенного поведения до и после вызова метода (наиболее гибкий способ)

Как пользоваться

После Spring 2.0 в Spring AOP есть два метода настройки.

  1. schema-based: после Spring 2.0 используйте XML для настройки, используйте пространство имен<aop />

  2. @AspectJ конфигурация: метод аннотации, предоставляемый после Spring 2.0. Хотя здесь он называется @AspectJ, это не имеет ничего общего с AspectJ.

PS: Лично я люблю @AspectJ таким образом, и это наиболее удобно в использовании. Это также может быть связано с тем, что я думаю, что Spring Beans, настроенные в XML, не являются краткими и выглядят плохо для написания, поэтому это немного исключает. 23333~

В этой статье в основном объясняется метод аннотаций и дается соответствующая ДЕМОНСТРАЦИЯ; последующий анализ исходного кода также будет использовать метод аннотаций в качестве примера для объяснения исходного кода Spring AOP (после прочтения всего анализа исходного кода будут использоваться другие методы). по аналогии, потому что принцип тот же)

Если вас интересуют другие способы настройки, вы можете поискать в Google другие учебные материалы.


Подойдите к разделительной линии,Официально начать

1. Включите@AspectJСпособ настройки аннотаций

включи@AspectJЕсть два способа настроить аннотации

  1. Настроить в XML:

    <aop:aspectj-autoproxy/>
    
  2. использовать@EnableAspectJAutoProxyаннотация

    @Configuration
    @EnableAspectJAutoProxy
    public class Config {
    
    }
    

После включения вышеуказанной конфигурации всев контейнере,одеяло@AspectJАннотированные бобыВсе они будут рассматриваться Spring как класс конфигурации АОП, называемый аспектом.

ПРИМЕЧАНИЕ. Здесь следует отметить, что аннотация @AspectJ работает только с Spring Beans, поэтому классы, которые вы украшаете с помощью @Aspect, либо украшаются аннотациями @Component, либо настраиваются в XML.

Например, написано следующее,

// 有效的AOP配置类
@Aspect
@Component
public class MyAspect {
 	//....   
}

// 如果没有在XML配置过,那这个就是无效的AOP配置类
@Aspect
public class MyAspect {
 	//....   
}

2. Настройте Pointcut (Расширенный Pointcut)

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

В Spring мы можем думать о Pointcut как о методе, используемом для сопоставления всех bean-компонентов в контейнере Spring, которые соответствуют заданному условию.

Например, написано следующее,

    // 指定的方法
    @Pointcut("execution(* testExecution(..))")
    public void anyTestMethod() {}

Ниже приведен полный список методов сопоставления Pointcut:

  1. execution: соответствует методу подпись

    Самый простой способ сделать это - пример выше,"execution(* testExecution(..))"Указывает, что совпадающее имяtestExecutionМетоды,*представляет любое возвращаемое значение,(..)Представляет ноль или более произвольных аргументов.

  2. **within:** указывает метод в классе или пакете (только для Spring AOP)

        // service 层
        // ".." 代表包及其子包
        @Pointcut("within(ric.study.demo.aop.svc..*)")
        public void inSvcLayer() {}
    
  3. @annotation: метод имеет специальную аннотацию

        // 指定注解
        @Pointcut("@annotation(ric.study.demo.aop.HaveAop)")
        public void withAnnotation() {}
    
  4. bean(idOrNameOfBean): соответствует имени bean-компонента (эксклюзивно для Spring AOP)

        // controller 层
        @Pointcut("bean(testController)")
        public void inControllerLayer() {}
    

Выше приведены несколько распространенных методов настройки в повседневном использовании.

Для получения более подробных требований соответствия вы можете обратиться к этой статье:Woohoo. Принесите арлингтон-терьера.com/spring-op-…

Что касается конфигурации Pointcut, у Spring официально есть такое предложение:

When working with enterprise applications, you often want to refer to modules of the application and particular sets of operations from within several aspects. We recommend defining a "SystemArchitecture" aspect that captures common pointcut expressions for this purpose. A typical such aspect would look as follows:

То есть, если вы разрабатываете корпоративные приложения, Spring рекомендует использоватьSystemArchitectureЭтот метод конфигурации аспекта означает, что все общедоступные конфигурации PointCut записываются и поддерживаются в этом одном классе. Пример, приведенный в официальном документе веб-сайта, выглядит следующим образом (в тексте используется конфигурация XML, поэтому аннотация @Component не добавляется)

package com.xyz.someapp;

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

@Aspect
public class SystemArchitecture {

  /**
   * A join point is in the web layer if the method is defined
   * in a type in the com.xyz.someapp.web package or any sub-package
   * under that.
   */
  @Pointcut("within(com.xyz.someapp.web..*)")
  public void inWebLayer() {}

  /**
   * A join point is in the service layer if the method is defined
   * in a type in the com.xyz.someapp.service package or any sub-package
   * under that.
   */
  @Pointcut("within(com.xyz.someapp.service..*)")
  public void inServiceLayer() {}

  /**
   * A join point is in the data access layer if the method is defined
   * in a type in the com.xyz.someapp.dao package or any sub-package
   * under that.
   */
  @Pointcut("within(com.xyz.someapp.dao..*)")
  public void inDataAccessLayer() {}

  /**
   * A business service is the execution of any method defined on a service
   * interface. This definition assumes that interfaces are placed in the
   * "service" package, and that implementation types are in sub-packages.
   * 
   * If you group service interfaces by functional area (for example, 
   * in packages com.xyz.someapp.abc.service and com.xyz.def.service) then
   * the pointcut expression "execution(* com.xyz.someapp..service.*.*(..))"
   * could be used instead.
   */
  @Pointcut("execution(* com.xyz.someapp.service.*.*(..))")
  public void businessService() {}
  
  /**
   * A data access operation is the execution of any method defined on a 
   * dao interface. This definition assumes that interfaces are placed in the
   * "dao" package, and that implementation types are in sub-packages.
   */
  @Pointcut("execution(* com.xyz.someapp.dao.*.*(..))")
  public void dataAccessOperation() {}

}

Вышеупомянутая SystemArchitecture хорошо понятна.Aspect определяет набор Pointcut, на который затем можно напрямую ссылаться везде, где нужны Pointcut.

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

3. Настроить совет

Обратите внимание, что в реальном процессе разработки класс Aspect должен следовать принципу единой ответственности и не записывать все конфигурации Advice в один класс Aspect.

Это для удобства демонстрации, поэтому написано слитно.

Сначала перейдите непосредственно к примеру кода, который содержит несколько методов конфигурации Advice (упомянутых выше в разделе о понятиях существительных).

/**
 * 注:实际开发过程当中,Advice应遵循单一职责,不应混在一起
 *
 * @author Richard_yyf
 * @version 1.0 2019/10/28
 */
@Aspect
@Component
public class GlobalAopAdvice {

    @Before("ric.study.demo.aop.SystemArchitecture.dataAccessOperation()")
    public void doAccessCheck() {
        // ... 实现代码
    }

    // 实际使用过程当中 可以像这样把Advice 和 Pointcut 合在一起,直接在Advice上面定义切入点
    @Before("execution(* ric.study.demo.dao.*.*(..))")
    public void doAccessCheck() {
        // ... 实现代码
    }

    // 在方法
    @AfterReturning("ric.study.demo.aop.SystemArchitecture.dataAccessOperation()")
    public void doAccessCheck() {
        // ... 实现代码
    }

    // returnVal 就是相应方法的返回值
    @AfterReturning(
        pointcut="ric.study.demo.aop.SystemArchitecture.dataAccessOperation()",
        returning="returnVal")
    public void doAccessCheck(Object returnVal) {
        //  ... 实现代码
    }

    // 异常返回的时候
    @AfterThrowing("ric.study.demo.aop.SystemArchitecture.dataAccessOperation()")
    public void doRecoveryActions() {
        // ... 实现代码
    }

    // 注意理解它和 @AfterReturning 之间的区别,这里会拦截正常返回和异常的情况
    @After("ric.study.demo.aop.SystemArchitecture.dataAccessOperation()")
    public void doReleaseLock() {
        // 通常就像 finally 块一样使用,用来释放资源。
        // 无论正常返回还是异常退出,都会被拦截到
    }

    // 这种最灵活,既能做 @Before 的事情,也可以做 @AfterReturning 的事情
    @Around("ric.study.demo.aop.SystemArchitecture.businessService()")
    public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable {
       	//  target 方法执行前... 实现代码
        Object retVal = pjp.proceed();
        //  target 方法执行后... 实现代码
        return retVal;
    }
}

В некоторых сценариях мы хотим получить входные параметры метода, когда @Before, например, для записи каких-то логов, мы можем пройтиorg.aspectj.lang.JoinPointреализовать. вышесказанноеProceedingJoinPointявляется его подклассом.

@Before("...")
public void logArgs(JoinPoint joinPoint) {
    System.out.println("方法执行前,打印入参:" + Arrays.toString(joinPoint.getArgs()));
}

Чтобы дать другой соответствующий метод, возвращаемый параметр печати:

@AfterReturning( pointcut="...", returning="returnVal")
public void logReturnVal(Object returnVal) {
    System.out.println("方法执行后,打印返参:" + returnVal));
}

Быстрая демонстрация

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

1. Напишите целевой класс

package ric.study.demo.aop.svc;

public interface TestSvc {

    void process();
}

@Service("testSvc")
public class TestSvcImpl implements TestSvc {
    @Override
    public void process() {
        System.out.println("test svc is working");
    }
}

public interface DateSvc {

    void printDate(Date date);
}

@Service("dateSvc")
public class DateSvcImpl implements DateSvc {

    @Override
    public void printDate(Date date) {
        System.out.println(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(date));
    }
}

2. Настройте Pointcut

@Aspect
@Component
public class PointCutConfig {
    @Pointcut("within(ric.study.demo.aop.svc..*)")
    public void inSvcLayer() {}   
}

3. Настроить совет

/**
 * @author Richard_yyf
 * @version 1.0 2019/10/29
 */
@Component
@Aspect
public class ServiceLogAspect {

    // 拦截,打印日志,并且通过JoinPoint 获取方法参数
    @Before("ric.study.demo.aop.PointCutConfig.inSvcLayer()")
    public void logBeforeSvc(JoinPoint joinPoint) {
        System.out.println("在service 方法执行前 打印第 1 次日志");
        System.out.println("拦截的service 方法的方法签名: " + joinPoint.getSignature());
        System.out.println("拦截的service 方法的方法入参: " + Arrays.toString(joinPoint.getArgs()));
    }

    // 这里是Advice和Pointcut 合在一起配置的方式
    @Before("within(ric.study.demo.aop.svc..*)")
    public void logBeforeSvc2() {
        System.out.println("在service的方法执行前 打印第 2 次日志");
    }
}

4. Включите@AspectJАннотировать метод конфигурации и начать

Здесь для удобства схемы класс конфигурации и класс запуска написаны вместе.

/**
 * @author Richard_yyf
 * @version 1.0 2019/10/28
 */
@Configuration
@EnableAspectJAutoProxy
@ComponentScan("ric.study.demo.aop")
public class Boostrap {

    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Boostrap.class);
        TestSvc svc = (TestSvc) context.getBean("testSvc");
        svc.process();
        System.out.println("==================");
        DateSvc dateSvc = (DateSvc) context.getBean("dateSvc");
        dateSvc.printDate(new Date());
    }
}

5. Выход

在service 方法执行前 打印第 1 次日志
拦截的service 方法的方法签名: void ric.study.demo.aop.svc.TestSvcImpl.process()
拦截的service 方法的方法入参: []
在service的方法执行前 打印第 2 次日志
test svc is working
==================
在service 方法执行前 打印第 1 次日志
拦截的service 方法的方法签名: void ric.study.demo.aop.svc.DateSvcImpl.printDate(Date)
拦截的service 方法的方法入参: [Mon Nov 04 18:11:34 CST 2019]
在service的方法执行前 打印第 2 次日志
2019-11-04 18:11:34

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

Как упоминалось ранее, Spring AOP будет использовать динамический прокси JDK для создания прокси-классов, когда у целевого класса есть интерфейс реализации.Давайте посмотрим на приведенную выше демонстрацию,

image.png

Что, если мы захотим заставить Cglib реализовать интерфейс вне зависимости от того, реализован ли он?

Spring предоставляет нам соответствующий метод настройки, то естьproxy-target-class.

注解方式:
//@EnableAspectJAutoProxy(proxyTargetClass = true) // 这样子就是默认使用CGLIB
XML方式:
<aop:config proxy-target-class="true">

После изменения,

image.png

резюме

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

Согласно привычкам автора в письменном виде, эта статья представляет собой главу предварительного изучения главы анализа исходного кода. В следующей главе мы будем использовать метод аннотации в качестве записи, чтобы представить дизайн исходного кода Spring AOP и интерпретировать соответствующий исходный код основного сигнала (после прочтения всего анализа исходного кода, мы узнаем по аналогии с другими методами, потому что Принципы одинаковы).

Если вам интересно, вы можете обратиться к разделу [Предисловие] и взглянуть на интеллект-карту.

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