spring详解(十)「AOP」

当相同的切面里的两个增强处理需要在相同的连接点被织入时,Spring AOP将以随机的方式来织入这两个增强处理,没有办法指定他们的顺序,如果一定要它们以特定的顺序被织入,则可以考虑把它们压缩到一个增强处理中,或者是把它们分别放在不同的切面,在通过切面的优先等级来排序

如果只要访问目标方法的参数,Spring还提供了一个更加简单的方式:我们可以在程序中使用args来绑定目标方法的参数,如果args表达是中指定一个或多个参数,则该切入点只匹配具有对应形参的方法,且目标方法的参数值将被出入增强处理方法。
如:

@Aspect
publicclass AccessArgsAspect {
    @AfterReturning(pointcut ="execution(* cn.hb.spring.service1.*.*(..))"
    +"&&args(food,time)", returning ="retValue")
    publicvoid access(String food, Date time, Object retValue) {
        System.out.println("目标方法中的String参数" + food);
        System.out.println("目标方法的time参数" + time);
        System.out.println("模拟日志记录功能......");
        System.out.println("目标参数的返回值:"+retValue);
    }
}
@Component
publicclass Chinese3implements Person2 {
    @Override
    public String sayHello(String word) {
        return word;
    }
    publicvoid eat(String food, Date time) {
        System.out.println("我正在吃:" + food+",现在的时间是:" + time);
    }
}

为什么目标方法的返回值是null,因为该切入点只匹配 public void eat(String food, Date time)方法。
从上面可以得出,使用args表达式有如下两个作用:
--提供更简单的方式访问目标方法的参数;
--可用于对切入表达式增加额外限制。
除此之外,使用args表达式时还可以使用如下形式:args(name,age,..),这表明增强处理方法中可通过name,age来访问目标方法的参数,上面表达式括号中的2点,它表示可匹配更多的参数,如:
public void doSomething(String name,int age)
这意味着只要目标方法第一个参数是String类型,第二个参数是int则该方法就可以匹配该切入点。


定义切入点

正如前面的FourAdviceTest程序中看到的,这个切面类定义了4个增强处理,这4个增强处理分别指定了相同的切入点表达式,这种做法显然不符合软件设计的原则:我们将那个切入点表达式重复了4次,如果需要该这个切入点,那么就要修改4处。

Spring AOP只支持以Spring Bean的方法执行组作为连接点.

例如:

@Pointcut("execution(* transfer(..))")
private void anyOldTransfer(){}

一旦采用上面的代码片段定义了名为anyOldTrandser的切入点之后,程序就可以重复使用该切入点了,甚至可以在其他切面类、其他包的切面类里使用该切入点,不过这取决于该方法签名前的访问修饰符。


切入点指示符

正如前面的execution就是一个切入点指示符,Spring AOP仅仅支持部分AspectJ切入点指示符,不仅如此Spring AOP只支持使用方法调用作为连接点,所以Spring AOP的切入点指示符仅匹配方法执行的连接点

Spring AOP一共支持如下几种切入点指示符:

execution:用于匹配执行方法的连接点,是Spring AOP最主要的切入点指示符,execution表达式的格式如下:

execution(modifies-pattern?  ret-type-pattern  declaring-type-parttern?  name--pattern(parm-pattern)  throws-pattern?)

以上打了问号的都可以省略。

上面格式中的execution是不变的,用于作为execution表达式的开头,整个表示式各个部分的解释为:

modifies-pattern:指定方法的修饰符,支持通配符,该部分可以省略。

ret-type-pattern:指定方法的返回值类型,支持通配符,可以使用“*”通配符来匹配所有返回值类型。

declaring-type-parttern:指定方法所属的类,支持通配符,该部分可以省略。

name--pattern:指定匹配指定方法名,支持通配符,可以使用“*”通配符来匹配所有方法。

parm-pattern:指定方法声明中的形参列表,支持两个通配符:“”、“..”,其中表示一个任意类型的参数,而“..”表示零个或多个任意类型的参数。

throws-pattern:指定方法声明抛出的异常,支持通配符,该部分可以省略。

例如下面几个execution表达式:

//匹配任意public方法的执行。
execution(public * * (..))
//匹配任意方法名以set开始的方法。
execution(* set* (..))
//匹配AccountService里定义的任意方法的执行。
execution(* org.hb.AccountService.* (..))
//匹配Service包中任意类的任意方法的执行。
execution(* org.hb.service.*.*(..))

within:限定匹配特定类型的连接点,当使用Spring AOP的时候,只能匹配方法执行的连接点,例如下面几个within表达式:

//在Service包中的任意连接点。
within(* org,hb.service.*)
//在Service包或者子包的任意连接点
within(* org.hb.service..*)

this:用于限定AOP代理必须指定类型的实例,用于匹配该对象的所有连接点。当使用Spring AOP的时候,只能匹配方法执行的连接点
例如:

//匹配实现了AccountService接口的代理对象的所有连接点
this(org.hb.service.AccountService)

target:用于限定目标对象必须是指定类型的实例,用于匹配该对象的所有连接点,当使用Spring AOP只匹配执行方法的连接点。例如:

//匹配实现了AccountService接口的目标对象的所有连接点
target(org.hb.service.AccountService)

args:用于对连接点的参数类型进行限制,要求参数类型是指定类型的实例,例如:

//匹配只接受一个参数,且传入的参数类型是Serializable的所有连接点
args(java.io.Serializable)

注意:该例中给出的切入点表达式与execution(* *(java.io.Serializable))不同:args版本只匹配动态运行时传入参数值是Serializable类型的情况,而execution版本只匹配方法签名只包含一个Serializable类型的参数的方法。

bean:用于指定只匹配指定Bean实例内连接点,实际上只能使用方法执行作为连接点,定义bean表达式需要传入id或name,表示只匹配Bean实例内连接点,支持“*”通配符。
例如:

/匹配tradeService Bean实例内方法执行的连接点
bean(tradeService)
//匹配名字以Service结尾的Bean实例内方法执行的连接点。
bean(*Service)

组合切入点表达式
Spring支持3中逻辑运算符来组合切入点表达式:
&&:要求连接点同时匹配两个切入点表达式;
||:只要连接点匹配任意一个切入点表达式;
!:要求连接点不匹配指定切入点表达式。


AOP 基于配置Xml文件的管理方式

除了前面介绍的基于JDK1.5的Annotation方式来定义切面、切入点和增强处理,Spring AOP也允许使用XML文件来定义管理它们。

实际上,使用XML定义AOP也是@AspectJ一样的同样需要指定相关数据:配置切面、切入点、增强处理所需要的信息完全一样,只是提供这些信息的位置不一样而已。使用XMLA文件配置AOPd的方式有很多优点但是也有一些缺点:

xml配置方式比@AspectJ方式有更多的限制:仅支持“singleton”切面的Bean,不能在xml中组合多个命名连接点的声明。

在Spring的配置文件中,所有的切面、切入点和增强处理都必须定义在<aop:config../>元素内部。<beans../>元素下可以包含多个<aop:config../>元素。

一个<aop:config../>可以包含多个pointcut、advisor和aspect元素,且这3个元素必须按照此顺序类定义。

注意:当我们使用<aop:config../>方式进行配置时,可能与Spring的自动代理方式冲突,例如我们使用BeanNameAutoProxyCreator或类似的方式显示启用了自动代理,则它可能导致问题(例如有些增请处理没有被织入)因此要么全部使用自动代理的方式,要么全部使用<aop:config../>配置方式。不要不两者混合使用。

配置切面

定义切面使用<aop:aspect../>元素,使用该元素来定义切面时,其实质是将一个已有的Spring Bean转换成切面Bean。
因为切面Bean可以当成一个普通的SpringBean来配置,所以可以为该切面Bean配置依赖注入。
配置<aop:aspect../>元素时可以指定如下3个属性:
id:定义该切面的标识名;
ref:指定以指定ref所引用的的普通Bean作为切面Bean。
order:指定该切面Bean的优先等级,数字越小,等级越大。

配置增强处理

<aop:before.../>:Before增强处理
<aop:after../>:After增强处理
<aop:after-returning.../>:afterReturning增强处理
<aop:after-throwing../>:afterThrowing增强处理
<aop:around.../>:Around增强处理
上面的元素不能配置子元素,但可以配置如下属性:
pointcut:该属性指定一个切入点表达式,Spring将在匹配该表达式的连接点时织入增强处理。
pointcut-ref:该属性指定一个已经存在的切入点的 名称,通常pointcut与pointcut-ref只需要使用其中的一个。
method:该属性指定一个方法名,指定切面Bean的该方法将作为增强处理。
throwing:该属性只对<aop:after-throwing../>起作用,用于指定一个形参名,afterThrowing增强处理方法可以通过该形参访问目标方法所抛出的异常。
returning:该属性只对<aop:after-returning.../>起作用,用于指定一个形参名,afterReturning增强处理方法可以通过该形参访问目标方法的返回值。

当定义切入点表达式时,XML文件配置方式和@AspectJ Annotation方式支持完全相同的切入点指示符,一样支持executionwithinargsthistargetbean等切入点指示符。

XML配置文件方式和@AspectJ Annotation方式一样支持组合切入点表达式,但XML配置方式不再使用简单的&&、||、!作为组合运算符,而是使用and(相当于&&)、or(||)和not(!)
如:
Spring配置文件:

<aop:config>
<aop:aspect id="fourAdviceAspect"ref="fourAdviceBean"
order="2">
<aop:after pointcut="execution(* cn.hb.spring.lee.*.*(..))"
method="release"/>
<aop:before pointcut="execution(* cn.hb.spring.lee.*.*(..))"
method="authority"/>
<aop:after-returning pointcut="execution(* cn.hb.spring.lee.*.*(..))"
method="log"returning="obj"/>
<aop:around pointcut="execution(* cn.hb.spring.lee.*.*(..))"
method="proceedTX"/>
</aop:aspect>
</aop:config>
<aop:config>
<aop:aspectid="secondAspect"ref="secondAdviceBean"order="1">
<aop:before pointcut="execution(* cn.hb.spring.lee.*.*(..)) and args(aa)"
method="authority"/>
</aop:aspect>
</aop:config>
<!-- 定义一个普通组件Bean -->
<bean id="chinese"class="cn.hb.spring.lee.Chinese"/>
<bean id="fourAdviceBean"class="cn.hb.spring.lee.FourAdviceTest"/>
<bean id="secondAdviceBean"class="cn.hb.spring.lee.SecondAdvice"/>

//业务类:

public class Chinese implements Person {
   public String sayHello(String word) {
      System.out.println("sayHello方法开始执行...");
      return word;
  }
  public void eat(String food) {
      System.out.println("我正在吃:" + food);
  }
  public void divide() {
      int a = 5 / 0;
      System.out.println("divide执行完毕/" + a);
  }
}

//切面Bean:

public class FourAdviceTest {
  public Object proceedTX(ProceedingJoinPoint pre)throws         Throwable {
  System.out.println("Around增强处理:执行目标方法前,执行模拟      开启事务.......");
  Object[] objs = pre.getArgs();
  if (objs !=null && objs.length > 0
    && objs[0].getClass() == String.class) {
    objs[0] ="被修改的参数";
    }
  Object obj = pre.proceed(objs);
  System.out.println("Around增强处理:执行目标方法之后,模拟结  束事务.....");
  return obj +",新增加的内容";
}
public void authority(JoinPoint jp) {
    System.out.println("Before增强:模拟权限检查");
    System.out.println("Before增强:被织入增强处理的目标方法:"
        + jp.getSignature().getName());
    System.out.println("Before增强:目标方法的参数为:" + Arrays.toString(jp.getArgs()));
    System.out.println("Before增强:被注入增强的处理的目标对象:" + jp.getTarget());
}
public void log(JoinPoint jp, Object obj) {
    System.out.println("AfterReturning增强:获取目标方法的返回值:" + obj);
    System.out.println("AfterReturning增强:模拟日志记录功能.....");
    System.out.println("AfterReturning增强:被织入增强处理的目标方法:"
        + jp.getSignature().getName());
    System.out.println("AfterReturning增强:目标方法的参数为:"
        + Arrays.toString(jp.getArgs()));
    System.out.println("AfterReturning增强:被注入增强的处理的目标对象:" + jp.getTarget());
}
public void release(JoinPoint jp) {
    System.out.println("After增强:模拟方法结束后,资源释放.....");
    System.out.println("After增强:被织入增强处理的目标方法:"
        + jp.getSignature().getName());
    System.out.println("After增强:目标方法的参数为:" + Arrays.toString(jp.getArgs()));
    System.out.println("After增强:被注入增强的处理的目标对象:" + jp.getTarget());
}
}

配置切入点

类似于@AspectJ方式,允许定义切入点来重用切入点表达式,XML配置方式也可以通过定义切入点来重用切入点表达式。

配置<aop:pointcut../>元素时通常需要指定如下两个属性:
id:指定该切入点的标识名;
expression:指定该切入点关联的切入点表达式。
如下面代码:

<aop:pointcut id="myPointcut" expression="execution(* lee.*.*(..))"/>

除此之外,如果程序已经使用Annotation定义了切入点,在<aop:pointcut ../>元素中指定切入点表达式时还有另一种用法:

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

推荐阅读更多精彩内容

  • AOP实现可分为两类(按AOP框架修改源代码的时机): 静态AOP实现:AOP框架在编译阶段对程序进行修改,即实现...
    数独题阅读 2,310评论 0 22
  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,598评论 18 399
  • 定义AfterReturning增强处理: AfterReturning增强处理在目标方法正常完成之后织入。 类似...
    FTOLsXD阅读 1,113评论 0 2
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,633评论 18 139
  • 确定了要做音频后并且注册了账号后。 首先我会根据平台进行自己的声音鉴定。这样的来确定自己的领域,我准备在情感大类里...
    叶子ya阅读 173评论 0 0