AOP笔记

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,我们可以把分散在应用各处的行为放入可重用的模块中。通过显示地声明在何处如何应用该行为,可以有效的减少代码冗余,让我们的类关注自身的主要功能。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 195,783评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,360评论 2 373
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 142,942评论 0 325
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,507评论 1 267
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,324评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,299评论 1 273
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,685评论 3 386
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,358评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,652评论 1 293
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,704评论 2 312
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,465评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,318评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,711评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,991评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,265评论 1 251
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,661评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,864评论 2 335

推荐阅读更多精彩内容