Spring Aop切面

Spring的Aop切面 散布于应用中多处的功能被称为横切关注点,且从概念上与应用的业务逻辑分离,将横切关注点与业务逻辑分离就是面向切面编程要解决的问题 妹

AOP概述

一个被划分为模块的应用 截图 如果要重用通用功能的话,最常见是继承 或 委托,然而两种方式都可能导致系统复杂化
切面提供了取代这些的另一种方案,更清晰简洁,通过声明的方式定义功能以何种方式在何处应用,无需修改受影响的类。横切关注点可以被模块化为特殊类,这些类就被称为切面。

  • 术语
    截图 通知(advice)
    AOP中,切面的工作称为通知,定义了切面是什么以及何时使用(调用前?调用后?抛出异常?)
    分为:前置通知(before) 后置通知(after) 返回通知(after-returning) 异常通知(after-throwing) 环绕通知(around)
    切点(pointcut)
    一个切点不需要通知应用的所有连接点,切点有助于缩小切面所通知的连接点范围,通常使用明确类、方法,或者利用正则定义锁匹配的类、方法指定切点
    连接点(join point)
    连接点是应用执行过程中能够插入切面的一个点,可以是调用方法时,抛出异常,或者修改一个字段时;切面代码可以利用这些点插入到应用的正常流程,添加新行为
    切面(aspect)
    是通知和切点的结合,包括何时以及何处完成功能
    引入(introduction)
    引入允许我们向现有类添加新方法或属性,在不修改现有类的情况下,具有新行为和状态
    织入(weaving)
    是把切面应用到目标对象并创建新代理对象的过程;在目标对象生命周期里有多个点可以织入:编译期(需要特殊的编译器,Aspectj是已这种方式织入)、类加载期(需要特殊的类加载器,Aspectj支持这种方式织入)、运行期(Spring aop是已这种方式织入)

  • Spring对AOP支持
    1.基于代理的经典Spring AOP
    2.纯POJO切面
    3.@AspectJ注解驱动的切面
    4.注入式AspectJ切面(适用于Spring各版本)
    关于Spring在运行时通知对象 截图

  • spring只支持方法级别的连接点
    keywords:通知,切点,连接点,切面,引入,织入

通过切点选择连接点

  • 编写切点
    package concert;
    public interface Performance {
      public void perform();
    }
    

    使用AspectJ切点表达式 截图 表达式*开始表示不关心方法返回值,如果需要匹配切点仅匹配concert包,可使用within()指示器 截图

  • 切点中选择bean
    执行beanPerformance.perform应用通知,但是bean id限定为woodstock
    execution(* concert.Performance.perform() and bean('woodstock'))
    

使用注解创建切面

@Component
public class TestPerformance implements Performance{
    public void perform() {
        System.out.println("TestPerformance perform");
    }
}
// 通过@Aspect进行标注该类是一个切面(通知类)
// AspectJ提供了5个注解定义通知
// @After @AfterReturning @AfterThrowing @Around @Before
@Aspect
public class Audience {
	// 通过@Pointcut可以在切面内定义可重用的切点
    @Pointcut("execution(* demo.Performance.perform(..))")
    public void performance(){}
    @Before("performance()")
    public void silenceCellPhones(){
        System.out.println("silenceCellPhones");
    }
}

即使使用AspectJ注解,但是该类也不会被视为切面,所以需要启用AspectJ注解

javaconfig:
@Configuration
@ComponentScan
// 启用自动代理功能
@EnableAspectJAutoProxy
public class AopConfig {
	// 声明切面Bean
    @Bean
    public Audience audience(){
        return new Audience();
    }
}
xmlconfig:
<aop:aspectj-autoproxy/>
<bean class="concert.Audience"/>
``
遇到的问题:<br/>
1.AspectJ使用maven的artifactId应该是aspectjweaver<br/>
2.aspectjweaver的版本需要与当前使用jdk版本一致,即jdk1.8对应的版本号是1.8.x,否则报错<br/>

- 创建环绕通知<br/>
@Around("execution(* springdemo.CD.Performance.perform(..))")
// ProceedingJoinPoint这个对象是必须要有的,因为要在通知中通过它来调用被通知的方法
public void watchPerformance(ProceedingJoinPoint jp){
    try{
        System.out.println("before...");
        // 当要将控制权交给被通知的方法时,它需要调用ProceedingJoinPoint的proceed()方法
        // 如果不调用proceed方法,方法就会被阻塞
        jp.proceed();
        System.out.println("after...");
    }catch (Throwable e){
        System.out.println("Exception...");
    }
} ```
  • 处理通知中的参数
    如果被通知的方法时有参数的,并且这些参数需要关注,那么久切片就需要访问这些参数 截图
    @Aspect
    public class TrackCounter {
      private Map<Integer, Integer> trackCounts = new HashMap<Integer, Integer>();
      // args表明传给getPlayCount的int参数也会传给通知
      @Pointcut("execution(* springdemo.CD.CompactDisc.playTrack(int)) && args(trackNumber)")
      public void trackPlayed(int trackNumber){}
    
      @Before("trackPlayed(trackNumber)")
      public void countTrack(int trackNumber){
          int currentCount = getPlayCount(trackNumber);
          currentCount ++;
          trackCounts.put(trackNumber, currentCount);
      }
      public int getPlayCount(int trackNumber){
          return trackCounts.containsKey(trackNumber) ? trackCounts.get(trackNumber) : 0;
      }
    }
    
  • 通过注解引入新功能
    由于java不是动态语言,所以不能像Python一样为对象或类动态添加方法,但是可以通过切片为spring bean添加新方法
    相当于切片实现了包装bean相同接口的代理,如果代理能暴露新接口
    截图
    接口
    public interface Encoreable {
      void performEncore();
    }
    实现类
    public class EncoreableImpl implements Encoreable {
      public void performEncore() {
          System.out.println("performEncore");
      }
    }
    切面
    @Aspect
    public class EncoreableConfig {
      // 通过@DeclareParents注解,将Encoreable接口引入到Performance bean中
      @DeclareParents(value="springdemo.CD.Performance+", defaultImpl = EncoreableImpl.class)
      public static Encoreable encoreable;
    }
    测试
    @Test
    public void testPerform(){
      ((Encoreable) performance).performEncore();
      performance.perform();
    }
    

    @DeclareParents注解由三部分组成: value属性指定了哪种类型的bean要引入该接口。在本例中,也就是所有实现Performance的类型。(标记符后面的加号表示是Performance的所有子类型,而不是Performance本身。)
    defaultImpl属性指定了为引入功能提供实现的类。在这里,我们指定的是DefaultEncoreable提供实现。
    @DeclareParents注解所标注的静态属性指明了要引入了接口。在这里,我们所引入的是Encoreable接口

keywords:@Aspect,execution,@After,@AfterReturning,@AfterThrowing,@Around,@Before,@Pointcut,@DeclareParents

在xml中声明切面

如果要声明切面,但是又不能为类添加注解时,必须转向xml配置 配置方式:

<aop:advisor>定义AOP通知器
<aop:after>定义AOP后置通知(不管被通知的方法是否执行成功)
<aop:afterreturning>定义AOP返回通知
<aop:afterthrowing>定义AOP异常通知
<aop:around>定义AOP环绕通知
<aop:aspect>定义一个切面
<aop:aspectjautoproxy>启用@AspectJ注解驱动的切面
<aop:before>定义一个AOP前置通知
<aop:config>顶层的AOP配置元素。大多数的<aop:*>元素必须包含在<aop:config>元素内
<aop:declareparents>以透明的方式为被通知的对象引入额外的接口
<aop:pointcut>定义一个切点
    <bean id="audience" class="springdemo.CD.Audience"/>
    <bean id="testPerformance" class="springdemo.CD.TestPerformance"/>
    <aop:config>
        <aop:aspect ref="audience">
            <aop:pointcut id="perform" expression="execution(* demo.Performance.perform(..))"/>
            <!--<aop:before method="silenceCellPhones" pointcut-ref="perform"/>-->
            <!-- 环绕 -->
            <aop:around method="watchPerformance" pointcut-ref="perform"/>
            <!-- 为被通知类引入新功能 -->
            <aop:declare-parents types-matching="demo.Performance+" implement-interface="demo.Encoreable" default-impl="demo.DefaultEncoreable"/>
        </aop:aspect>
    </aop:config>