AOP二世 基于AspectJ和Schema的AOP

目录

AspectJ形式的Spring AOP

@AspectJ形式AOP使用之先睹为快

编程方式织入

通过自动代理织入

@AspectJ形式的Pointcut

@AspectJ形式Pointout的声明方式

@AspectJ形式Pointcut表达式的标志符

execution

within

this和target

扫描二维码关注公众号,回复: 10855819 查看本文章

args

@within

@target

@args

@annotation

@AspectJ形式的Polntcut在Spring AOP中的真实面目

@AspectJ形式的Advice

Before Advice

After Throwing Advice

After Returning Advice

After (Finally) Advice

Around Advice

Introduction

 @AspectJ中的Aspect更多话题

Advice的执行顺序

Aspect的实例化模式

基于Schema的AOP

基于Schema的AOP配置概览

向基于Schema的AOP迁移

单纯的迁移

深入挖掘 aop advisor 

 @AspectJ到“基于Schema的AOP"迁移

基于Schema的Aspect声明

基于Schema的Pointcut

基于Schema的Advice声明

Before Advice

After Returning Advice

After Throwing Advice

After (Finally) Advice

Around Advice

Introduction

Advice的参数化

Advice的执行顺序

Aspect的实例化模式


AspectJ形式的Spring AOP

Spring框架2.0版本发布之后,Spring AOP增加了新的特性,或者说增加了新的使用方式。

支持AspectJ5发布的@AspectJ形式的AOP实现方式。现在,我们可以直接使用POJO来定义 Aspect以及相关的Advice, 并使用一套标准的注解标注这些POJO。Spring AOP会根据注解信 息查找相关的Aspect定义,并将其声明的横切逻辑织入当前系统。

简化了的XML配置方式。现在,使用新的基于XSD的配萱方式,我们可以使用aop独有的命名 空间,并且注册和使用POJO形式实现的AOP概念实体。因为引入了AspectJ的Pointcut描述语言, 也可以在新的配置方式中使用AspectJ形式的Pointcut表达式

但这只是从使用的角度来看。如果从更“本质”一点儿的角度进行分析的话,我们会发现当升级到 2.0版本之后,实际上如下两点是最主要的。

可以使用POJO声明AspectJ相关的Advice, 而再也不用像1.x版本中那样,要实现特定的Advice 就需要实现规定的接口。

获得了新的Pointcut表述方式,因为现在引入了AspectJ的Pointcut语言,再也不用在“直接 指定方法名”还是“使用正则表达式”之间选择了。至于说基于XSD的简化的配置方式,应该算 是锦上添花之作。

虽然2.0之后的SpringAOP集成了AspectJ, 但实际上只能说是仅仅拿来AspectJ的“皮大衣“用一下。

而底层各种概念的实现以及织入方式,依然使用的是Spring1.x原先的实现体系。这就好像我们中国人 说中国话,而英语在世界上较为普及并且有范围较广的影响力,我们可以学习英语,把英语拿过来为 我所用,但本质上,我们还是中国人,而不是英国人。换句话说,Spring AOP还是Spring AOP, 只不 过多学了门外语而已。

下面让我们看一下当Spring AOP拥有了Aspect:J这种表达能力之后,同样的话该怎么来说吧!

@AspectJ代表一种定义Aspect的风格,它让我们能够以POJO的形式定义Aspect, 没有其他接口定 义限制。唯一需要的,就是使用相应的注解标注这些Aspect定义的POJO类。之后,SpringAOP会根据 标注的注解搜索这些Aspect定义类,然后将其织入系统。

这种方式是从AspectJ所引入的,定义的Aspect类基本上可以在SpringAOP和AspectJ之间通用。不 过,SpringAOP只使用AspectJ的类库进行Pointcut的解析和匹配,最终的实现机制还是SpringAOP最初的架构,也就是使用代理模式处理横切逻辑的织入。

下面我们来看看@AspectJ形式是如何使用的!

@AspectJ形式AOP使用之先睹为快

如果将之前的PerformanceMethodInterceptor定义的横切逻辑以@Aspect形式实现,首先得定义一个Aspect,以最普通的POJO来定义这个Aspect就可以。按照@Aspect形式重构后的Performance MethodInterceptor定义,如代码清单所示。

定义这么一个Aspect, 我们再也无需像1.x时代的SpringAOP那样实现相应的接口了,现在唯一要 做的就是为这个Aspect类加上一个@Aspect的注解。这样,稍后我们可以根据这个@Aspect, 来判断 Classpath中哪些类是我们要找的Aspect定义。

我们知道,Aspect中可以定义多个Pointcut以及多个Advice, 所以,除了要使用@Aspect标注Aspect 类之外,还需要通过名为@Pointcut的注解指定Pointcut定义,通过@Around等注解来指定哪些方法定 义了相应的Advice逻辑。至于说这些注解如何使用,以及对应的方法定义还有什么需要注意的地方, 我们先不要管,稍后会详细讲述。

假设我们有如下目标对象类定义:

现在有两种方式将Aspect定义织入这个目标对象类,实现对其符合Pointcut定义的Joinpoint (也就 是方法执行)进行拦截。

编程方式织入

还记得在讲解Proxy Factory的时候,除了ProxyFactoryBean, 我们还提到ProxyFactory的另 一个“兄弟”吗?对,那就是org.springframework.aop.aspectj.annotation.AspectJProxy­Factory。

通过AspectJProxyFactory. 我们就可以实现Aspect定义到目标对象的织入,这样就有了如下代 码所示的编程方式织入过程:

AspectJProxyFactory的使用与ProxyFactory没有多大差别,只不过多了addAspect ()方法, 通过该方法可以直接为AspectJProxyFactory添加相应的Aspect定义。实际上,如果我们愿意,完全 可以把AspectJProxyFactory当作ProxyFactory来用!

通过自动代理织入

针对@AspectJ风格的AOP, Spring AOP专门提供了一个AutoProxyCreator实现类进行自动代理, 以免去过多编码和配置的工作,这个AutoProxyCreator我们之前也提到过,即org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator。它是在Abstract­Advisor AutoProxyCreator的基础上给出的一个扩展类,它的直接父类是AspectJAwareAdvisor­AutoProxyCreator。

与使用其他AutoProxyCreator一样,我们只需要在loC容器的配置文件中注册一下AnnotationAwareAspectJAutoProxyCreator就可以了,如下配置所示:

现在,AnnotationAwareAspectJAutoProxyCreator会自动搜集IoC容器中注册的Aspect, 并应 用到Pointcut定义的各个目标对象上。如果我们通过容器取得现在的target对象的话,会发现它已经 是被代理过的了,如下:

当然,如果把target作为依赖对象注入其他的bean定义,那么依赖的主体对象现在持有的也是被 代理过的目标对象。

刚才AnnotationAwareAspectJAutoProxyCreator注册到容器的方式是基于DTD的配置方式, 在Spring 1.x以及2.x版本中都可以使用。如果我们能够使用Spring 2.x版本,井且使用基于XSD的配置 方式,还可以有另一种更加简洁的配置方式,如代码清单所示。

通过面向aop命名空间的<aop:aspectj-autoProxy>,可以达到与基于DtD的配置方式中,直接声 明AnnotationAwareAspectJAutoProxyCreator相同的效果。

该元素背后的工作实际上就是由AnnotationAwareAspectJAutoProxyCreator做的。另外,不要忘了将aop命名空间的Schema定义引入XSD定义。我想,要用<aop:aspectj-autoproxy>的话,应该也不会忘记这一点!

小心 在使用@AspectJ形式的AOP的时候,应该尽量使用容器内的自动代理支持.通常,只 有出于测试目的,才会使用编程的方式进行直接的织入操作令在使用的过程中,你会发现,实 际上这两种织入方式是有差异的,一些行为并不统一.

从现在开始,只要使用到了@Aspect形式的AOP, 就不要忘记将aspectjweaver. jar和 aspectjrt. jar加入到应用程序的Classpath中。因为有些具体工作是要求助于AspectJ的类库的,要知 道都哪些具体任务,我们接着往下行吧!

@AspectJ形式的Pointcut

在Spring框架发布2.0版本之前,SpringAOP没有像AspectJ那样的正式的Pointcut描述语言,而且也 只支持方法级别的拦截。所以通常情况下,简单的方法名指定以及正则表达式两种方式,基本可以很 好地达到目的。在Spring发布2.0版本之后,SpringAOP框架集成了AspectJ的部分功能,这其中就包括 AspectJ的Pointcut描述语言支持。

@AspectJ形式Pointout的声明方式

@AspectJ形式的Pointcut声明,依附在@Aspect所标注的Aspect定义类之内,通过使用 org .aspectj. lang. annotation. Pointcut这个注解,指定AspectJ形式的Poincut表达式之后,将这 个指定了相应表达式的注解标注到Aspect定义类的某个方法上即可。代码清单给出了一个使用 pointcut的简单示例。

@AspectJ形式的Pointcut声明包含如下两个部分。

Pointcut Expression。Pointcut Expression的载体为pointcut, 该注解是方法级别的注解,所以, Pointcut Expression不能脱离某个方法单独声明。Pointcut Expression附着于上的方法称为该 Pointcut Expression的Pointcut Signature。

Pointcut Expression是真正规定Pointcut匹配规则的地 方,可以通过@Pointcut直接指定AspectJ形式的Pointcut。@Pointcut所指定的AspectJ形 式的Pointcut表达式由如下两部分组成。

Pointcut标志符(Pointcut Designator)。标志符表明该Pointcut将以什么样的行为来匹配表达 式,可以使用的Pointcut表达式将在后文中详细讲述。

表达式匹配模式。在Pointcut标志符之内可以指定具体的匹配模式

Pointcut Signature。Pointcut Signature在这里具体化为一个方法定义,它是Pointcut Expression 的载体。Pointcut Signature所在的方法定义,除了返回类型必须是void之外,没有其他限制。

方法修饰符所起的作用与Java语言中语义相同,public型的Pointcut Signature可以在其他 Aspect定义中引用,private则只能在当前Aspect定义中引用。可以将Pointcut Signature作为相 应Pointcut Expression的标志符,在Pointcut Expression的定义中取代重复的Pointcut表达式定义, 如代码清单所示。

stil1Mehtod1Execution ()的Pointcut expression, 通过第一个Pointcut定义的Pointcut Signature, 即method1Execution(}, 引用了第一个Pointcut定义,所以,这两个Pointcut定义的规则是一样的。

AspectJ的Pointcut表达式支持通过&&,||,以及!逻辑运算符,进行Pointcut表达式之间的逻辑运算, 运算符可以应用于具体的Pointcut表达式,以及相应的Pointcut Signature。代码清单演示了这几种 逻辑运算符的使用。

可以看到,通过前两个简单的Pointcut定义以及相应的逻辑运算,可以得到更为复杂的Pointcut定 义。因为前两个简单的Pointcut定义只需要在当前Aspect内引用,所以,我们声明为private。不过, 我们也完全可以将其声明为public型的,这样,其他的Aspect中的Pointcut定义也同样可以引用的到。 对于一个系统中能够公用或者统一管理的一类Pointcut来说,完全可以声明一个专门的Aspect来定义这 些Pointcut, 如代码清单所示。

然后,在其他Aspect定义的Pointcut定义中引用它们,以避免重复定义,比如:

为了能够让Pointcut表达式能够运行起来,我们还是抓紧时间来看一下都有哪些可以使用的 Pointcut标志符吧!

@AspectJ形式Pointcut表达式的标志符

虽然AspectJ的Pointcut:可用的标志符很丰富,基本上可以囊括所有Joinpoint类型的表述,但 是,因为Spring AOP只支持方法级别的Joinpoint, 所以可以通过AspectJExpressionPointcut指定的AspectJ形式的Pointcut:是有一定限制的,我们只能使用AspectJ的Pointcut表述语言中的少数几种 标志符。

execution

Spring AOP仅支持方法执行类型的Joinpoint, 所以execution将会是我们使用最多的标志符,使 用它,将帮助我们匹配拥有指定方法签名的Joinpoint。使用execution标志符的Pointcut表达式的规定 格式如下:

其中,方法的返回类型、方法名以及参数部分的匹配模式是必须指定的,其他部分的匹配模式可以省略

假设我们拥有以下类定义:

那么可以使用如下的Pointcut表达式来匹配Foo的doSomething的方法执行:

execution{public void Foo.doSomething{String))

因为部分匹配模式可以省略,所以,我们也可以简化以上模式,如下所示:

execution{void doSomething(String))

除此之外,我们还可以在execution的表达式中使用两种通配符,即*和..

*可以用于任何部分的匹配模式中,可以匹配相邻的多个字符,即一个Word。使用*之后,我 们以上的execution表达式就可以简化成:

当然这样的简化之后,匹配的范围要比原来的表达式所匹配的范围要广得多。另外,我们还 可以在方法参数匹配模式中也使用*来匹配,如下所示:

在这里,以上表达式表示只有一个参数的方法,参数类型可以为任何类型。

..通配符可以在两个位置使用,一个是在declaring-type-pattern规定的位置,一个在方法 参数匹配模式的位置。如果用于declaring-type-pattern规定的位置,则可以指定多个层次 的类型声明,如下:

如果..用于方法参数列表匹配,则表示该方法可以有0到多个参数,参数类型不限,如下:

注意,如果在这里使用*,则只能匹配一个参数。另外,我们还可以进行*、..与具体参数类型的 组合,如下所示:

within

within标志符只接受类型声明,它将会匹配指定类型下所有的Joinpoint。不过,因为SpringAOP 只支持方法级别的Joinpoint, 所以,在我们为within指定某个类后,它将匹配指定类所声明的所有方 法执行。假设我们声明一个使用within标志符的Pointcut如下:

那么,该Pointcut表达式在Spring AOP中将会匹配MockTarget类中的所有方法声明。另外,我们 也可以通过*和.. 通配符来扩展匹配的类型范围,如下所示:

this和target

之所以将这两个标志符放在一起讲,是因为通过对它们的对比,可以更清楚地了解这两个标志符 各自的语义。

在AspectJ中this指代调用方法一方所在的对象(caller) , target指代被调用方法所在的对象(callee) , 这样通常可以同时使用这两个标志符限定方法的调用关系。

比如,如果Object1、Object2都会调用Object3的某个方法,那么,Pointcut 表达式定义this (Object2} && target (Object3)只会 当Object2调用Object3上的方法的时候才会匹配,而Object1调用Object3上的方法则不会被匹配。

Spring AOP中的this和target标志符语义,有别于AspectJ中这两个标志符的原始语义。

现在, this指代的是目标对象的代理对象,而target如其名般指代的就是目标对象。

如果使用 this (ObjectType)作为Pointcut定义,那么当目标对象的代理对象是ObjectType类型的时候,该 Pointcut定义将匹配ObjectType类型中所有的Joinpoint。在Spring AOP中,也就是ObjectType中定义 的所有方法。

而使用target{ObjectType)作为Pointcut定义,当目标对象是ObjectType类型的时候, 该Pointcut定义将匹配Objectfype型目标对象内定义的所有Joinpoint。在SpringAOP中,当然还是所有 的方法执行。

实际上,从代理模式来看,代理对象通常跟目标对象的类型是相同的,因为目标对象与它的代理 对象实现同一个接口。即使使用CGLIB的方式,目标对象的代理对象属于目标对象的子类型,通过单 独使用this或者target指定类型,起到的限定作用其实是差不多的。假设我们有对象定义,如下:

不论使用基于接口的代理方式,还是基于类的代理方式,如下两个Pointcut所起的所用实际上是差不多的:

因为TargetFoo作为目标对象实现了ProxyInterface。对基于接口的代理来说,它的代理对象同 样实现了这个接口。对于基于类的代理来说,因为目标对象的代理对象是继承了目标对象,自然也继 承了目标对象实现的接口。所以,在这里,这两个Pointcut定义起得作用差不多。如果通过this和target指定具体类型,会怎么样呢?如下所示:

这时,对于基于接口的代理和基于类的代理来说,效果就不同了。对于前者来说,target­(TargetFoo)可以匹配目标对象中的所有Joinpoint, 因为目标对象确实是Target Foo类型

而 this(TargetFoo)则不可以。此时,这两个标志符出现分歧了。不过,对于后者,即基于类的代理来 说,这两个Pointcut表达式限定的语义还是基本相同的。

通常,this和target标志符都是在Pointcut表达式中与其他标志符结合使用,以进一步加强匹配的限定规则,比如:

当然,我们也可以将this和target结合到一个Pointcut定义中。这样,在目标对象和代理对象的类型不同的时候,可以达到严格匹配规则的目的。为了说明这一点,让我们的TargetFoo定义再一个接口吧!代码清单给出了变更后的相关类定义。

当为目标对象生成代理对象时,我们声明只对Proxyinterface接口进行代理,那么使用以上 Pointcut表达式可以匹配Target Foo。如果还有其他的Proxy Interface实现类,但是它们没有同时实 现Proxyinterface2, 那么这些其他的Proxy Interface实现类则不会被匹配。

除了以上这种代理对象实现的接口比目标对象少的情况,对于Introduction来说,代理对象所实现 的接口数揖通常比目标对象多,也同样可以同时使用this和target进一步限定匹配的规则,比如:

Introduction为目标对象动态添加了新的接口,但是新的接口添加到了目标对象的代理对象上,所 以,在这一点上,this和target可以指定的特性是有差异的。

总之,在理解this和target的匹配行为的基础上,应该将this和target标志符与其他标志 符一起使用。

args

该标志符的作用是,帮助我们捕捉拥有指定参数类型、指定参数数量的方法级Joinpoint, 而不管 该方法在什么类型中被声明

如果我们声明Pointcut表达式如下:

但是,与使用execution标志符可以直接明确指定方法参数类型不同,args标志符会在运行期间 动态检查参数的类型,即使我们的方法签名声明如下:

public boolean login(Object user);

只要传入的是User类型的实例,那么使用args标志符的Pointcut表达式依然可以捕捉到该 Joinpoint

但是,类似于execution (* *(User}}这样的Pointcut,则无法捕捉以上Pointcut形式 声明的Joinpoint, 因为它是静态的Pointcut。

@within

如果使用@within指定了某种类型的注解,那么,只要对象标注了该类型的注解,使用了@within 标志符的Pointcut 表达式将匹配该对象内部所有Joinpoint。对于SpringAOP来说,当然是对象内部声明 的所有方法级Joinpoint。例如,假设我们声明注解如下:

当使用@within标志符声明Pointcut时,如下所示:

@within(AnyJoinpontAnnotation)

Foo类中的method1、method2等方法将全部被该Pointcut表达式所匹配。因为该类标注了 AnyJoinpontAnnotation, 其他类如果也标注了该注解,也会被一概纳入襄中。

小心 @within只接受注解类型,并对被指定注解类型所标注的类生效.

@target

如果目标对象拥有@target标志符所指定的注解类型,那么目标对象内部所有的Joinpoint被匹 配。当然,对于Spring AOP来说,是目标对象中所有的方法级别Joiopoint匹配。

以@within部分使用的Foo类和它相关的AnyJoinpontAnnotation为例,如果指定Pointcut表达 式如下:

只要当前的目标对象像Foo那样标注了@AnyJoinpontAnnotation, 则目标对象内部的所有方法 级别Joinpoint将被匹配。

在SpringAOP中,@within和@target没有太大的区别。只不过@within属于静态匹配,而@target 则是在运行时点动态匹配Joinpoint。

@args

使用@args标志符的Pointcut 达式将会尝试检查当前方法级的Joinpoint的方法参数类型。如果该 次传入的参数类型拥有@args所指定的注解,当前Joiopoint被匹配,否则将不会被匹配。如图所示。

第一次方法调用,传入的参数为InterceptableOne类型的实例,而该类型没有标注指定的注解, 即@AnyJoinpontAnnotation, 所以当前handOut方法执行将不会被Pointcut所匹配。第二次方法调用,传入的参数为InterceptableTwo, 而它标注了@AnyJoinpontAnnotation, 所以,这次方 法执行将被匹配。

@args会尝试对系统中所有对象的每次方法执行的参数,都进行指定的注解的动态检查。只要参 数类型标注有@args指定的注解类型,当前方法执行就将匹配,至于说参数类型是什么,它则不是十 分关心。

小心@args只接受注解类型声明!另外,我尝试在同一个@args标志符中同时指定两个注解, 类似于@args{annotationTypel,annotationType2}。但Spring AOP不予理睬.呵呵,不过 没关系,稍后使用逻辑运算符就可以了!

@annotation

使用@annotation标志符的Pointcut表达式,将会尝试检查系统中所有对象的所有方法级别 Joinpoint。如果被检测的方法标注有@annotation标志符所指定的注解类型,那么当前方法所在的 Joinpoint将被Pointcut表达式所匹配。

@annotation标志符的应用场景也比较广泛,尤其是使用Java6之后。假设我们要对系统中的事务 处理进行统一管理,除了稍后将介绍的声明性事务处理之外,我们也可以通过团队内部规定的使用注 解的方式管理事务。当开发人员希望对某个方法加事务控制的时候,只要使用相应的注解标注一下即 可,如下所示;

要是早有这个东西就好了,也省得我在之前的项目中重新实现了一套类似的功能。

最终需要注意的就是,@annotation所接受的注解类型只应用于方法级别,即标注了 target(ElementType.METHOD)的注解声明。

注意 所有以@开头的标志符,都只能指定注解类型参数,而且,注解只有Java 5甚至之后才有效。所以,你已经猜到了,要用这些,就得祈祷我们的系统已经升级到Java 5或者更高版本 的NM了.

在Spring AOP中,如果要使用AspectJ的Pointcut表达式来指定Poiotcut, 只允许使用以上列出的几 种标志符。AspectJ中同样可以使用的initialization、get、set、handler等标志符,在SpringAOP中 是不支持的。如果通过@Pointcut设置了包含这些不支持的标志符的Pointcut表达式,SpringAOP将抛 出IllegalAugumentException而拒绝这种“无礼诮求“。毕竟,Spring AOP只是借用一下AspectJ的 Pointcu改述语言,而底层的Joinpoint类型匹配却依然是Spring最初的承诺。

即使是在Spring AOP中可以使用的AspectJ中的Pointcut表达式标志符,它们的语义跟AspectJ中的最初语义也会或多或少有所偏差。所以,与其说SpringAOP现在可以使用AspectJ的Pointcut 乏述诺言, 不如说Spring AOP借用了AspectJ的Pointcut表述语言的“外衣”,而实际上底层的语意和最终匹配, 却还是沿用Spring AOP最初的机制。

另外,将来Spring AOP还可能在原来表达式的基础上增加新的标 志符,如bean (. . .) 。而这种bean标志符在AspectJ的Pointcu氓表述语言中是不存在的,AspectJ扩展了 Java语言,现在Spring AOP的Pointcut表达式则要扩展AspectJ的Pointcut表达式了。

@AspectJ形式的Polntcut在Spring AOP中的真实面目

实际上,@AspectJ形式的所有Pointcut表达式,在SpringAOP内部都会通过解析,转化为具体 的Pointcut对象。因为SpringAOP有自己的Pointcut定义结构,所以,@AspectJ形式声明的这些Pointcut 表达式,最终会转化成一个专门而向AspectJ的Pointcut实现。

org.springframework.aop.aspectj.AspectJExpressionPointcut代表Spring AOP中面向AspectJ的Pointcut具体实现。虽然它会使用AspectJ的相应支持,但依然遵循Spring AOP的Pointcut定义, 其在Pointcut中的地位如图所示。

定义ExpressionPointcut和AbstractExpressionPointcut主要是为了以后的扩展性。如果还 有Aspecu的Pointcut描述语言之外的形式,我们可以在这两个的基础上进行集成。

在AspectJProxyFactory或者AnnotationAwareAspectJAutoProxyCreator通过反射获取了 Aspect中的Pointcut定义的AspectJ形式的Pointcut定义之后,在SpringAOP框架内部都会构造一个对 应的AspectJExpressionPointcut对象实例。AspectJExpressionPointcut内部持有通过反射获得 的Pointcuet表达式

AspectJExpressionPointcut属于Spring AOP的Pointcu.t定义之一,所以,Spring AOP框架内部 处理Pointcut匹配的逻辑不需要改变,依然使用原来的匹配机制,即通过ClassFilter和 MethodMatcher进行具体Joinpoint的匹配工作。

不过,AspectJExpressionPointcut在实现 ClassFilter和Method.Matcher相应方法逻辑的时候,会委托AspectJ类库的相关类来做具体的工作。 AspectJExpressionPointcut会委托AspectJ类库中的PointcutParser来对它所持有的AspectJ形式 的Pointcut表达式进行解析。PointcutParser解析完成之后会返回一个PointcutExpression对象(依 然是AspectJ类库中的类),之后,匹配与否就直接委托这个PointcutExpression对象的相关方法进 行处理了。

AspectJExpressionPointcut属于面向AspectJ的Pointcut实现,所以,我们可以像Spring AOP中 其他各种Pointcut实现类那样来使用它。只不过,构造完成后,我们不是为它设置要拦截的方法名或者 正则表达式,而是如下所示设置@AspectJ形式的Pointcut表达式:

当然,我们使用@AspectJ形式的AOP是没有必要通过编码来使用AspectJExpressionPointcut 的,直接在Aspect中通过@PoIntcut指定AspectJ形式的Pointcut表达式,比编码使用AspectJ­ExpressionPointcut要简洁得多。不过,如果愿意,我们完全可以在1.x版本的Spring AOP中使用 AspectJExpressionPointcut类型的Pointcut来指定具体的Pointcut定义,并不是说AspectJEx­pressionPointcut只有在@Aspect形式的AOP中才可以使用。

@AspectJ形式的Advice

@AspectJ形式的Advice定义,实际上就是使用@Aspect标注的Aspect定义类中的普通方法。只不 过,这些方法需要针对不同的Advice类型使用对应的注解进行标注。

可以用于标注对应Advice定义方法的注解包括:

@Before。用于标注Before Advice定义所在的方法;

@AfterReturning. 用于标注A知r Returning Advice定义所在的方法;

@After-Throwing。用于标注After Throwing Advice定义所在的方法,也就是在SpringAOP中称 为ThrowsAdvice的那种Advice类型;

@After。用于标注.After(finally) Advice定义所在的方法,1.x版本的SpringAOP中没有对应这种 类型的Advice接口定义或者实现;

@Around。用于标注Around Advice定义所在的方法,也就是常说的拦截器类型的Advice;

@DeclareParents。用于标注Introduction类型的Advice, 但该注解对应标注对象的域(Field),而不是方法(Method) 

除了@DeclareParents比较不同之外,其他用于标注不同类型.Advice的注解,全部都是方法级别 的注解定义,只能用于标注方法定义。同时,各种Advice最终织入到什么位置,是由相应的Pointcut 定义决定的。所以,我们需要为这些用于标注Advice的注解指定对应的Pointcut定义,可以直接指定@AspectJ形式的Pointcut表达式,也可以指定单独声明的@Pointcut类型定义的Pointcut Signature (不 包括@DeclareParents, 它的使用会单独详细阐述),如代码清单所示。

好了,还是让我们详细看一下各种Advice在@AspectJ形式中的使用方式和可能需要注意的问题 吧!

Before Advice

以@AspectJ形式声明Before AdVice , 只需要在Aspect定义类中定义相应的方法,然后使用 org.aspectj.lang.annotation.Before标注该方法即可。

如果将之前的ResourceSetupBeforeAdvice定义移植到@AspectJ形式,我们可以如代码清单所示定义其横切逻辑。

org.aspectj.lang.annotation.Before的value成员变量必须指定,可以像代码清单那样 直接指定Pointcut表达式,也可以指定单独声明的Pointcut定义的Pointcut Signature, 如代码清单所示。

某些情况下,我们可能需要在Advice定义中访间Joinpoint处的方法参数,在1.x的Spring AOP中, 我们可以通过MethodBeforeAdvice的before方法传入的Object []数组,访问相应的方法参数。现在, 我们有如下两种方式达到相同的目的。

通过org .aspe,ctj. lang. JoinPoint., 在@AspectJ形式的Aspect中,定义Before Advice的方法 可以将第一个参数声明为org.aspectj.lang.JoinPoint类型,通过Joinpoint我们可以借助它 的getArgs ()方法,访问相应)oinpoint处方法的参数值。

另外,我们还可以借助它的其他方法 取得更多信息,比如,getThis ()获得当前代理对象,getTarget ()取得当前目标对象, getSignature ()取得当前Joinpoint处的方法签名等。

这样,通过如下形式的方法定义,我们就可以借助org.aspectj.lang.JoinPoint取得需要的 信息:

当然,通常情况下,如果不需要这些信息,org.aspectj·lang .JoinPoint的方法参数声明是 可以省略的。

通过arge标志符绑定。前面已经介绍过了args标志符,我们可以通过为其指定相应的对象类 型,来进一步限定Pointcut定义。不过,它还有一个用处:当args标志符接受的不是具体的对 象类型,而是某个参数名称的时候,它会将这个参数名称对应的参数值绑定到对Advice方法的 调用。

我们可以在Before Advice对应的方法之上声明需要的参数(任何必要的类型),然后在Pointcut 定义中使用args将这些声明的参数绑定到调用即可,如下:

注意,args指定的参数名称必须与Advice定义所在方法的参数名称相同。在这里,args指定 的值和setupResourcesBerore方法的参数名都是taskName。

当然,如果Advice引用的是独立的Pointcut定义,使用args绑定的形式也是大同小异的。只不过 现在Advice方法直接引用带有参数的Pointcut定义罢了,具体示例如下所示:

另外,我们还可以同时使用org.aspectj.lang.JoinPoint以及使用args参数名称绑定功能。这 实际上是显而易见的,如下所示:

不过,需要注意,org.aspectj.lang.JoinPoint永远处于第一个参数位置。

针对Before Advice访问方法参数的两种方式,我们需要进一步扩展它们的应用场景。

首先,在@AspectJ形式的Advice声明方式中,不只Before Advice的方法第一个参数可以声明为 org.aspectj.lang.JoinPoint类型,实际上,除了Around Advice和Introduction不可以这么 用之外,余下的Advice类型的方法声明都遵循这个规则;

其次,不只可以使用args标志符来绑定参数声明到方法,实际上,JoinPoint标志符中,除了 execution标志符不会直接指定对象类型之外,其他像this、target、@within、@target、 @annotation、@args等原本都是指定对象类型的。它们与args一样,在这样的场合下,如果 它们指定的是参数名称,那么,所起的作用与args在这种参数绑定的场景中的作用是类似的, 比如:

当目标对象上Joinpoint处的方法标注了org.springframework.transaction.annotation. Transactional, 我们可以通过这种方式取得其事务设置的详细信息。

After Throwing Advice

After Throwing Advice的@AspectJ形式声明,同样是在Aspect定义类中声明相应的方法,只不过要 使用org.aspectj.lang.annotation.AfterThrowing对其进行标注。

将ThrowsAdvice部分的实例ExceptionBarrierThrowsAdvice移植到@AspectJ形式的声明,如

@AfterThrowing有一个独特的属性,即throwing, 通过它,我们可以限定Advice定义方法的参 数名,并在方法调用的时候,将相应的异常绑定到具体方法参数上。在这里,我们使用throwing="e" 将RuntimeException类型的异常绑定到了aftertThrowing方法,这样就可以访问具体的异常信息。

当然,根据不同的场景,我们可以对代码清单的代码示例进行增删。如果不需要访问具体异 常,那么就声明没有任何参数的方法。这样,也不用明确指定注解的哪个属性对应哪个值,如下所示:

不过,如果不但要访问具体的异常信息,还需要访问其他信息,那么我们结合这两种方式来获取必要的信息,如下所示: 

是使用Joinpoint的一站式服务,还是使用args等标志符的专职服务,完全看个人的喜好了。

注意与1.x中的ThrowsAdvice可以同时针对不同的异常类型声明不同的方法进行处理一样, 我们也可以在Aspect中针对多个不同的异常类型,声明不同的After Throwing Advi.ce的方法定义.

After Returning Advice

要声明@AspectJ形式的After Returning Advice, 使用org.aspectj.lang.annotation.After­Returning标注Aspect定义中的相应方法即可。

如果我们将之前的TaskExecutionAfterReturningAdvice移植过来,那么看起来如代码清单所示。

这里,我们只通过声明Joinpoint参数来获取目标对象的信息。但对于After Returning Advice来说, 某些时候需要访问方法的返回值,这时,可以通过@AfterReturning的returning属性将返回值绑定 到After Returning Advice定义所在的方法,如下所示:

After (Finally) Advice

最初1.x版本发布的Spring AOP中没有专门针对After(Finally) Advice类型的接口定义,所以,通常 我们会通过使用Around Advice (或者说拦截器)来达到与使用After(Finally) Advice相同的目的。2.0 之后,我们通过@AspectJ形式的AOP就能够在Aspect中直接声明这种类型的Advice。

要声明@AspectJ形式的After{Finatly) Advice, 只需要通过org.aspectj.lang.annotation. After对Aspect中定义的相应方法进行标注。

对于匹配的Joinpoint处的方法执行来说,不管该方法是正常执行返回,还是执行过程中抛出异常 而非正常返回,都会触发其上的After(Finally) Advice执行。所以,After(Finally) Advice适合用于释放 某些系统资源的场景。

假设我们的系统中,某个目录存放的都是临时文件,系统退出后或者某些合适的时机,这些临时 文件需要删除以释放磁盘空间,那么,我们可以定义一个After(Finally) Advice来做类似的工作(见代 码清单)。

当然,更多的时候,我们可以使用After(Finally) Advice来处理网络连接的释放、数据库资源的释 放等。

如果处理逻辑中需要访问Joinpont处的方法参数相关信息,可以通过Before Advice部分提及的两种 方式进行处理。

Around Advice

@AspectJ形式的Around Advice声明与之前几个Advice类型的声明类似,最大的区别就是用于标注 具体Advice定义方法的注解不一样,我们使用org.aspectj.lang.annotation.Around来标注Aspect 中Around Advice所定义的方法。

不过,Around Advice的方法定义与之前几个Advice类型的方法定义有一些不同。我们说过,Before Advice、After Throwing Advice、After Returning Advice以及After Advice的方法定义第一个参数可以为 org. aspectj. lang .JoinPoint类型,而且是可选的。而对于Around Advice的方法定义来说,它的第 一个参数必须是org.aspectj.lang.ProceedingJoinPoint类型(属于org.aspectj.lang.Join­Point的子类),而且必须指定。通常情况下,我们需要通过ProceedingJoinPoint的proceed()方 法继续调用链的执行。

将PerformanceMethodinterceptor逻辑移植到@AspectJ形式的Around Advice声明形式,如代 码清单所示。

当然,我们可以根据情况来决定是否要调用proceed(}方法,甚至调用多少次。

除了直接调用proceed()方法,还可以在调用proceed()方法的时候,传入一个Object[]数组代 表方法参数列表。通常,在修改方法调用参数值的时候,会使用带Object []参数的proceed()方法。

同样以PerformanceTraceAspect为例,我们为Around Advice的方法定义增加参数,代表当前任 务的名称。在继续执行PerformanceTraceAspect后面调用链上的Advioe逻辑或者目标方法之前,我 们会在PerformanceTraceAspect内先对传入的参数进行处理,然后再继续调用流程,详情如代码清 单所示。

如果有多个参数,应该按照方法定义的顺序指定,以便定义的Around Advice可以在Spring AOP以 及AspectJ之间通用。

Introduction

以@AspectJ形式声明的Introduction. 完全不同于之前提过的任何Advice, 这回不是使用注解对Aspect中各Advice的定义方法进行标注,而是对Aspect中的实例变量定义进行标注。

我们知道,在Spring中,Introduction的实现是通过将需要添加的新的行为逻辑,以新的接口定义 增加到目标对象上。要以@AspectJ形式声明Introduction, 我们铺要在Aspect中声明一个实例变诡,它 的类型对应的就是新增加的接口类型,然后使用org. aspectj. lang. annotation. DeclareParents 对其进行标注。通过@DeclareParents指定新接口定义的实现类以及将要加诸其上的目标对象。

下面我们来春一个例子。

以讲解ProxyFactoryBean部分的ITask以及ICounter工作为实例对象,将ICounter的行为逻辑加 到ITask类型的目标实现类上。假设ITask的实现类是MockTask, 而!Counter的实现类是 Counterlmpl, 我们通过如下的Aspect声明将ICounter的行为in扛ocluce附加到ITask之上:

该定义需要注意如下儿个地方。

@DeclareParents所归属的域定义类型为ICounter, 也就是将为目标对象新坳加的对象类型。 通过@DeclareParents的value属性,可以指定将要应用到的目标对象。现在只指定了单一的 目标对象,还可以通过通配符指定一批目标对象。

假设en. spring21. unveilspr-ing. service 包下的所有类都想增加Icounter的行为,那么,可以参照如下代码为value指定表达:.

通过@DeclareParents的Defaultimpl属性,我们可以指定新增加的接口定义的实现类。在 这里,ICounter的实现类为CounterImpl, 所以可以指定Defaultimpl=Counterimpl.class。

如果将这个Aspect定义按照如下代码注册到Spring的IoC容器:

会发现,不同的目标对象现在对应着不同的ICounter实例,因为每个方法调用后返回的状态都是 各自的。

 @AspectJ中的Aspect更多话题

我想关于@AspectJ形式的Aspect声明就没有必要再重复一遍了,简单的POJO...@J\spect就是一个, @AspectJ形式的Aspect。不过,跟Aspect有关的儿个话题倒是有必要进一步讲述一下。

Advice的执行顺序

对于多个Advice来说,如果它们引用的Pointcut定义恰好匹配同一个Joinpoint的时候,在这同一个 Joinpoint上,这些Advice该按照什么顺序执行呢?对于这个问题,我们应该从以下两个角度来看。

当这些Advice都声明在同一个aspect内的时候。如果匹配同一个Joinpoint的多个Advice声明 在同一个Aspect定义中,那么这些Advice的执行顺序,由它们在Aspect中的声明顺序决定。最 先声明的Advice拥有最高的优先级。

对于Before Advice来说,拥有最高优先级的最先运行,而 对于AfterReturngingAdvice, 拥有最高优先级的则是最后运行。

假设声明拥有多个Advice定义的Aspect (如代码清单所示),这些Advice全部引用同一个 Pointcut定义。

如果将该Aspect织入目标对象,那么可以得到如下的输出结果:

Before Advice的输出很容易理解,尤其需要注意After Returning Advice的输出,afterRet­urningOne ()先于afterReturningTwo ()声明,拥有较高的优先级,但因为它属于After Returning Advice, 所以,要晚于afterReturning'Iwo ()的执行。其他类型的Advice执行可以 按照这个规则依此类推。

当这些Advice声明在不同的Aspect内的时候。如果多个Advice声明所对应的Pointcut定义匹配 同一个JoinPoint, 但它们又不是声明在同一个Aspect内的话,情况是怎么样的呢?这时,我们 需要用到Spring的org.springframework.core.Ordered接口,只需要让相应的Aspect定义实 现Ordered接口即可,否则,AdVice的执行顺序是不确定的。

ordered.getOrder ()方法返回较小值的Aspect, 其 内部所声明的Advice拥有较高优先级,而Ordered.getorder()方法返回值较大的Aspect, 其 内部声明的Advice则拥有较低的执行优先级。

如果再声明一个Aspect (见代码清单) , 让它的一个Before Advice也引用MultiAdvices­Aspect定义的Pointcut。

同时,让MultiAdvicesAspect也实现Ordered接口,并先将getOrder()的返回值定为20, 那么当将这两个Aspect同时织入同一个目标对象的时候,输出就会变为:

因为MultiAdvicesAspect比Another Aspect拥有的执行优先级较高,所以它内部的两个Before Advice要比AnotherAspect内部的Before Advice优先执行。

当使用org.springframework.core.Ordered 对Aspoct内的Advice执行顺序进行限定时, 如下两种情况需要注意.

如果使用Spring的loC容器注册并使用这些Aspect, 让自动代理机制处理这些横切逻辑到目标 对象的织入,那么,多个Aspect内的Advice顺序正是如以上所声明的那样

如果通过编程的方式来使用这些Aspect, 那么,情况就大不一样了,Aspect内的Advice执行 顺序完全由添加到AspectJProxyFactory的顺序来决定,而不是Ordered接口所规定的顺 序。如果采用如下代码将MultiAdvicesAspect和Another Aspect织入目标对象的话, Another Aspect内的Advice要比MultiAdvicesAspect内的Advice优先执行:

如果先通过addAspect添加MultiAdvicesAspect, 后添加AnotherAspect, 才会跟使用Ordered 接口并通过自动织入所达到的效果一样

Aspect的实例化模式

对于注册到容器的各个Aspect, 它们默认的实例化模式(singleton instantiation model)采用的是 singieton, 也就是说,在容器中会实例化并持有每个Aspect定义的单一实例(行为上恰好与容器的 singleton型scope的行为相吻合)。

除了singleton的实例化模式,.AspectJ还支持perthis、pertarget、percflow、percflowbelow以及perwithin 等实例化模式。不过,Spring2.0之后的AOP只支持默认的singleton、perthis和pertarge 3种实例化模式。

要想指定相应Aspect的实例化模式,可以通过@Aspect指定perthis或者pertarget语句。如果想让 MultiAdvicesAspect的实例化模式从默认的singleton变为perthis, 可以如下指定:

这样,在Perthis指定的Pointcut定义匹配之后,会为相应的代理对象实例化各自的Aspect实例。

对于pertarget来说,则是为匹配的单独的目标对象实例化相应Aspect实例。,(可以在perthis或者pertarget 语句中,使用Pointcut定义的Pointcut Signature代替直接的Pointcut表式定义。)

不过,使用perthis或者pertargeit指定了Aspect的实例化模式之后,将这些Aspect注册到容器时,不 能为其bean定义指定singleton的scope, 否则会出现异常。毕竟,容器先限定了只有一个实例,就不能 为每一个代理对象或者目标对象实例化相应实例。

当然,如以上代码所示,为了perthis和perta.rget能够正常工作,我们也应该考虑将相应的目标对象 的scope设置为prototype或者其他自定义scope类型。

基于Schema的AOP

基于Schema的AOP是Spring2.0发布之后新培加的一种AOP{! 用方式,我们可以从如下两个角度来 看待基于Schema的AOP。

配置方式的改变。Spring框架从1.x版本升级到2.x版本之后,提倡的容器配置方式从基于DTD 的XML专向了基于Schema的XML, 进一步提高了配置方式的灵活性和可扩展性。同时,新的 基于Schema的配置方式为Spring的AOP功能专门提供了独有的命名空间。原来1.x中基于DID 的AOP配置方式,可以稍微转换一下配置方式就移植到基于Schema的AOP, 所以,从这一点 来说,基于Schema的AOP只是配置方式的改变。当然,这只是从本质上说,稍后我们将看到, 还有少许新的特性实际上是可用的。

@AspectJ形式AOP的折中。要使用@AspectJ形式的AOP, 必须要求使用Java 5或者更高版本 的JDK或JRE, 因为注解是Java5发布之后才引入的特性。如果我们不得不使用Java5之前的版 本,而又想使用基于POJO的Aspect户明方式,这时,就可以使用基于Schema的Spring AOP。 使用基于Scheina的AOP, 我们可以依然使用POJO声明Aspect以及相关的Advice。不过,不需要注解标注了,直接通过Schema的配置文件进行配贺就可以,@AspectJ形式的Pointcut表达式也全都可以配置到基于Schema的配置文件中。

要使用基于Schema的AOP, IoC容器的配翌文件应该使用基于Schema的XML, 同时在文件头中增 加针对AOP的命名空间声明,如代码清单所示。

添加了aop的命名空间声明之后,我们就可以使用aop命名空间内的各种配置元素了。

基于Schema的AOP配置概览

新的基于Schema的AOP配置方式,针对Pointcut、Advisor以及Aspect等概念提供了独立的配置,所有这些配置元素都包含在统一的配置元素中,即<aop:config>,如代码清单所示。

<aop:config>只有一个属性proxy-target-class; 对应Proxyconfig中的proxyTargeClass属性,通过该属性,我们可以控制是使用基于接口的代理还是基于类的代理,如代码清单所示。                                                                                                                                                                                 ,.

<aop:config>内部可以有三个子元素,分别是<aop:pointcut.<aop:advisor>和<aop aspect>。

在这个顺序的基础上,我们可以根据需要声明多个并列的相同元素。 另外,我们还可以在同一个配置文件中配置多个<aop:config->, 如代码清单所示。

对于<aop:config>来说,底层基本上是使用1.x中的自动代理机制实现的。相应的自动代理实现 类,会根据该元素内部对应的Pointcut、Advisor以及Aspect的子元素取得必要的织入信息,然后为容器 内注册的bean进行自动代理。所以,如果愿意,不用<aop:config>, 而依然使用明确指定 autoProxyCreator实现类的方式也是可以的。

警告通常情况下,使用<aop:config>和直接使用AutoProxyCreator两种方式最好不要一起 使用,因为这可能造成一些问题,比如某些Advice逻辑没有织入系统.在Spring的参考文档和 Support论坛上都提到过这个问题.

在大体了解了基于Schema的配置方式之后,让我们来深入看一下各个元素的配置与具体开发之间 的关系吧}

向基于Schema的AOP迁移

单纯的迁移

1.x 版本的SpringAOP通过Advisor的概念对横切关注点进行封装。当把相应的Pointcut定义和Advice 定义注册到容器之后(通常是在基于DTD的XML配置文件中),通过声明相应的Advisor实现,将这些 Pointcut以及Advice定义装配到一起,最后通过某个AutoPoxyCreator进行最后的织入。

在转向2.x版本基于Schema的配置方式之后,这些概念实际上是相同的,唯一需要改变的是具体配 置方式的改变。现在,使用<aop:advisor>代替各种具体的Advisor实现类的bean定义声明,使用 <aop:config>取代各种AutoProxyCreator。

如果将PerformanceMethodinterceptor基于Schema的方式进行配置,那么配悝文件看起来 将如代码清单所示。

现在的配置看起来是不是要更具描述性呢?

对于PerformanceMethodinterceptor和它对应的Pointcut的bean定义来说,与之前基于DTD 的配置方式没有什么改变,按照通常的注册方式就可以。当然,对于Pointcut的定义来说,我 们可以替换任何我们喜欢用的Pointcut实现类。

在<aop:config>中使用<aop:advisor>配置相应的Advisor, 也就是特定于Spring AOP的 Aspect •• <aop: advisor>的如下几个属性可供使用。

 id。指定当前Advisor定义的标志过。

pointcut-ref。通过这个属性指定当前Advisor所对应的Poin~ut定义是什么,需要指定容 器中注册的具体的Pointcut对象引用。

advice-ref。指定当前Advisor对应的Advice对象引用。

order。指定当前Adviosr的顺序号,因为基本上所有的Advisor实现都实现了Ordered接口。

基本上,<aop:advisor>就是一个个具体的Advisor的bean定义的对等体。

到目前为止,<aop: ·advisor>和<aop:config>可以不加任何条件地满足我们之前的AOP配置需 求。不过,它们的组合可不仅限于这些。

深入挖掘 aop advisor 

对于<aop:advisor>所使用的Pointcut定义来说,除了可以使用pointcut-ref来指定具体的1.x版 本中各种Pointcut实现类的引用,我们还有如下两种选择。

使用pointcut而不是pointcut-ref

<aop:advisor>还有一个pointcut属性,但它不对之前的aop开放。通过该属性,我们只能指定AspectJ形式的Pointcut表达式,所以,如果要使用它,不要忘记将AspectJ的类库添加到应用程序的ClassPath中。代码清单演示了<aop:advisor>的pointcut属性的使用。

现在,直接通过pointcut这个属性指定AspectJ形式的Pointcut表达式就可以,不用为了当前的 Advisor定义单独声明一个Pointcut实现类的bean定义引用。

使用aop pointcut

前面讲述过,<aop:config>内部可以有一个或者多个<aop:pointcut>声明,这些声明因为是独 立定义的,所以,可以被<aop:advisor>甚至<aop:aspect>所引用。

可以依然使用<aop:advisor>的pointcut-ref属性指定Pointcut定义的引用。不过,这次,不是 指向1.x版本中独立声明的Pointcut对象引用,而是指向<aop:pointcut>, 如代码清单所示。

如果指定<aop:pointcut>的type为regex, 那么可以为expression指定正则表达式形式的 Pointcut描述。如果指定<aop:pointcut>的type为aspectj, 那么可以为expression指定AspectJ形式 的Pointcut表达式。默认情况下,不明确指定type, 将按照AspectJ形式的Pointcut表达式进行解析。不 过,不得不提到的是,关于type的语义只是理论上的,实际上,当指定type的值为regex时,框架在 处理表达式解析的时候依然采用的是Aspectj形式的解析规则。我在Spring Support论坛上提到过这个问 题,回答是AopNamespaceHandler可能忽略了这种情况的处理。所以,如果这个问题在新的Spring 版本中没有修正,应该避免使用type为regex的形式(JIRA SPR-4180 issue)。

注意通过type="regex"的<aop:pointcut> +<aop:advisor>, 或者<aop:advisor> +独 立的Pointcut对象声明,是在以新的配置方式来使用1.x版本的AOP, 没有任何附加; 通过 type="aspectj"的<aop :pointcut> + <aop :advisor>, 或者直接通过pointcut属性来使用 <aop:advisor>, 则是扩展了1.x版本的AOJl, 可以让1.x版本的AOP使用AspectJ形式的Pointcut 表达式.

 @AspectJ到“基于Schema的AOP"迁移

如果你想使用@AspectJ形式的Aspect和Advice声明方式,而又不能使用Java 5甚至更高版本的JVM, 或者你就是想使用基于XML配萱文件的AOP使用方式,那么恭喜你,基于Schema的AOP可以 满足这方面的要求。

@AspectJ形式的AOP采用相应的注解来标注必要的信息,如哪个对象是Aspect定义,Aspect定义 中哪个方法对应的是什么类型的Advice声明,或者Pointcut的定义是什么等。在我们使用基于Schema 的AOP之后,Aspect和Advice的定义还是基于POJO的,不过那些通过注解表达的信息全部都移到了 XSD形式的XML配置文件中。

让我们详细看一下原来@AspectJ形式的各种实体,在基于Schema的AOP中是如何表达的。

基于Schema的Aspect声明

基于Schema的Aspect声明由两部分组成: Aspect的定义和Aspect到容器的配置。

Aspect的定义

我们依然使用POJO来声明Aspect, 只不过,用不用@Aspect来标注这个POJO已经不再重要。因为 Aspect的信息将通过Schema配置文件来捕捉。

使用基于Schema的AOP之后,我们的Aspect定义就是一个最为普通的POJO了,如以下代码所示:

Aspect定义到容器的配置

将SchemaBasedAspect像通常的bean定义那样注册到容器之后,我们需要使用<aop:aspect>来 引用它,如下所示:

<aop:aspect>是<aop:config>的子元素,这在前面讲述过,它有三个属性。

id。Aspect定义在配萱文件中的标志id。

ref。通过ref指向Aspect定义对应的容器内的bean定义。

order 。Aspect定义对应的顺序号,用于控制Aspect内定义的Advice在同一个Joinpoint的执行顺 序。

基于Schema的Pointcut

基于Schema的Pointcut声明(使用<aop:pointcut>)可以位于两个位置:一个位置是直接声明到 <aop:config>下面,这样的Pointcut定义可以在其余的Advisor定义和Aspect定义中共享引用。

另一个 位置就是<aop:aspect>元素内部,这种Pointcut只能在其所声明的<aop:aspect>内部引用,相当于修 饰符为private的定义。不管<aop:pointcut>声明位于什么位置,它们的可配置项是相同的,唯一不 同的就是它们可以引用的范围。

关于<aop:config>下的<aop;pointcut>, 我们在已经讲述过了,而在 <aop:aspect>下声明<aop:pointcut>与之没有任何不同,如下所示:

通常情况下,直接通过expression指定Pointcut:就可以了。如果我们使用了Java5或者更高 版本,而且以@AspectJ形式声明了Aspect, 那么还可以在expression中指定Pointcut定义的Pointcut Signature, 如下所示:

以上内容假设SystemCommonsAspect类中以@AspectJ形式定义了系统中公用的Pointcut定义。

另外,因为通常Pointcut可以进行逻辑运算,AspectJ语法中的逻辑运算符是&&、||和!,而在XML 中,&&是限制字符,所以,在基于Schema的AOP中,我们使用and、or和not来代替原始的逻辑运绊 符。当框架内部解析这些Pointcut表达式时,将会对表达式中的and、or和not关键字进行替换。

基于Schema的Advice声明

在基于Schema的AOP中的Advice声明也分为两部分,即Advice的定义和Advice的到容器的配置。 对于Advice的定义来说很简单,实际上就是Aspect定义类中的一个个方法定义,方法参数按照需要进行声明。除了Around Advice对应的方法定义第一个参数是ProceedingJoinPoint类型外,其他类型 的Advice定义对应的方法声明没有太多说头。所以,我们重点来看一下这几种Advice类型在基于 Schema的AOP中是如何配置的。

Before Advice

基于Schema的AOP中Before Advice对应的配置元素是位于<aop:aspect>内的<aop:before>。假设我们在SchemaBasedAspect中声明的Before Advice定义如下所示:

那么,可以通过<aop:before>通知AOP框架,SchemaBasedAspect中的doBefore方法就是我们的Before  Advice定义,如下所示:

我们通过<aop:before>的pointcut-ref指定Before Advice所引用的Pointcut定义。不过同样可以 使用它的pointcut属性直接指定具体的Pointcut表达式。因为Advice定义属于Aspect内定义的方法,所 以,配置Advice的时候可以通过<aop:before>的method属性指定具体的Before Advice对应的方法名 称。

After Returning Advice

如果我们在schemaBasedAspect中声明一个对应After Returning Advice的方法定义,如代码清单所示。

当然,我们可能需要在After Returning Advice中访问方法的返回值,这时,可以通过<aop: after-returning>的returning属性指定返回参数名称,井在After Returning Advice对应的方法定义 的参数列表中增加返回值的卢明,如下代码演示了针对这种情况的处理:

唯一需要注意的就是通过returning指定的值,需要与方法声明中的参数名称相同。

After Throwing Advice

After Throwing Advice的Schema声明使用<aop:aspect>内的<aop:after-throwing>元素。

如果我们在SchemaBasedAspect中声明一个用于处理RuntimeException的After Throwing Advice, 如代码清单所示。

同样需要注意的是,throwing指定的值要与方法定义的参数名称相同。

After (Finally) Advice

After Advice同样用于释放系统资源相关场景,在基于Schema的AOP中,使用<aop:aspect>中的 <aop: after>对其进行配置

Around Advice

我想看到这里你应该头都大了吧?除了配置使用的具体元素不同,实现和配置也都差不多嘛。对, 不过,坚持一下,还剩一个。

Around Advice与之前的几个Advice类型的声明和配置都差不多,唯一的区别可能就是Around Advice的定义,即第一个参数必须是org.aspectj.lang.ProceedingJoinPoint类型,而且是必须指定的,不像其他几个Advice对应的方法定义,第一个参数类型为o:rg.aspectj.lang. JoinPoint, 而且可以根据需要决定是否声明。

如果在SchemaBasedAspect中声明一个Around Advice定义如代码清单。

Introduction

在原来@AspectJ形式的AOP中,我们通过在Aspect中为目标对象声明新增加的接口类型的实例变 量,然后在这个变量基础上标注注解的方式来声明Introduction。现在转向基于Schema的AOP之后,我 们不用在Aspect中定义任何东西,现在所有的信息全部通过Schema形式的配置文件进行配置。我们唯 一要做的,就是根据Introdution的需要,实现新的接口以及该接口的实现类,然后通过基于Schema的 配置文件将其注册到容器就可以了。

如果要为目标对象增加计数功能,那么我们在定义了ICounter接口和它的实现类Counterimpl 之后,可以通过XSD形式的配置文件对Introduction的定义进行配置,如代码清单所示。

如我们所见,基于Schema的Introduction的定义是通过<aop:aspect>下的<aop:declare-parents>进行的:

types-matching。用于指定要对哪些目标对象进行Introduction逻辑的织入。

implement-interface。指定新增加的Introduction行为的接口定义类型。

Default-impl。指定新增加的Introduction行为的接口定义的默认实现类。

在将该配置加入容器的基于Schema的配置文件之后,所有en. spring21 . unvei lspring. target 包下的目标对象将被添加!Counter接口定义的行为。

Advice的参数化

与@Aspectj形式的AOP样,在基于Schema的AOP中,Aspect内声明的各个Advice对应的方法定 义,可以接受指定类型的参数声明,只要按照规定的顺序声明参数即可。

在各个Advice对应的Pointcut定义中通过args、@annotation等标志符指定了要绑定的参数名称之 后,我们就可以在相应的Advice方法上声明对应的参数。当Advice逻辑被调用时,这些参数的参数值 将被绑定到方法调用。

如果我们要明确这些参数名称,也可以通过各个Advice对应的Schema配置元素的arg-names属性 明确指定。不过,通常情况下,只要按照顺序定义Pointcut和Acivice方法的参数声明,就可以省去 arg-names的使用。

Advice的执行顺序

当多个Advice需要在同一个Joinpoint处执行时,多个Advice的执行顺序与之前在@AspectJ形式的 AOP部分提及的内容相同,即同一个Aspect内声明的多个Advice, 优先级由它们声明的顺序决定,最 先声明的Advice拥有较高的优先级;位于多个不同Aspect内的Advice, 它们的执行顺序由Aspect的顺序 决定,具体语义以Ordered接口定义为准。

在基于Schema的AOP中,要指定Aspect的顺序号,可以通过<aop:aspect>的order属性进行,如 下所示:

Aspect的实例化模式

@AspectJ形式的AOP, 除了支持默认的singleton模式的实例化,还支持perthis和pertarget实例化模 式。不过,对于基于Schema的AOP来说,它的Aspect只支持singleton实例化模式。

发布了524 篇原创文章 · 获赞 80 · 访问量 15万+

猜你喜欢

转载自blog.csdn.net/xushiyu1996818/article/details/104111221