AOP笔记

作者: 沥人土土 | 来源:发表于2017-04-13 16:48 被阅读0次

    Spring提供了4种类型的AOP支持:

    • 基于代理的经典Spring AOP
    • 纯POJO切面
    • @AspectJ注解驱动的切面
    • 注入式AspectJ切面(适用于Spring各版本)

    Spring仅支持AspectJ切点指示器(pointcut designator)的一个子集:

    AspectJ指示器 描述
    arg() 限制连接点匹配参数为指定类型的执行方法
    @args() 限制连接点匹配参数由指定注解标注的执行方法
    execution() 用于匹配是连接点的执行方法
    this() 限制连接点匹配AOP代理的bean引用为指定类型的类
    target 限制连接点匹配目标对象为指定类型的类
    @target() 限制连接点匹配特定的执行对象,这些对象对应的类要具有指定类型的注解
    within() 限制连接点匹配指定的类型
    @within() 限制连接点匹配指定注解所标注的类型(当时用Spring AOP时,方法定义在由指定的注解所标注的类里)
    @annotation 限定匹配带有指定注解的连接点

    在Spring中使用AspectJ其他指示器时,将会抛出IllegalArgumentException异常。

    切点表达式(以剧院为例子):

    execution(* concert.Performance.perform(..))
    *:返回任意类型
    concert.Performance:方法所属的类
    .perform(..):方法

    仅匹配concert包:
    execution(* concert.Performance.perform(..)) && within(concert.*)
    可使用关系符(如&&, ||等)连接, "!"标识not操作。

    除上表所列的指示器,Spring还引入了一个新的bean()指示器,它允许我们在切点表达式中使用bean的id来标识bean:
    execution(* concert.Performance.perform(..)) && bean('xiyangyang')
    指定对id为'xiyangyang'的bean进行操作;
    execution(* concert.Performance.perform(..)) && !bean('xiyangyang')
    对所有id不为'xiyangyang'的bean操作。

    使用注解创建切面

    Spring的AspectJ自动代理仅仅使用@AspectJ作为创建切面的指导,切面依然是基于代理的。

    创建环绕通知

    环绕通知(@Around)是最为强大的通知类型,它能使你编写的逻辑将被通知的目标方法完全包装起来,就像在一个通知方法中同时编写前置通知和后置通知。
    例:

    @Aspect
    public class Audience{
      @Pointcut("execution(** concert.Performance.perform(..))")
      public void performance(){}
    
      @Around("performance()") //环绕通知方法
      public void watchPerformance(ProceedingJoinPoint jp){
        try{
          System.out.println("Silencing cell phones");
          System.out.println("Taking seats");
          jp.proceed();
          System.out.println("CLAP CLAP CLAP!!");
        }catch(Thorwable e){
          System.out.println("Demanding a refund");
        }
      }
    }
    

    As we can see,@Around 接受ProceedingJoinPoint作为参数。这个对象是必须要有的,我们需要在通知中通过它调用被通知的方法。通知方法中可以做任何的事情,当要将控制权交给被通知的方法时,他需要调用ProceedingJointPoint的proceed()方法。这个方法必须被调用,否则通知会阻塞对被通知方法的调用。

    处理通知中的参数

    假如我们想记录某方法执行的次数,有两种方法,一是直接在每次调用时记录使用次数,然而记录使用次数和方法本身是不同的关注点,因此不应该属于方法。二就是使用切面。
    例:

    @Aspect
    public class TrackCounter{
      private Map<Integer, Integer> trackCounts = 
          new HashMap<Integer, Integer>();
    
    @Pointcut(
        "execution(* soundsystem.CompactDisc.playTrack(int)) " + //通知playTrack()方法
        "&& args(trackNumber)")
    public void trackPlayed(int trackNumber){ }
    
    @Before("trackPlayed(trackNumber)") //播放前,为该磁道计数
    public void countTrack(int trackNumber){
      int currentCount = getPlayCount(trackNumber);
      trackCounts.put(trackNumber, currentCount +1);
    }
    
    public int getPlayCount(int trackNumber){
      return trackCounts.contaisKey(trackNumber)
            ? trackCounts.get(trackNumber) : 0;
    }
    
    }
    

    其中,
    "execution(* soundsystem.CompactDisc.playTrack(int)) " + "&& args(trackNumber)"
    * : 返回任意类型
    soundsystem.CompactDisc:方法所属的类型
    .playTrack:方法
    int:接受int类型的参数
    args(trackNumber):指定参数
    这里需要关注的是args(trackNumber)限定符。它表明传给playTrack()方法的int类型参数也会传递到通知中去。参数的名称trackNumber也与窃电方法签名中的参数相匹配。
    现在把TrackCounter和要记录的类定义为bean,并启动AspectJ代理,就可以记录播放次数了。

    知道如何使用切面包装方法后,可以看看如何通过编写切面,为被通知的对象引入全新的功能。

    通过注解引入新功能

    如果使用代理暴露新接口,切面所通知的bean看起来像是实现了新的接口,即便底层实现类并没有实现这些接口也无所谓。
    当引入接口的方法被调用时,代理会把此调用委托给实现了新接口的某个其他对象。实际上就是一个bean的实现被拆分到了多个类中。
    举个栗子,为Performance实现引入Encoreable接口:

    public interface Encoreable{
      void performEncore();
    }
    

    首先我们创建一个新切面:

    @Aspect
    public class EncoreableIntroducer{
      @DeclareParents(value="concert.Performance+",
                     defaultImpl=DefaultEncoreable.class)
      public static Encoreable encoreable;
    }
    

    与之前的切面不同,EncoreableIntroducer通过的是@DeclareParants注解将Encoreable接口引入到Performance bean中。和其他切面一样,我们需要在Spring中将EncoreableIntroducer声明为一个bean。
    Spring的自动代理机制会获取到此声明。注解和自动代理提供了一种很便利的方式来创建切面。简单且涉及极少Spring配置。但面向注解的切面声明有一个明显劣势:必须能够为通知类添加注解。这说明必须有源码。如果不想将AspectJ放到代码中,可在Spring XML中配置。因为工作中不用xml,我这里就不说啦。

    注入AspectJ切面

    如果在执行通知时,切面依赖于一个或多个类,我们可以在切面内部实例化这些协作对象。更好的方法是借助Spring的依赖注入把bean装配进AspectJ切面中。

    总结一下

    AOP是OOP的一个强大补充。通过AspectJ,我们可以把分散在应用各处的行为放入可重用的模块中。通过显示地声明在何处如何应用该行为,可以有效的减少代码冗余,让我们的类关注自身的主要功能。

    相关文章

      网友评论

          本文标题:AOP笔记

          本文链接:https://www.haomeiwen.com/subject/fydmattx.html