Spring Aop简介

一、AOP简介

  • 面向切面编程(aspect-oriented programming, AOP) 允许你把遍布应用各处的功能分离出来形成可重用的组件
  • AOP能够确保POJO的简单性

1.1 相关概念

  • 横切关注点(cross-cutting concern): 指在软件开发中,散布于应用中多处的功能
  • 切面(aspect): 横切关注点可以被模块化为特殊的类, 这些类被称为切面

1.2 AOP术语

  • 通知包含了需要用于多个应用对象的横切行为;连接点是程序执行过程中能够应用通知的所有点;切点定义了通知被应用的具体位置(在哪些连接点)

1.2.1 通知(Advice)

  • 定义:
    • 切面的工作被称为通知, 通知定义了切面是什么以及何时使用
  • 类型:
    • 前置通知(Before):在目标方法被调用之前调用通知功能;
    • 后置通知(After):在目标方法完成之后调用通知, 此时不会关心方法的输出是什么;
    • 返回通知(After-returning):在目标方法成功执行之后调用通知;
    • 异常通知(After-throwing):在目标方法抛出异常后调用通知;
    • 环绕通知(Around):通知包裹了被通知的方法, 在被通知的方法调用之前和调用之后执行自定义的行为。

1.2.2 连接点(Join point)

  • 定义:
    • 在应用执行过程中能够插入切面的一个点

1.2.3 切点(Pointcut)

  • 如果说通知定义了切面的“什么”和“何时”的话, 那么切点就定义了“何处”
  • 切点的定义会匹配通知所要织入的一个或多个连接点

1.2.4 切面(Aspect)

  • 切面是通知和切点的结合。 通知和切点共同定义了切面的全部内容——它是什么, 在何时和何处完成其功能。

1.2.5 引入(Introduction)

  • 引入允许我们向现有的类添加新方法或属性

1.2.6 织入(Weaving)

  • 定义:
    • 织入是把切面应用到目标对象并创建新的代理对象的过程
  • 织入时机:
    • 编译期: 切面在目标类编译时被织入。 这种方式需要特殊的编译器。 AspectJ的织入编译器就是以这种方式织入切面的。
    • 类加载期: 切面在目标类加载到JVM时被织入。 这种方式需要特殊的类加载器(ClassLoader) , 它可以在目标类被引入应用之前增强该目标类的字节码。AspectJ 5的加载时织入(load-time weaving, LTW) 就支持以这种方式织入切面。
    • 运行期: 切面在应用运行的某个时刻被织入。 一般情况下, 在织入切面时, AOP容器会为目标对象动态地创建一个代理对象。 Spring AOP就是以这种方式织入切面的

二、Spring AOP

  • 4种类型的AOP支持:
    • 基于代理的经典Spring AOP;
    • 纯POJO切面;
    • @AspectJ注解驱动的切面;
    • 注入式AspectJ切面(适用于Spring各版本)
  • 说明:
    • 前三种都是Spring AOP实现的变体, Spring AOP构建在 动态代理 基础之上, 因此, Spring对AOP的支持局限于方法拦截
    • 借助Spring的aop命名空间, 我们可以将纯POJO转换为切面, 需要XML配置
    • 注解驱动的AOP, 依然是Spring基于代理的AOP, 这种AOP风格的好处在于能够不使用XML来完成功能

2.1 通过切点来选择连接点

  • Spring仅支持AspectJ切点指示器(pointcut designator)的一个子集
  • 在Spring中尝试使用AspectJ其他指示器时, 将会抛出IllegalArgument-Exception异常

2.1.1 编写切点

  • 只有execution指示器是实际执行匹配的, 而其他的指示器都是用来限制匹配的
  • 在Spring的XML配置里面描述切点时, 我们可以使用and来代替“&&”。 同样, or和not可以分别用来代替“||”和“!”
  • 示例:
    # execution在方法执行时触发, *表示返回任意类型, 两点表示任意参数
    # within指示器限定切点范围为concert包下任意类的方法被调用
    execution(* concert.Performance.perform(..)) and within(concert.*)

2.1.2 在切点中选择bean

  • bean()使用bean ID或bean名称作为参数来限制切点只匹配特定的bean
  • 示例: 在执行Performance的perform()方法时应用通知, 但限定为除了ID为woodstock的bean
    execution(* concert.Performance.perform()) and !bean('woodstock')

2.2 使用注解创建切面

  • 使用注解来创建切面是AspectJ 5所引入的关键特性

2.2.1 定义切面

  • AspectJ提供了五个注解来定义通知:
    • @After: 通知方法会在目标方法返回或抛出异常后调用
    • @AfterReturning
    • @AfterThrowing
    • @Around: 通知方法会将目标方法封装起来
    • @Before
  • @Pointcut注解能够在一个@AspectJ切面内定义可重用的切点
  • 示例:

    /**
    * 除了注解和没有实际操作的performance()方法, Audience类依然是一个POJO, 与其他的Java类并没有什么区别
    */
    @Aspect //该注解表明Audience不仅仅是一个POJO,还是一个切面
    public class Audience {
    
    //定义命名的切点
    @Pointcut("execution(** concert.Performance.perform(..) )")
    public void performance() {}  //该方法本身只是一个标识, 供@Pointcut注解依附
    
    @Before("performance()")
    public void silenceCellPhones() {
    System.out.println("Silence cell phones");
    }
    
    @Before("performance()")
    public void takeSeats() {
    System.out.println("Taking seats");
    }
    
    @AfterReturning("performance()")
    public void applause() {
    System.out.println("CLAP CLAP CLAP!!!");
    }
    
    @AfterThrowing("performance()")
    public void demandRefund() {
    System.out.println("Demanding a refund");
    }
    }
  • 装配为Spring中的bean, 启用自动代理
    • JavaConfig中使用@EnableAspectJAutoProxy注解
    • XML装配使用Spring aop命名空间中的元素
      @Configuration
      @EnableAspectJAutoProxy //启用AspectJ注解的自动代理
      @ComponentScan
      public class ConcertConfig {
      @Bean
      public Audience audience() {
      return new Audience();
      }
      }

2.2.2 创建环绕通知

  • 实际上就像在一个通知方法中同时编写前置通知和后置通知
  • 示例

    /**
    * 你可以不调用proceed()方法, 从而阻塞对被通知方法的访问
    * 与之类似,你也可以在通知中对它进行多次调用, 被通知方法失败后, 进行重复尝试
    */
    @Aspect //该注解表明Audience不仅仅是一个POJO,还是一个切面
    public class Audience {
    
    //定义命名的切点
    @Pointcut("execution(** concert.Performance.perform(..) )")
    public void performance() {}  //该方法本身只是一个标识, 供@Pointcut注解依附
    
    @Around("performance()")
    public void watchPerformance(ProceedingJoinPoint jp) {
    try {
      System.out.println("Silence cell phones");
      System.out.println("Taking seats");
      jp.proceed(); //在通知中通过它来调用被通知的方法
      System.out.println("CLAP CLAP CLAP!!!");
    } catch (Throwable e) {
      System.out.println("Demanding a refund");
    }
    }
    }

2.2.3 处理通知中的参数

  • 切点表达式中使用args(trackNumber)限定符来传递参数

2.2.4 通过注解引入新功能

  • @DeclareParents注解组成
    • value属性指定了哪种类型的bean要引入该接口
    • defaultImpl属性指定了为引入功能提供实现的类
    • @DeclareParents注解所标注的静态属性指明了要引入了接口
  • 示例
    @Aspect
    public class EncoreableIntroducer {
    //标记符后面的"+"表示是Performance的所有子类型, 而不是Performance本身
    @DeclareParents(value="concert.Performance+", defaultImpl=DefaultEncoreable.class)
    public static Encoreable encoreable;
    }

2.3 在XML中声明切面

  • 说明:
    • aop命名空间的其他元素能够让我们直接在Spring配置中声明切面, 而不需要使用注解
  • 配置元素
    • : 定义AOP通知器
    • : 定义AOP后置通知(不管被通知的方法是否执行成功)
    • : 定义AOP返回通知
    • : 定义AOP异常通知
    • : 定义AOP环绕通知
    • : 定义一个切面
    • : 启用`@AspectJ`注解驱动的切面
    • : 定义一个AOP前置通知
    • : 顶层的AOP配置元素。 大多数的``元素必须包含在元素内
    • 以透明的方式为被通知的对象引入额外的接口
    • 定义一个切点

2.3.1 声明前置和后置通知

  • 说明:
    • 大多数的AOP配置元素必须在元素的上下文内使用
  • 示例: 将没有注解的Audience类转换为切面
    <aop:config>
    <aop:aspect ref="audience">
    <aop:before
      pointcut="execution(** concert.Performance.perform(..) )"
      method="silenceCellPhones"/>
    <aop:before
      pointcut="execution(** concert.Performance.perform(..) )"
      method="takeSeats"/>
    <aop:after-returning
      pointcut="execution(** concert.Performance.perform(..) )"
      method="applause"/>
    <aop:after-throwing
      pointcut="execution(** concert.Performance.perform(..) )"
      method="demandRefund"/>
    </aop:aspect>
    </aop:config>

2.3.2 声明环绕通知

  • 说明:
    • 使用元素
  • 示例:
    <aop:config>
    <aop:aspect ref="audience">
    <!-- 定义pointcut -->
    <aop:pointcut
      id="performance"
      expression="execution(** concert.Performance.perform(..) )" />
    <aop:around
      pointcut-ref="performance"
      method="watchPerformance"/>
    </aop:aspect>
    </aop:config>

2.3.3 为通知传递参数

2.3.4 通过切面引入新功能

  • 使用Spring aop命名空间中的元素
    <aop:aspect>
    <aop:declare-parents
    types-matching="concert.Performance+"
    implement-interface="concert.Encoreable"
    default-impl="concert.DefaultEncoreable"
    />
    </aop:aspect>
  • 使用delegate-ref属性来标识: delegate-ref属性引用了一个Spring bean作为引入的委托。 这需要在Spring上下文中存在一个ID为encoreableDelegate的bean。
    <aop:aspect>
    <aop:declare-parents
    types-matching="concert.Performance+"
    implement-interface="concert.Encoreable"
    delegate-ref="encoreableDelegate"
    />
    </aop:aspect>
    <!-- Spring bean配置 -->
    <bean id="encoreableDelegate"
    class="concert.DefaultEncoreable" />

2.4 注入AspectJ切面

  • AspectJ提供了Spring AOP所不能支持的许多类型的切点

三、参考

3.1 博客

3.2 书籍

  • [Spring实战]

标签: none

添加新评论