AOP一世 基于XML的AOP

目录

Spring AOP中的Joinpoint

Spring AOP中的Pointcut 

常见的Pointcut

NameMatchMethodPointcut

JdkRegexpMethodPointcut和Perl5RegexpMethodP.ointcut

AnnotaticmMatchingPointcut

ComposablePointcut

ControlPlowPointcut

 扩展Pointcut (Customize Pointcut)

自定义StaticMethodMatcherPointcut

自定义DynamicMetbodMatcnerPointout

loC容器中的Pointcut

 Spring AOP中的Advice

per-class类型的Advice

Before Advice

ThrowsAdvice

AfterReturningAdvice

Around Advice

per-instance类型的Advice

Introduction

Delegatingintroductioninterceptor

DelegatePerTargetObject­Introductioninterceptor

Spring AOP中的Aspect

PointcutAdvisor家族

DefaultPointcutAdvisor

NameMatchMethodPointcutAdvisor

RegexpMethodPointcutAdvisor

DefaultBeanFactoryPointcutAdvisor

IntroductionAdvisor分支

Ordered的作用

Spring AOP的织入

如何与ProxyFactocy打交道

基于接口的代理

基于类的代理

Introduction的织入

看清ProxyFactory的本质

容器中的织入器一—ProxyFactoryBean

ProxyFactoryBean的本质

ProxyFactocyBean的使用

加快织入的自动化进程

自动代理得以实现的原理

可用的AutoProxyCreator

BeanNameAutoProxyCreator

DefaultAdvisorAutoProxyCreator

扩展AutoProxyCreator

TargetSource

可用的Target Source实现类

singletonTargetSource

 PrototypeTargetSource

HotSwappableTargetSource

commonsPoolTargetSource

ThreadLocalTargetSource

自定义TargetSource


Spring AOP中的Joinpoint

之前我们已经提到,AOP的Joinpoint可以有许多种类型,如构造方法调用、字段的设置及获取、 方法调用、方法执行等。但是,在Spring AOP中,仅支待方法级别的Joinpoint。更确切地说,只支持 方法执行(Method Execution)类型的Joinpoint。这一点我们从节就能看出来。

虽然SpringAOP仅提供方法拦截,但是在实际的开发过程中,这已经可以满足80%的开发需求了。

所以,我们不用过于担心Spring AOP的能力。

Spring AOP之所以如此,主要有以下儿个原因。

(1)前面说过了,SpringAOP要提供一个简单而强大的AOP框架,并不想因大而全使得框架本身过 于腺肿。如果能够仅付出20%的努力,就能够得到80%的回报,这难道不是很好吗? Keep It Simple, Stupid原则指导我们抛弃旧有的EJB2时代的思想和膜式,它同样适用在这里;否则,事倍功半,并不 是想看到的结果。

(2)对于类中属性(Field)级别的Joinpoint, 如果提供这个级别的拦截支持,那么就破坏了面向对 象的封装,而且,完全可以通过对setter和getter方法的拦截达到同样的目的。

(3)如果应用需求非常特殊,完全超出了SpringAOP提供的那80%的需求支持,不妨求助于现有的 其他AOP实现产品,如AspectJ。目前来看,AspectJ是Java平台对AOP支持最完善的产品,同时,Spring AOP也提供了对AspectJ的支持。(不过,需要注常的是,AspectJ也不是完全支持所有类型的Joinpoint, 如程序中的循环结构。部分原因应该归结为要实现这20%的需求,可能需要付出80%的工作和努力。)

Spring AOP中的Pointcut 

Spring中以接口定义org.springframework.aop.Pointcut作为其AOP框架中所有Pointcut的顶层抽象,该接口定义了两个方法用来帮助捕捉系统中的相应Joinpoint并提供了一个TruePointcut 类型实例。

如果Pointcut类型为TruePointcut, 默认会对系统中的所有对象,以及对象上所有被支持 的Joinpoint进行匹配。org.springframework.aop.Pointcut接口定义如以下代码所示:

ClassFilter和MethodMatcher分别用于匹配将被执行织入操作的对象以及相应的方法。之所以 将类型匹配和方法匹配分开定义,是因为可以重用不同级别的匹配定义,并且可以在不同的级别或者 相同的级别上进行组合操作,或者强制让某个子类只覆写(Override)相应的方法定义等。

ClassFilter的作用是对Joinpoint所处的对象进行Class级别的类型匹配,其定义如下:

当织入的目标对象的Class类型与Pointcut所规定的类型相符时,matches方法将会返回true, 否 则,返回false, 即意味不会对这个类型的目标对象进行织入操作。比如,如果我们仅希望对系统 中Foo类型的类执行织入,则可以如下这样定义ClassFilter:

当然,如果类型对我们所捕捉的Joinpoint无所谓,那么Pointcut中使用的ClassFilter可以直接使 用"ClassFilter TRUE=TueclassFilter.INSTANCE"。

当Pointcut中返回的ClassFilter类型为 该类型实例时,Pointcut的匹配将会针对系统中所有的目标类以及它们的实例进行。

相对于Class Filter的简单定义,MethodMatcher则要复杂得多。毕竟,Spring主要支持的就是 方法级别的拦截一一“重头戏”可不能单薄啊! MethodMatcher定义如下:

MethodMatcher通过重载(Overload) , 定义了两个matches方法,而这两个方法的分界线就是 isRuntime ()方法。在对对象具体方法进行拦截的时候,可以忽略每次方法执行的时候调用者传入的 参数,也可以每次都检查这些方法调用参数,以强化拦截条件。假设对以下方法进行拦截:

如果只想在login方法之前插入计数功能,那么login方法的参数对于Joinpoint捕捉就是可以忽略 的。而如果想在用户登录的时候对某个用户做单独处理,如不让其登录或者给予特殊权限,那么这个 方法的参数就是在匹配Joinpoint的时候必须要考虑的。
(1)在前一种情况下,isRuntime返回false, 表示不会考虑具体Joinpoint的方法参数,这种类型 的MethodMatcher称之为staticMethodMatcher。因为不用每次都检查参数,那么对于同样类型的方 法匹配结果,就可以在框架内部缓存以提高性能。

isRuntime方法返回false表明当前的MethodMatcher为StaticMethodMatcher的时候,只有boolean matches(Method method, Class targetClass) 方法将被执行,它的匹配结果将会成为其所属的Pointcut主要依据。
(2)当isRuntime方法返回扛ue时,表明该MethodMatcher将会每次都对方法调用的参数进行匹 配检查,这种类型的MethodMatcher称之为DynamicMethodMatcher。因为每次都要对方法参数进行 检查,无法对匹配的结果进行缓存,所以,匹配效率相对于StaticMethodMatcher来说要差。而且大 部分情况下,StaticMethodMatcher已经可以满足需要,最好避免使用DynamicMethodMatcher类型。

如果一个MethodMatcher为DynamicMethociMatcher (isRuntime ()返回true) ; 并且当方法boolean rnatches(Method method, Class targetClass); 也返回true的时候,三个参数的matches方法将 被执行,以进一步检查匹配条件

如果方法boolean matches (Method method, Class targetClass)返回false, 那么不管这个MethodMatcher是StaticMethodMatcher还是DynamicMethodMatcher, 该结果已经是最终的匹配结果一一你可以猜得到,三个参数的matches方法那铁定是执行不了了。
在MethodMatcher类型的基础上,Pointcut可以分为两类,即StaticMethodMatcherPoint和 dynamicMethodMatcherPointcut。因为StaticMethodMatcherPointcut具有明显的性能优势,所 以,Spring为其提供了更多支持。图给出了Spring AOP中各Pointcut类型之间的一个局部“族谱”

常见的Pointcut

总地来说,图给出了较为常用的几种Pointcut实现。

NameMatchMethodPointcut

这是最简单的Pointcut实现,属于StaticMethodMatcherPointcut的子类,可以根据自身指定的 一组方法名称与Joinpoint处的方法的方法名称进行匹配,比如:

 

但是,NameMatchMethodPointcut无法对重载(Overload)的方法名进行匹配,因为它仅对方法 名进行匹配,不会考虑参数相关信息,而且也没有提供可以指定参数匹配信息的途径。

NameMatchMethodPointcut除了可以指定方法名,以对指定的Joinpoint进行匹配,还可以使用* 通配符,实现简单的模糊匹配,如下所示:

如果基于*, 通配符的NameMatchMethodPointcut依然无法满足对多个特定Joinpoint的匹配需 要,那么使用正则表达式好了。

JdkRegexpMethodPointcut和Perl5RegexpMethodP.ointcut

StaticMethodMatcherPointcut的子类中有一个专门提供基于正则表达式的实现分支,以抽象类 AbstractRegexpMethodPointcut为统帅。与NameMatchMethodPointcut相似,AbstractRegexp­MethodPointcut声明了pattern和patterns属性,可以指定一个或者多个正则表达式的匹配模式 (Pattem)。

其下设JdkRegexpMethodPointcut和Perl5RegexpMethodPointcut两种具体实现,就如你在图中所看到的那样。

JdkRegexpMethodPointcut的实现基于JDK 1.4之后引入的JDK标准正则表达式。如果想使用该 Pointcut实现,那么首先需要保证应用程序是运行在1.4或者更高版本的JVM之上。JdkRegexpMethod­Pointcut的简单使用示例如下:

注意,使用正则表达式来匹配相应的Joinpoint所处的方法时,正则表达式的匹配模式必须以匹配 整个方法签名(Method Signature)的形式指定,而不能像NameMatchMethodPointcut那样仅给出匹 配的方法名称。也就是说,如果有对象定义如下:

那么,使用正则表达式.*doSth.*则会匹配Bar的doSth方法,即相当于cn.spring21.demo.Bar.doSth。但如果Pointcut使用doSth.*作为匹配的正则表达式模式,就无法捕捉到Bar的doSth方法的执 行。当然,也可以通过正则表达式中的转义,更确切地指定对doSth方法的匹配: cn\ . spring21 \. sample\. Bar\ .doSth。不过,这已经属于正则表达式如何使用的范畴了。

如果当前应用还无法使用JDK 1.4或者更高版本,或者我们更喜欢perl5风格的正则表达式,那么 可以使用Perl5RegexpMethodPointcut。该Pointcut实现使用Jakarta ORO提供正则表达式支持,所以, 在使用之前,请先将JakartaORO的jar包加入应用程序的classpath中。除了正则表达式的语法上可能有 少许差异,Perl5RegexpMethodPointcut的使用和需要注意的问题与JdkRegeXMethodJointcut几 乎相同,如下所示。

(1)可以通过pattern或者pattems对象属性指定一个或者多个正则表达式的匹配模式。

(2)指定的正则表达式匹配模式应该覆盖匹配整个方法签名,面不是只指定到方法名称部分。 有关该类使用的正则表达式的具体风格,请参照JakartaORO的相应文档。

AnnotaticmMatchingPointcut

不好意思,JDK又升级了。AnnotationMatchingPointcut只能用在使用JDK5或者更商版本的 应用中,因为注解是在Java 5 (Tiger)发布后才有的。

AnnotationMatchingPointcut根据目标对象中是否存在指定类型的注解来匹配Joinpoint, 要使 用该类型的Pointcut, 首先需要声明相应的注解。代码清单给出了两个我们将用到的注解的定义。

注解定义中的@Target指定该注解可以标注的类型。对于我们声明的两个注解来说就是, ClassLevelAnnotation用于类层次,而MethodLevelAnnotation只能用于方法层次。代码清单给出了这两个注解的使用示例。

针对代码清单中的GenericTargetObject类型,不同的AnnotationMatchingPointcut定义 会产生不同的匹配行为。

AnnotationMatchingPointcut仅指定类级别的注解,GenericTargetObject类中所有方法执行 的时候,将全部匹配,不管该方法指定了注解还是没有指定。

也可以通过AnnotationMatching­Pointcut的静态方法,来构建类级别的注解对应的AnnotationMatchingPointcut实例:

如果只指定方法级别的注解而忽略类级别的注解,则该Pointcut仅匹配特定的标注了指定注解的方 法定义,而忽略其他方法。对于GenericTargetObject, 或者其他类中任何方法来说,只要它标注了 Method.LevelAnnotation, 则这些方法将都会被匹配并织入相应的横切逻辑。

通过同时限定类级别的注解和方法级别的注解,我们可以进一步缩小“包围圈”。现在,只有标注 了@ClassLevelAnnotation的类定义中同时标注了@MethodLevelAnnotation的方法才会被匹配,二 者缺一不可。对于我们的GenericTargetObject来说,只有它的gMethod1 ()方法才会被拦截,而 gMethod2 ()则不会。

注解作为外部配置方式的另一种应用配置方式(或者更高一步,Attribute-Oriented Programming) , 已经愈发受到更多开发人员的青眯。从早期的xdoclet、Jakarta Commons Attributes, 到Javas引入正式 的注解支持,我们现在几乎到处都能吞到注解的身影—--Guice ... AspectJ甚至这里的AnnotationMat­chingPointcut。不过,注解配置方式与外部的配置方式并不冲突,只要合理利用,二者都可以发挥更大的作用。稍后,我们就可以看到这两种方式的强大结合。                               .

ComposablePointcut

之前在介绍Pointcut概念的时候,我们说过,Pointcut通常还提供逻辑运算功能,而Composable­Pointcut就是SpringAOF提供的可以进行Pointcut逻辑运红的Pointcut实现。它可以进行Pointcut之间的 ”并“以及“交”运算,如:

我们说了,Pointcut定义根据ClassFilter和MethodMatcher划分为两部分,一部分是为了重用这 些定义,另一部分是为了可以相互组合。而通过ComposableJointcut, 我们可以很明白地行出这两 个目的,以下代码演示了这一点:

C中tpesablePointcut pointcut3 == pointcut2.union(classFilterl) .intersection(methodMatcherl); 我们在pointcutl和pointcut3中复用了classFilterl和methodMatcher-1以及pointcut2的定 义,同时,还进行了Pointcut同Cla.ssFilter以及MethodMatcher之间的逻辑组合运算。

当然,如果只想进行Poiotcut与Pointcut之间的逻辑组合运算,Spring AOP提供了org.spring.framework. aop. support . Pointcuts工具类,其简单使用示例如下所示:

ControlPlowPointcut

较之其他类型的Pointcut类型,最最最特殊的Pointcut类型,ControlFlowPointcut在理解和使用 上都需要我们多付出点儿脑细胞。虽然ControlFlowPointcut不是很常用,但某些场合可能需要用到,所以,还是应搞消楚这个Pointcut的特点和使用方式; ControlFlowPointcut匹配程序的调用流程,不是对某个方法执行所在的Joinpoint处的单一特征 进行匹配。

假设我们所拦截的目标对象(Target Object)有方法声明如代码清单

如果使用之前的任何Pointcut实现,我们只能指定在TargetObject的methodl方法每次执行的时 候,都织入相应横切逻辑。也就是说,一旦通过Pointcut指定methodl处为Joinpoint, 那么对该方法的 执行进行拦截是必定的,不管methodl是被谁调用。而通过ControlFlowPointcut, 我们可以指定, 只有当TargetObject的method1方法在TargetCaller类所声明的方法中被调用的时候,才对 methodl方法进行拦截,其他地方调用methodl的话,不对method1进行拦截。如图

虽然method1可以被多个对象在不同的执行流程内被调用,但是通过ControlFlowPointcut, 我 们可以指定,只有按照图中实线所标注的执行流程,才会触发:methodl处的Joinpoint, 而其他经过 method1这个Joinpoint处的程序调用执行流程,则不会触发该Joinpoint处的横切逻辑织入操作。

使用ControlFlowPointcut, 我们对以上需求的Pointcut表述,如代码清单所示。

当织入器按照Pointcut的规定,将Advice织入到目标对象之后,从任何其他地方调用method1, 是 不会触发Advice所包含的横切逻辑的执行的。只有在ControlFlowPointcut规定的类内部调用目标对 象的method1, 才会触发Advice中横切逻辑的执行。

令注意 实例代码中的weaver为虚构概念,不是Spring中的织入器实现.稍后马上会为你奉上 SpringAOP使用什么原理以及使用什么工具作为织入器的相关内容。

如果在ControlFlowPointcut的构造方法中单独指定Class类型的参数,那么ControlFlow­Pointcut将尝试匹配指定的Class中声明的所有方法,跟目标对象的Joinpoint处的方法流程组合。

所以, 如果只是想完成"TargetCaller. callMethod ()调用TargetObject.method1 ()''这样的流程匹配, 而忽略TargetCalier中其他方法与TargetObject中方法的Control Flow匹配,我们可以同时在构造 ControlFlowPointcut的时候,传入第二个参数,即调用方法的名称

我们的TargetCaller现在就声明了一个方法(即callMethod)调用了TargetObject的方法,所 以,new ControlFlowPointcut (TargetCaller. class, callMethod"); 形式的声明与newControl­FlowPointcut{TargetCaller.class); 在当前场景下会产生相同的效果。

因为ControlFlowPointcut类型的Pointcut需要在运行期间检查程序的调用栈,而且每次方法调 用都需要检查,所以性能比较差。如果不是十分必要,应该尽量避免这种Pointcut的使用。

 扩展Pointcut (Customize Pointcut)

虽然我认为以上SpringAOP提供的Pointcut已经足够使用,但却无法保证一定就没有更加特殊的需 求,以致于以上Pointcut类型都无法满足要求。这种情况下,我们可以扩展Spring AOP的Pointcut定义, 给出自定义的Pointcut实现。

要自定义Pointcut, 不用白手起家,SpringAOP已经提供了相应的扩展抽象类支持,我们只需要继 承相应的抽象父类,然后实现或者覆写相应方法逻辑即可。前面已经讲过,SpringAOP的Pointcut类型 可以划分为StaticMethodMatcherPointcut和切namicMethodMatcherPointcut两类。要实现自定 义Pointcut, 通常在这两个抽象类的基础上实现相应子类即可。

自定义StaticMethodMatcherPointcut

StaticMethodMatcherPointcut根据自身语意,为其子类提供了如下儿个方面的默认实现。

默认所有StaticMethodMatcherPointcut的子类的ClassFilter均为ClassFilter. TRUE, 即忽略类的类型匹配。如果具体子类需要对目标对象的类型做进一步限制,可以通过public void setClassFilter(ClassFilter classFilter)方法设置相应的ClassFilter实现。

因为是StaticMethodMatcherPointcut, 所以,其MethoctMatcher的isRuntime方法返回 false, 同时三个参数的matches方法抛出unsupportedOperationException异常,以表示 该方法不应该被调用到。

最终,我们需要做的就是实现两个参数的matches方法了。

如果我们想提供一个Pointcut实现,捕捉系统里数据访问层的数据访问对象中的查询方法所在的 Joinpoint, 那么可以实现一个StaticMethodMatcherPointcut, 如下:

自定义DynamicMetbodMatcnerPointout

DynamicMethodMatcherPointcut也为其子类提供了部分便利。

(1) getClassFilter ()方法返回ClassFilter.TRUE, 如果需要对特定的目标对象类型进行限定, 子类只要覆写这个方法即可。

(2)对应的MethodMatcher的isRuntirne总是返回true, 同时,StaticMethodMatcherPointcut 提供了两个参数的matches方法的实现,默认直接返回true.

要实现自定义DynamicMethodMatcherPointcut, 通常情况下,我们只需要实现三个参数的 matches方法逻辑即可。代码清单给出了一个自定义的PKeySpecificQuecyMethodPointcut实现类。

如果愿意,我们也可以覆写一下两个参数的matches方法,这样,不用每次都得到三个参数的 matches方法执行的时候才检查所有的条件。

loC容器中的Pointcut

Spring中的Pointcut实现都是普通的Java对象,所以,我们同样可以通过Spring的IoC容器来注册并使用它们。

如果某个Pointcut自身需要某种依赖,可以通过loC容器为其注入。或者如果容器中的某个对象需 要依赖于某个Pointcut, 也可以把这个Pointcut注入到依赖对象中。不过,通常在使用Spring AOP的过 程中,不会直接将某个Pointcut注册到容器,然后公开给容器中的对象使用。这一点稍后将详细讲述。 只不过,需要说明的就是,将各个Pointcut以独立的形式注册到容器使用是完全合情合理的,如下所示:

 Spring AOP中的Advice

Spring AOP加入了开源组织AOP Alliance (http://aopalliance.sourceforge.net/)。,目的在于标准化 AOP的使用,促进各个AOP实现产品之间的可交互性。鉴于此,Spring中的Advice实现全部遵循AOP Alliance规定的接口。图中就是Spring中各种Advice类型实现与AOP Alliance中标准接口之间的关系 (Introduction型的Advice各单独讲解)。

Advice实现了将被织入到Pointcut规定的Joinpoint处的横切逻辑。在Spring中,Advice按照其自身 实例(instance)能否在目标对象类的所有实例中共享这一标准,可以划分为两大类,即per-class类型 的Advice和per-instance类型的Advice。

per-class类型的Advice

per-class类型的Advice是指,该类型的Advice的实例可以在目标对象类的所有实例之间共享。这种 类型的Advice通常只是提供方法拦截的功能,不会为目标对象类保存任何状态或者添加新的特性

除了图中没有列出的Introduction类型的Advice不属于per-class类型的Advice之外,图中的所有 Advice均属此列。

per-class类型的Advice会是我们最常接触的Advice类型,所以,先从它们开刀!

Before Advice

我们先从最简单的Advice类型——Before Advice 说起。

Before Advice所实现的横切逻辑将在相应的Joinpoint之前执行,在Before Advice执行完成之后, 程序执行流程将从Joinpoint处继续执行,所以Before Advice通常不会打断程序的执行流程。但是如果 必要,也可以通过抛出相应异常的形式中断程序流程。

要在Spring中实现Before Advice, 我们通常只需要实现org.springframewqrk.aop.MethodBeforeAdvice接口即可,该接口定义如下:

就像我们在图中所看到的那样,MethodBeforeAdvice继承了BeforeAdvice, 而BeforeAdvice 与AOP的Advice一样,都是标志接口,其中没有定义任何方法。

我们可以使用Before Advice进行整个系统的某些资源初始化或者其他一些准备性的工作。当然, 其应用场景井非仅限于此,各位可以根据具体情况选择是否使用Before Advice。

假设我们的系统需要在文件系统的指定位置生成一些数据文件(系统实现中可能存在多处这样的 位觉),创建之前,我们需要首先检查这些指定位觉是否存在,不存在则需要去创建它们。为了避免 不必要的代码散落,我们可以为系统中相应目标类提供一个Before Advice, 对文件系统的指定路径进 行统一的检查或者初始化。代码清单给出了用于初始化指定的资源路径的ResourceSetup­BeforeAdvice定义。

ThrowsAdvice

Spring中以接口定义org.springframework.aop.ThrowsAdvice对应通常AOP概念中的After­ThrowingAdvice。虽然该接口没有定义任何方法,但是在实现相应的ThrowsAdvice的时候,我们的 方法定义需要遵循如下规则:

其中,三个参数可以省略。我们可以根据将要拦截的Throwable的不同类型,在同一个 ThrowsAdvice中实现多个AfterThrowing方法。框架将会使用Java反射机制(Java Reflection)来调 用这些方法,如代码清单所示。

ThrowsAdvice. 通常用于对系统中特定的异常情况进行监控,以统一的方式对所发生的异常进行处 理,我们可以在一种称之为Fault Barner的模式中使用它。当然,我们更应该根据具体应用的场景来发 挥ThrowsAdvice的最大作用。

能够马上想到的例子可能就是对系统中的运行时异常(RuntimeException)进行监控,一旦捕 捉到异常,需要马上以某种方式通知系统的监控人员或者运营人员,假设我们通过email的方式发送通 知,我们可以实现如代码清单所示的一个ThrowsAdvice。

该ThrowsAdvice实现中使用了Spring为JavaMail服务提供的抽象层,我们将在后面详细介绍该特 性。你可以在当前ExceptionBarrierThrowsAdvice的基础上进行扩展,如添加更多配置项,或者进 一步丰宫邮件内容。

AfterReturningAdvice

org.springframework.aop.AfterReturningAdvice接口定义了Spring的AfterReturningAdvice, 其定义如下:

通过String中的AfterReturningAdvice, 我们可以访问当前Joinpoint的方法返回值、方法、方法 参数以及所在的目标对象。

因为只有方法正常返回的情况下,AfterReturningAdvice才会执行,所以用来处理资源消理之 类的工作井不合适。不过,如果有需要方法成功执行后进行的横切逻辑,使用AfterReturningAdvice 倒比较合适。

另外,虽然String的AfterReturningAdvice可以访问到方法的返回值,但不可以更改 返回值。这一点与通常的AfterReturningAdvice的特性有所出入。好在,如果真想这么做,通过其 他Advice类型还是可以做到的,如稍后即将隆重推出的Spring中的Around Advice实现。

因为Spring中的AfterReturningAdvice不能对方法返回值进行更改,所以其应用场景大受影响。

不过,或多或少,还是可以寻找到某些应用的场景的。想一个更有魅力的示例有些费脑筋,所以,直 接拿我遇到的场景来说吧。为了便于运营人员验证系统状态,FX的批处理程序在正常完成之后会往数 据库的指定表中插入运行状态,运营人员可以通过验证这些状态判断相应的批处理任务是否成功执 行,所以,我们可以实现一个AfterReturning Advice对所有批处理任务的执行进行拦截。该 AfterReturningAdvice实现见代码清单

很简单,是吧!不过,如果不用AOP的话,散落到各个批处理任务中的类似逻辑可就不是两行代 码可以搞定的了。

Around Advice

Spring AOP没有提供After (Finally).Advice, 使得我们没有一个合适的Advice类型来承载类似 于系统资源消除之类的横切逻辑。Spring AOP的AfterReturningAdvice不能更改Joiopoint所在方法 的返回值,使得我们在方法正常返回后无法对其进行更多干预。不过,有了Around Advice, 这些问题 就都不是问题了。

Spring中没有直接定义对应Aiound Advice的实现接口,而是直接采用AOP Alliance的标准接口, 即org.aopalliance.intercept.Methodinterceptor, 该接口定义如下:

当然,从本节一开始的UML类图(图中就可以看出这个苗头了。

Methodinterceptor作为Around Advice那可是神通广大!之前提到的几种Advice能完成的事情, 对于Methodinterceptor来说简直是不在话下。所以,Methodinterceptor, 或者说Around Advice 可以应用的场景那是相当地多啊!系统安全验证及检查、系统各处的性能检测、简单的日志记录以 及系统附加行为的添加等。

为了演示Methodinterceptor的使用以及需要注意的问题,我们以“简单的检测系统某些方法的 执行性能”为例,实现一个Ferformanceinterceptor, 如代码清单

通过MethodInterceptor的invoke方法的Methodinvocation参数,我们可以控制对相应 Joinpoint的拦截行为。通过调用Methodinvocation的proceed()方法,可以让程序执行继续沿着调用 链传播,这是通常我们所希望的行为。

如果我们在哪一个Methodinterceptor中没有调用proceed(), 那么程序的执行将会在当前Methodinterceptor处“短路 , JoinPoint上的调用链将被中断,同一 Joinpoint上的其他Methodinterceptor的逻辑以及Joinpoint处的方法逻辑将不会被执行。除非你真的 知道自己在做什么,否则,不要忘记调用proceed()方法哦!另外,我们还可以通过Methodinv?cation 对象取得Joinpoint的更多信息。

正如在PerforrnanceMethodinterceptor中所看到的那样,我们可以在proceed(}方法,也就是 Joinpoint处的逻辑执行之前或者之后插入相应的逻辑,甚至捕获proceed方法可能抛出的异常。到 现在,你是否可以理解为什么Methodinterceptor可以完成其他类型Advice可以完成的任务了呢?

为了进一步演示Methodinterceptor的使用,我们可以设想这样的场景: 如果某个销售系统规 定,在商场优惠期间,所有商品一律8折出售(或者其他折扣条件),那么我们就应该在系统中所有 取得商品价格的位置插入这样的横切逻辑。之前使用AfterReturningAdvice无法做的事情,现在我 们使用Spring的Around Advice, 也就是Methodinterceptor来做好了。代码清单中声明的 DiscountMethodlnterceptor即用于此目的。

我们可以直接通过编程的方式来使用该类,如以下代码所示:

既然我们使用了Spring框架并且这些Advice 实现都是普通的POJO, 更多时候,会直接将其集成到IOC容器中,如下所示:

per-instance类型的Advice

与per-class类型的Advice不同,per-instance类型的Advice不会在目标类所有对象实例之间共享,而 是会为不同的实例对象保存它们各自的状态以及相关逻辑。

就拿上班族为例(或许是比较痛苦的例子, 呵呵),如果员工是一类人的话,那么公司的每一名员工就是员工类的不同对象实例。每个员工上班 之前,公司设置了一个per-class类型的Advice进行“上班活动”的一个拦截,即打卡机,所有的员工都公 用一个打卡机。当每个员工进入各自的位置之后,他们就会使用各自的电脑进行工作,而他们各自的 电脑就好像per-instance类型的Advice一样,每个电脑保存了每个员工自己的资料。

在Spring AOP中,Introduction是唯一的一种:Per-instance型Advice。

Introduction

Introduction可以在不改动目标类定义的情况下,为目标类添加新的属性以及行为。这就好比我们 开发人员,如果公司人员紧张,没有配备浏试人员,那么,通常就会给我们扣上一顶`测试人员"的帽 子,让我们同时进行系统的测试工作,实际上,你还是你,只不过多了点儿事情而已。

在Spring中,为目标对象添加新的属性和行为必须声明相应的接口以及相应的实现。这样,再通 过特定的拦截器将新的接口定义以及实现类中的逻辑附加到目标对象之上。之后,目标对象(确切地 说是目标对象的代理对象)就拥有了新的状态和行为。这个特定的拦截器就是org.springframework. aop. Introductioninterceptor, 其定义如下:

Introductioninterceptor继承了Methodinterceptor以及DynamicintroductionAdvice。

通 过DynamicintroductionAdvice, 我们可以界定当前的Introductioninterceptor为哪些接口类提 供相应的拦截功能。

通过Methodinterceptor, Introductioninterceptor就可以处理新添加的接 口上的方法调用了。

毕竞,原来的目标对象不会处理自己认为没有的东西啊。另外,通常情况下,对 于Introductioninterceptor来说,如果是新培加的接口上的方法调用,不必去调用Method­Interceptor的proceed()方法。毕竟,当前位置已经是“航程”的终点了(当前被拦截的方法实际上 就是整个调用链中要最终执行的唯一方法)。

如果把每个目标对象实例看作盒装牛奶生产线上的那一盒盒牛奶的话,那么生产合格证就是新的 Introduction逻辑,而Introductioninterceptor就是把这些生产合格证贴到一盒盒牛奶上的那个 “人”。

因为Introduction较之其他Advice有些特殊,所以,我们有必要从总体上看一下Spring中对 Introduction的支持结构,给出了Introduction相关的类图结构。

lntroductionlnterceptor是从哪里出来的,前面已经讲过了,从中也可以看出相关的继承 层次关系。我们要说的是实现Introduction型的Advice的两条分支,即以DynamicintroductionAdvice为 首的动态分支和以Introductioninfo为首的静态可配置分支。

从上面DynamicintroductionAdvice 的定义中可以看出,使用DynamicintroductionAdvice , 我们可以到运行时再去判定当前Introduction 可应用到的目标接口类型,而不用预先就设定。而Introducdonlnfo类型则完全相反,其定义如下;

实现类必须返回预定的目标接口类型,这样,在对Introductioninfo型的Introduction进行织入的时候,实际上就不需要指定目标接口类型了,因为它自身就带有这些必要的信息。

要对目标对象进行拦截并添加Introduction逻辑,我们可以直接扩展Introductioninterceptor, 然后在子类的invoke方法中实现所有的拦截逻辑。不过,除非特殊状况下需要去直接扩展Introduc­tioninterceptor, 大多数时候,直接使用Spring提供的两个现成的实现类就可以了。

Delegatingintroductioninterceptor

从名字也可以看的出来,Delegatingintroductioninterceptor不会自己实现将要添加到目标 对象上的新的逻辑行为,而是委派(delegate)给其他实现类。不过这样也好,职责划分可以更加明确 嘛!

就以简化的开发人员为例来说明Delegatingintroductioninterceptor的用法吧!我们声明 IDeveloper接口及其相关类,如代码清单所示。

使用Delegatingintroductioninterceptor为Developer添加新的状态或者行为,我们可以按 照如下步骤进行。

(1)为新的状态和行为定义接口。我们要为Developer添加测试人员的职能,首先需要将需要的职 能以接口定义的形式声明。这样,就有了ITester声明,如干:

(2)给出新接口的实现类。接口实现类给出将要添加到目标对象的具体逻辑。当目标对象将要行 使新的职能的时候,会通过该实现类寻求帮忙。代码清单给出了针对ITester的实现类。

我们可以在接口实现类中添加相应的属性甚至辅助方法,就跟实现通常的业务对象一样。

(3)通过Delegatingintroductioninterceptor进行Introduction的拦截。有了新增加职能的接口 定义以及相应实现类,使用Delegatingintroductioninterceptor, 我们就可以把具体的Introduction 拦截委托给具体的实现类来完成,如下代码演示了这一过程:

(4) Introduction的最终织入过程在细节上有需要注意的地方,我们将在后面提到。虽然,Delegatingintroductioninterceptor是Introduction型Advice的一个实现,但你可能料想不到的是,它 其实是个“伪军”,因为它的实现根本就没有兑现Introduction作为per-instance型Advice的承诺。

实际上, Delegatingintroductioninterceptor会使用它所持有的同一个delegate接口实例,供同一目标 类的所有实例共享使用。你想啊,就持有一个接口实现类的实例对象,它往哪里去放对应各个目标对 象实例的状态啊?所以,如果要真的想严格达到Introduction型Advice所宣称的那样的效果,我们不能 使用Delegatingintroductioninterceptor, 而是要使用它的兄弟,DelegatePerTargetObject­Introductioninterceptor。

DelegatePerTargetObject­Introductioninterceptor

与Delegatingintroductioninterceptor不同,DelegatePerTargetObjectintroductionin­terceptor会在内部持有一个目标对象与相应Introduction逻辑实现类之间的映射关系。

当每个目标对 象上的新定义的接口方法被调用的时候,DelegatePerTargetObjectlntroductionlnterceptor会 拦截这些调用,然后以目标对象实例作为键,到它持有的那个映射关系中取得对应当前目标对象实例 的Introduction实现类实例。剩下的当然就是,让当前目标对象实例吃自己家锅里的饭了。如果根据当 前目标对象实例没有找到对应的Introduction实现类实例,DelegatePerTargetObjectIntroduction­Interceptor将会为其创建一个新的,然后添加到映射关系中。

使用DelegatePerTargetObjectintroductioninterceptor与使用Delegatingintroductioninterceptor没有太大的差别,唯一的区别可能就在于构造方式上。现在我们不是自己构造delegate 接口实例,而只需要告知DelegatePerTargetObjectintroductioninterceptor相应的delegate接 口类型和对应实现类的类型。剩下的工作留给DelegatePerTargetObjectlntroductioninter­ceptor就可以了,如以下代码所示:

当然啦,如果Delegatingintroductioninterceptor和DelegatePerTargetObjectintroduc­tioninterceptor默认的invoke方法实现逻辑无法满足你的需求,你也可以直接扩展这两个类,覆写 (Override)相应的方法。

不过,不知为什么,Delegatingintroductioninterceptor和DelegatePerTargetObjectintroduc­tioninterceptor自身实现上对扩展有所限制,实例变量没有提供可以公开 给子类的途径,一些应该声明为protected以便子类共享的方法也没有放开,而是声明为private。

Delegatinglntroductioninterceptor倒是可以通过它的无参数的构造方法进行扩展,但要求子类 必须同时实现新的Introduction逻辑的接口。DelegatePerTargetOojectintroductioninterceptor 干脆就没有发现什么有用的可扩展点。所以,给我的感觉就是,直接扩展这两个类跟直接扩展Introductioninterceptor相比,好像也没有太多优势。希望Spring T臼m之后能够修改这两个类, 以便能够更方便地进行扩展。

要扩展Introductionlnterceptor或者Delegatingintroductioninterceptor和Delegate­PerTargetObjectintroductioninter-ceptor, 通常是因为目标对象的行为,与新附加到目标对象 的状态和行为相关联。这时,在处理两方面的方法调用的时候,就倘要根据情况添加新的调用处理逻 辑一一假设Developer要进行开发的时候,检测到其作为Tester本身也在忙活,Developer要“罢工,, , 我们可以实现拥有类似逻辑的Introductioninterceptor实现,如代码清单所示。

最后要说的是Introduction的性能问题。与AspectJ直接通过编译器将Introduction织入目标对象不 同,SpringAOP采用的是动态代理机制,在性能上,Introduction型的Advice要逊色不少。如果有必要, 可以考虑采用AspectJ的Introduction实现。

Spring AOP中的Aspect

当所有的Pointcut和Advice准备好之后,就到了该把它们分门别类地装进箱子的时候了。你知道我 说的箱子是什么,对吧?当然是Aspect。

在解释Aspect的概念的时候曾经提到过,Spring中最初没有完全明确的Aspect的概念,但是,这并 不意味谙就没有。只不过,Spring中的这个Aspect在实现和特性上有所特殊而已。

Advisor代表Spring中的Aspect, 但是,与正常的Aspect不同,Advisor通常只待有一个Pointcut和一 个Advice。而理论上,Aspect定义中可以有多个Pointcut和多个Advice, 所以,我们可以认为Advisor 是一种特殊的Aspect。

为了能够更清楚Advisor的实现结构体系,我们可以将Advisor简单划分为两个分支,一个分支以 org.springframework.aop.PointcutAdvisor为首,另一个分支则以org.springframework.aop. IntroductionAdvisor为头儿,如图所示。

PointcutAdvisor家族

实际上,org. springfrarmework.aop.PointcutAdvisor才是真正的定义一个Pointcut和一个 Advice的Advisor, 大部分的Advisor实现全都是PointcutAdvisor的“部下”

下面我们就来行一下几个常用的PointcutAdvisor实现。

DefaultPointcutAdvisor

DefaultPointcutAdvisor是PointcutAdvisor的“大弟子”,是最通用的PointcutAdvisor实 现。除了不能为其指定Introduction类型的Advice之外,剩下的任何类型的Pointcut、任何类型的Advice都可以通过DefaultPointcutAdvisor来使用。

我们可以在构造DefaultPointcutAdvisor的时候, 就明确指定属于当前DefaultPointcutAdvisor实例的PointcutAdvice. 也可以在DefaultPointcutAdvisor实例构造完成后,再通过setPointcut以及setAdvice方法设置相应的PointcutAdvice(使 用示例见代码清单)。

此处给出代码并不是让你在实际的环境中就这么用,而是为了演示事实的真相。实际上,Spring 中任何的bean都可以通过loC容器来管理,SpringAOP中的任何概念对此也同样适用。大多数时候,我 们会通过IoC容器来注册和使用Spring AOP的各种概念实体。

通常使用Spring的loC容器注册管理DefaultPointcutAdvisor的情形,如代码清单

NameMatchMethodPointcutAdvisor

NameMatchMethodPointcutAdvisor是细化后的DefaultPointcutAdvisor, 它限定了自身可以 使用的Pointcut类型为NameMatchMethodPointcut, 并且外部不可更改。不过,对于使用的Advice来 说,除了Introduction, 其他任何类型的Advice可以使用。

NameMatchMethodPointcutAdvisor内部持有一个NameMatchMethodPointcut类型的Pointcut实 例。

当通过NameMatchMethodPointcutAdvisor公开的setMappedName和setMappedNames方法设置 将被拦截的方法名称的时候,实际上是在操作NameMatchMethodPointcutAdvisor所持有的这个 NameMatchMethodPointcut实例。

NameMatchMethodPointcutAdvisor的使用也很简单,通过编程方式还是通过IoC容器都可以, 编程方式使用示例如下:

通过IoC容器使用的情形,见代码清单

RegexpMethodPointcutAdvisor

与NameMatchMethodPointcutAdvisor类似,RegexpMethodPointcutAdvisor也限定了自身可 以使用的Pointcut的类型,即只能通过正则表达式为其设置相应的Pointcut。

RegexpMethod.PointcutAdvisor自身内部持有一个AbstractRegexpMethodPointcut的实例。希 望你还记得,AbstractRegexpMethodPointcut有两个实现类,即Perl5RegexpMethodPointcut和 JdkRegexpMethodPointcut。默认情况下,RegexpMethodPointcutAdvisor会使用JdkRegexp­MethodPointcut。如果要强制使用Per15RegexpMethodPointcut, 那么可以通过RegexpMethod­PointcutAdvisor的setPerl5(boolean)达成所愿。

RegexpMethodPointcutAdvisor提供了许多构造方法,我们可以在构造时就指定Pointcut的正则 表达式匹配模式以及相应的Advicet也可以构造完成之后再指定,在使用上与其他的Advisor实现并无 太多差别。我们这里只演示在IoC容器中的配觉使用方式(见代码清单)。

DefaultBeanFactoryPointcutAdvisor

DefaultBeanFactoryPointcutAdvisor是使用比较少的一个Advisor实现,因为自身绑定到了 BeanFactory, 所以,要使用DefaultBeanFactoryPointcutAdvisor, 我们的应用铁定要绑定到Spring 的IoC容器了。而且,通常情况下,DefaultPointcutAdvisor已经完全可以满足需求。

DefaultBeanFactoryPointcutAdvisor的作用是,我们可以通过容器中的Advice注册的 beanName来关联对应的Advice。只有当对应的Pointcut匹配成功之后,才去实例化对应的Advice, 减 少了容器启动初期Advisor和Advice之间的耦合性。

要使用DefaultBeanFactoryPointcutAdvisor, 我们通常需要在容器的配置文件中进行如代码 清单所示的配置。

注意,对应advice的配置属性名称为adviceBeanName, 而它的值就对应advice的beanName。

除了这一点,与DefaultPointcutAdvisor使用并无二致。

IntroductionAdvisor分支

IntroductionAdvisor与PointcutAdvisor最本质上的区别就是,IntroductionAdvisor只能应 用于类级别的拦截,只能使用Introduction型的Advice, 而不能像PointcutAdvisor那样,可以使用任 何类型的Pointcut, 以及差不多任何类型的Advice。也就是说,IntroductionAdvisor纯粹就是为 Introduction而生的。

IntroductionAdvisor的类层次比较简单,只有一个默认实现DefaultintroductionAdvisor, 其继承层次见图。

既然IntroductionAdvisor仅限于Introduction的使用,那么DefaultintroductionAdvisor的使用也比较简单,只可以指定Introduction型的Advice (即IntroductionInterceptor)以及将被拦截的接口类型。其使用示例见代码清单

我们也可以指定Advice以及一个Introductioninfo对象类来构造DefaultintroouctionAdvisor, 因为Introductioninfo可以提供必要的目标接口类型。代码清单是结合Introductionlnfo使用 的DefaultintroductionAdvisor的使用示例。

不用奇怪为什么我们在构造DefaultintroductionAdvisor的时候传入两个"introducetion­Interceptor" , 它们两个其实是不一样的,前者是作为Introduction型的Advice实例,后者则是作为 Introductioninfo的实例。不要忘了Delegatingintroductionlnterceptor实现了Introduciton­Info接口哦!

Ordered的作用

系统中只存在单一的横切关注点的情况很少,大多数时候,都会有多个横切关注点需要处理,那 么,系统实现中就会有多个Advisor存在。当其中的某些Advisor的Pointcut匹配了同一个Joinpoint的时 候,就会在这同一个Joinpoint处执行多个Advice的横切逻辑。如果这些Advisor所关联的Advice之间没 有很强的优先级依赖关系,那么谁先执行,谁后执行都不会造成任何影响。而一且这儿个需要在同一 Joinpoint处执行的Advice逻辑存在优先顺序依赖的话,就需要我们来干预了,否则,系统的行为就会 偏离我们的预想。

记得有一天,同事大鹏突然问我,说:“头儿,这个任务初始化的时候抛出异常但没被我们的 ThrowsAdvice截获,帮看一下唤?“我心里纳闷,不能吧?查了一下Pointcut的正则表达式定义,没错啊,应该能捕获到啊。最后查到抛出异常的是应用到同一个方法的Advice所抛出的,我才猛然醒悟…...

现在假设有两个Advisor, 一个进行权限检查,当检查到当前调用没有权限的时候,抛出相应异常, 称为PermissionAuthAdvisor; 另一个Advisor,用一个ThrowsAdvice对系统中的所有需要检测的异 常进行拦截,称其为ExceptionBarrierAdvisor。如果以如下形式声明这两个Advisor, 就不会有问题:

即使,PermissionAuthAdvisor的Advice抛出异常,:我们的ExceptionBarr-ierAdvisor也可以捕获该异常并进行系统内的统一处理。而如果我们像如下这样,颠倒它们两个的声明顺序,那就有问题了:

在PermissionAuthAdvisor中的Advice抛出异常之后,ExceptionBarrierAavisor并没有起作 用,问题出在哪儿呢?

Spring在处理同一Joinpoint处的多个Advisor的时候,实际上会按照指定的顺序和优先级来执行它 们,顺序号决定优先级,顺序号越小,优先级越高,优先级排在前面的,将被优先执行。我们可以从 0或者1开始指定,因为小于0的顺序号原则上由Spring AOP框架内部使用。默认情况下,如果我们不 明确指定各个Advisor的执行顺序,那么Spring会按照它们的声明顺序来应用它们,最先声明的顺序号 最小但优先级最大,其次次之。

有了这些前提,我们就可以知道为什么仅颠倒两个Advisor的顺序就会造成某个Advisor失效。让 我们来看图。

在图中,左边是正常的情况,当调用流程在PermissionAuthAdvisor中出现间题时,甚至是 在目标对象上出现问题时,ExceptionBarrierAdvisor调用流程返回的时候捕获到相应异常言而 右边就是调换顺序后的结果,可以看到现在PermissionAuthAdvisor上出现问题的话,因为调用流程 已经经过了ExceptionBarrierAdvisor, 所以,ExceptionBarrierAdvisor根本无法捕获PermissionAuthAdvisor上的异常(虽然目标对象上的问题可以捕获到)。

虽然我们可以通过调整配置中各个Advisor声明的顺序来避免以上问题,但是,这并非最彻底的解 决方法。最彻底的方法就是,为每个Advisor明确指定顺序号。在Spring框架中,我们可以通过让相应 的Advisor以及其他顺序紧要的bean实现org.springfram.ework.core.Ordered接口来明确指定相应 顺序号。

不过,从图中也应该行到了,各个Advisor实现类,其实已经实现了Ordered接口。我们无 需自己去实现这个接口了,唯一要做的是直接在配置的时候指定顺序号。代码清单中的配贺为我 们的两个Advisor指定了明确的顺序号,从而避免了最初问题的出现。

Spring AOP的织入

俗话说得好,“万事俱备,只欠东风叮各个模块我们已经实现好了,剩下的工作,就是拼装各个 模块。

要进行织入,AspectJ采用ajc编译器作为它的织入器; JBossAOP使用自定义的ClassLoader作为它 的织入器;而在Spring AOP中,使用类org.springfrarnework.aop.framework.ProxyFactory作为 织入器。

如何与ProxyFactocy打交道

首先需要声明的是,ProxyFactory并非SpringAOP中唯一可用的织入器,而是最基本的一个织入 器实现,所以,我们就从最基本的这个织入器开始,来窥探一下Spring AOP的织入过程到底是一个什 么样子。

使用ProxyFactory来进行横切逻辑的织入很简单。我们知道,SpringAOP是基于代理模式的AOP 实现,织入过程完成后,会返回织入了横切逻辑的目标对象的代理对象。为ProxyFactory提供必要 的“生产原材料”之后,ProxyFactory就会返回那个织入完成的代理对象(如以下代码所示):

使用ProxyFactory只需要指定如下两个最基本的东西。

第一个是要对其进行织入的目标对象。我们可以通过ProxyFactory的构造方法直接传入,也 可以在ProxyFactory构造完成之后,通过相应的setter方法进行设置。

第二个是将要应用到目标对象的Aspect。哦,在Spring里面叫做Advisor, 呵呵。不过,除了可 以指定相应的Advisor之外,还可以使用如下代码,直接指定各种类型的Advice。

weaver.addAdvice(...);

对于Introduction之外的Advice类型,ProxyFactory内部就会为这些Advice构造相应的 Advisor, 只不过在为它们构造的Advisor中使用的Pointcut为Pointcut.TRUE, 即这些“没穿 衣服”的Advice将被应用到系统中所有可识别的Joinpoint处;

而如果添加的Advice类型是Introduction类型,则会根据该Introduction的具体类型进行区分:

如果是Introductionlnfo的子类实现,因为它本身包含了必要的描述信息,框架内部会为其构 造一个Default Introduction.Advisor; 而如果是DynarnicintroductionAdvice的子类实 现,框架内部将抛出AopConfigException异常(因为无法从DynamicintroductionAdvice 取得必要的目标对象信息)。

但是,在不同的应用场景下,我们可以指定更多ProxyFactory的控制属性,以便让Proxy Factory 帮我们生成必要的代理对象。我们知道,SpringAOP在使用代理模式实现AOP的过程中采用了动态代 理和CGLIB两种机制,分别对实现了某些接口的目标类和没有实现任何接口的目标类进行代理,所以, 在使用ProxyFactory对目标类进行代理的时候,会通过Proxy Factory的某些行为控制属性对这两种 情况进行区分。

在继续下面内容之前,有必要先设定一个简单的场杂,以便大家结合实际情况来查看和分析在不 间场兼下,Proxy Factory在使用方式上的细微差异。假设我们的目标类型定义如下:

有了要拦截的目标类,还得有织入到Joinpoint处的横切逻辑,也就是要用到某个Advice实现。我 们就把之前的PerformanceMethodinterceptor先拿来一用

有了这些之后,让我们来看一下使用ProxyFactocy对实现了ITask接口的目标类,以及没有实现 任何接口的目标类如何进行代理。

基于接口的代理

MockTask实现了ITask接口,要对这种实现了某些接口的目标类进行代理,我们可以为ProxyFactory明确指定代理的接口类型,如下所示:

通过setinterfaces (}方法可以明确告知ProxyFactory, 我们要对:ITask接口类型进行代理。

另 外,在这里,我们通过NameMatchMethodPointcutAdvisor来指定Pointcut 相应的Advice (Perfor­manceMethodinterceptor)。至于什么类型的Pointcut、Advice以及Advisor, 我们完全可以根据个人 的喜好或者具体场景来使用,举一反三嘛!

不过,如果没有其他行为属性的干预,我们也可以不使用set Interfaces ()方法明确指定具体的 接口类型。这样,默认情况下,ProxyFactory只要检测到目标类实现了相应的接口,也会对目标类进行基于接口的代理,如下所示:

简单点儿说,如果目标类实现了至少一个接口,不管我们有没有通过Proxy Factory的set Interfaces(}方法明确指定要对特定的接口类型进行代理,只要不将Proxy Factory的optimiz忒扣roxy­TargetClass两个属性的值设置为true (这两个属性稍后将谈到),那么ProxyFactory都会按照面 向接口进行代理。

注意:面 向接口进行代理的结果,最后只能强转为接口,不能强转为类,这里就是不能转换为MockTask,

基于类的代理

如果目标类没有实现任何接口,那么,默认情况下,ProxyFactory会对目标类进行基于类的代 理,即使用CGLIB。假设我们现在有一个对象,定义如下:

如果使用Executable作为目标对象类,那么,ProxyFactory就会对其进行基于类的代理,如下

但是,即使目标对象类实现了至少一个接口,我们也可以通过proxyTargetClass属性强制 ProxyFactory采用基于类的代理。以MockTask为例,它实现了ITask接口,默认情况下ProxyFa.ctory 对其会采用基于接口的代理,但是,通过proxy'l'argetClass, 我们可以改变这种默认行为(见如下 代码):

除此之外,如果将ProxyFactory的optimize属性设定为true的话,ProxyFactory也会采用基于 类的代理机制。关于optimize属性的更多信息,我们将在后面给出。

总地来说,如果满足以下列出的三种情况中的任何一种,ProxyFactory将对目标类进行基于类 的代理。

如果目标类没有实现任何接口,不管proxyTargetClass的值是什么,ProxyFactory会采用 基于类的代理。

如果ProxyFactory的proxyTargetClass属性值被设置为true, ProxyFactory会采用基于类 的代理。

如果Proxy Factory的optimize属性值被设置为true, ProxyFactory会采用基于类的代理。

Introduction的织入

之所以将Introduction的织入单独列出,是因为Introduction型Advice比较特殊,如下所述。

Introduction可以为已经存在的对象类型添加新的行为,只能应用于对象级别的拦截,而不是 通常Advice的方法级别的拦截,所以,进行Introduction的织入过程中,不需要指定Pointcut, 而只需要指定目标接口类型。

Spring的Introduction支待只能通过接口定义为当前对象添加新的行为,所以,我们需要在织入 的时机,指定新织入的接口类型。

鉴于以上两点,使用Proxy Factory进行Introduction的织入代码示例如代码清单所示。

如果我们不使用Advisor而直接为ProxyFactory指定Advice的话,还记得Proxy Factory会如何处 理的嘛? ProxyFactory会在自身内部构建相应的Advisor来使用,对吧?因为TesterFeature­Introductionlnterceptor是Introductioninfo的子类,所以,ProxyFactory内部会创建一个默 认的DefaultlntroductionAdvisor实例,就跟我们注释掉的两行代码效果一样。

对Introduction进行织入,与基于接口的代理形式有点像,但有少许差异。

对Introduction进行织入, 新添加的接口类型必须是通过set Interfaces指定的,而原来的目标对象,是采用基于接口的代理形 还是采用基于类的代理形式,完全是可以自由选择的。

上面我们通过setinterfaces同时指定了目 标对象实现的接口和新添加的接口类型,在进行Introduction织入的同时使用了基于接口的代理形式。 我们同样可以在织入Introduction的同时,使用基于类的代理形式(见代码清单)。

我们通过weaver.setProxyTargetClass(true)强制使用了基于类的代理,所以,现在得将代 理对象转型为Developer而不是IDeveloper。

从介绍Advice类型到介绍Advisor类型,针对Introduction的部分都是单独陈述的,或许你已经猜到, Introduction的Advice以及Advisor是不能跟其他Advfoe和Advisor混用的,要织入Introduction, 你只能使 用IntroductionAdvisor或者其子类,而不能使用其他的组合。

看清ProxyFactory的本质

知其表而不知其里,充其岱你只能算一个画匠,而不是画师; 只懂得如何使用API而不知道这 些API为何如此设计,使你迈不出从“画匠”到“画师”的那一步,如果你想迈出这一步,那不妨随 我看一下这ProxyFactory内部到底有何"猫腻儿”,何如?

认识ProxyFactory的本质,不仅可以让我们清楚它如何实现,帮助我们在以后的系统设计中吸 取宝贵的经验,而且可以进一步帮助我们更好地使用ProxyFactocy。

要了解ProxyFactory, 我们得先从它的“根”说起,即 org.spr1.ngframework.aop.frarne- work. AopProxy, 该接口定义如下:

SpringAOP框架内使用AopProxy对使用的不同的代理实现机制进行了适度的抽象,针对不同的代 理实现机制提供相应的AopProxy子类实现。目前,Spring AOP框架内提供了针对JDK的动态代理和 CGLlB两种机制的AopProxy实现

当前,AopProxy有Cglib2AopProxy和JdkDynamic'AopProxy两种实现。因为动态代理需要通过 InvocationHandler提供调用拦截,所以,JdkDynamicAopProxy同时实现了InvocationHandler接 口。

不同AopProxy实现的实例化过程采用工厂模式(确切地说是抽象工厂模式)进行封装,即通过 org.springframework.aop.frarnework.AopProxyFactory进行。AopProxyFactory接口的定义如下 所示:

AopProxyFactory根据传入的AdvisedSupport实例提供的相关信息,来决定生成什么类型的 AopProxy。不过,具体工作会转交给AopProxyFactory的具体实现类。

而实际上这个AopProxy­Factory实现类现在就一个,即org.springframework.aop.framework.DefaultAopProxyFactory。

DefaultAopProxyFactory的实现逻辑很简单,如以下伪代码所示:

也就是说,如果传入的AdvisedSupport实例config的isOptimize或者isProxyTargetClass方 法返回true, 或者目标对象没有实现任何接口,则采用CGLIB生成代理对象,否则使用动态代理。

还 记得ProxyFactory会采用基于类的代理形式生成代理对象需要满足的条件吗?这里是一个关键点, 但是走到这里,你还是无法理解为什么ProxyFactory会有这种好像偶然的行为。别急,我们接着看!

AopProxyFactory需要根据createAopProxy方法传入的AdvisedSupport实例信息,来构建相应 的AopProxy。下面我们要看看这个AdvisedSupport到底是何方神圣。

说得简单一点儿,AdvisedSupport其实就是一个生成代理对象所需要的信息的载体,该类相关 的类层次图,见图

advisedSupport所承载的信息可以划分为两类,一类以org.springframework.aop.frarnework.ProxyConfig为统领,记载生成代理对象的控制信息

一类以org. springfrarnework.aop. framework. Advised为旗帜,承载生成代理对象所需要的必要信息,如相关目标类、Advice、Advisor等。

ProxyConfig其实就是一个普通的JavaBean, 它定义了5个boolean型的风性,分别控制在生成代 理对象的时候,应该采取哪些行为措施,下面是这5个属性的详细情况。

proxyTargetClass。如果proxyTargetClass属 性设置为true, 则Proxy Factory将会使用CGLIB对目标对象进行代理,默认值为false。

optimize。该属性的主要用于告知代理对象是否需要采取进一步的优化措施,如代理对象生 成之后即使为其添加或者移除了相应的Advice, 代理对象也可以忽略这种变动。另外,我们 也曾经提到,当该属性为true时,Pr-ox:yFactory会使用CGLIB进行代理对象的生成。默认情 况下,该属性为false。更多信息参照Spring的Javadoc以及参考文档。

opaque。该属性用于控制生成的代理对象是否可以强制转型为Advised, 默认值为false, 表. 示任何生成的代理对象都可强制转型为Advised, 我们可以通过Advised查询代理对象的一些 状态。

exposeProxy。可以让SpringAOP框架在生成代理对象时,将当前代理对 象绑定到ThreadLocal。如果目标对象需要访问当前代理对象,可以通过AopContext. currentProxy ()取得。该属性的用途将在后文中详细讲述。出于性能方面考虑,该属性默认 值为false。

frozen。如果将frozen设置为true, 那么一旦针对代理对象生成的各项信息配置完成,则不 容许更改。比如,如果ProxyFactory的设置完毕,并且fronzen为true, 则不能对Advice进 行任何变动,这样可以优化代理对象生成的性能。默认情况下,该值为false。

要生成代理对象,只有ProxyConfig提供的控制信息还不够,我们还需要生成代理对象的一些具 体信息,比如,要针对哪些目标类生成代理对象,要为代理对象加入何种横切逻辑等,这些信息可以 通过org. springframework. aop. framework.Advised设过或者查询。默认情况下,SpringAOP框架 返回的代理对象都可以强制转型为Advised, 以查询代理对象的相关信息。

Advised的接口定义代码太长,我们就不在此罗列了。简单点儿说,我们可以使用Advised接口访问 相应代理对象所持有的Advisor, 进行添加Advisor、移除Advisor等相关动作。即使代理对象已经生成 完毕,也可对其进行这些操作。直接操作Advised, 更多时候用于测试场景,可以帮助我们检查生成的 代理对象是否如所期望的那样。 (有关Advised的更多信息,请参照Spring的参考文档,因为与我们 的主题相关性不大,这里不进行详细讲述。)

回到之前的AdvisedSupport话题,AdvisedSupport继承了ProxyConfig, 我们可以通过 AdvisedSupport设置代理对象生成的一些控制属性。

AdvisedSupport同时实现了Advised接口,我 们也可以通过AdvisedSupport设置生成代理对象相关的目标类、Advice等必要信息。这样,具体的 AopProxy实现在生成代理对象时,可以从AdvisedSupport这里取得所有这些必要信息。

现在回到主题ProxyFactory。AopProxy、AdvisedSupport与ProxyFactory是什么关系呢?先 看图。

ProxyFactory集AopProxy和AdvisedSupport于一身(根据AdvisedSupport,产生AopProxy),所以,可以通过ProxyFactory设过生成 代理对象所需要的相关信息,也可以通过ProxyFactory取得最终生成的代理对象。前者是AdvisedSupport的职责,后者是AopProxy的职责。

为了重用相关逻辑,Spring AOP框架在实现的时候,将一些公用的逻辑抽取到了org.spring.framework.aop.framework.ProxyCreator Support中,它自身就继承了AdvisedSupport, 所以, 生成代理对象的必要信息从其自身就可以搞到。

为了简化子类生成不同类型AopProxy的工作,Proxy­CreatorSupport内部持有一个AopProxyFactocy实例,默认采用的是DefaultAopProxyFactory (也 可以通过构造方法或者setter方法设置其他实现,如果有的话)。

DefaultAopProxyFactory的默认行 为前面已经讲述过了。ProxyFactory作为一个ProxyCreatorSupport自然继承了这种行为,从它的 使用中我们已经领略过了。

前而已经说过了,Proxy Factory只是Spring.AOP中最基本的织入器实现。实际上,P.roxyFactory 还有几个“兄弟”,这从Proxy­CreatorSupport的继承类图(图)中可以看到。

容器中的织入器一—ProxyFactoryBean

虽然使用ProxyFactory, 可以让我们能够独立于Spring的IoC容器之外来使用Spring的AOP支持, 但是,将SpringAOP与Spring的IoC容器支持相结合,才是发挥SpringAOP更大作用的最佳途径。通过 结合Spring的IoC容器,我们可以在容器中对Pointcut和Advice等进行管理,即使它们依赖于其他业务 对象,也可以很容易地注入其中。

在IOC容器中,使用org.springframework.aop.framework.ProxyFactoryBean作为织入器,它的 使用与ProxyFactory无太大差别。不过在演示ProxyPactoryBean的使用之前,我们有必要在看消了 Proxy Factory本质的前提下,进一步弄明白ProxyFactoryBean的本质。

ProxyFactoryBean的本质

对于ProxyFactoryBean, 我们应该这样断词,即Proxy+PactoryBean. 而不是FroxyFactory+ Bean。

也就是说,ProxyFactoryBean本质上是一个用来生产Proxy的FactoryBean。还记得IoC容器中的 Factory Bean的作用吧?如果容器中的某个对象持有某个FactoryBean的引用,它取得的不是 FactocyBean本身,而是FactoryBean的getObject ()方法所返回的对象。所以,如果容器中某个对 象依赖于ProxyFactoryBean, 那么它将会使用到ProxyFactoryBean的getObject ()方法所返回的代 理对象,这就是ProxyFactoryBean得以在容器中游刃有余的原因。

要让ProxyFactoryBean的getObj ect ()方法返回相应目标对象类的代理对象其实很简单。因为 ProxyFactoryBean继承了与ProxyFactory共有的父类ProxyCreatorSupport, 而对Creator­Support. 基本上已经把要做的事情(如设置目标对象、配置其他部件、生成对应的AopProxy等)全部 完成了。我们只需在ProxyFactoryBean的getObj ect ()方法中通过父类的create.AOPProxy()取得相 应的AopProxy, 然后"return AopProxy. get Proxy ()''即可。

因为涉及FactoryBean, 所以在实现getObject()时,逻辑上还得点缀一下。我们来看FroxyFactoryBean的getObject()定义(见代码清单)。

FactoryBean定义中要求标明返回的对象是以singleton的scope返回,还是以prototype的scope返回。

所以,得针对这两种情况分别返回不同的代理对象,以满足FactoryBean的isSingleton ()方法的语义。

如果将ProxyFactoryBean的singleton设置为true, 则ProxyFactoryBean在第一次生成代 理对象之后,会通过内部实例singletoninstance (Object类型)缓存生成的代理对象。之后, 所有的请求将会返回这一缓存实例,从而满足singleton的语义。

反之,如果将ProxyFactor:yBean 的singleton属性为false, 那么,ProxyFactoryBean每次都会重新检测各项设置,井为当前调 用准备一套新的环境,然后再根据最新的环境数据,返回一个新的代理对象。因此,如果singleton 属性为false, 在生成代理对象的性能上存在损失。如果非要这么做,请确保有充足的理由。

singleton 默认值为true, 即返回同一个代理对象实例。

如果对ProxyFactoryBean的细节感兴趣,可以读一下ProxyFactoryBean的代码。

ProxyFactocyBean的使用

与ProxyFactor:y一样,通过ProxyFactoryBean, 我们可以在生成目标对象的代理对象的时候, 指定使用基于接口的代理还是基于类的代理方式,而且,因为它们全部继承自同一个父类,大部分可 设项目都相同。不过,ProxyFactoryBean在继承了父类ProxyCreatorSupport的所有配置属性之 外,还添加了几个自己独有的,如下所示。

proxyinterfaces。如果我们要采用基于接口的代理方式,那么需要通过该属性配置相应的 接口类型,这是一个Collection类型实例,所以我们可以通过配置元素<list>来指定一个或者 多个接口类型。实际上,这与通过Interfaces风性指定接口类型是等效的,我们完全可以随 个人喜好来使用,虽然使用ProxyInterfaces可以保持使用上的统一风格。另外,如果目标 对象实现了某个或者多个接口,即使我们不通过该属性指定要代理的接口类型, ProxyFactroyBean也可以自动检测到目标对象所实现的接口,并对其进行基于接口的代理。 因为ProxyFactoryBean有一个autodetectinterfaces属性,该属性默认值为true, 即如果 没有明确指定要代理的接口类型,ProxyFactoryBean会自动检测目标对象所实现的接口类型 并进行代理。

interceptorNames。通过该属性,我们可以指定多个将要织入到目标对象的Advice、拦截器 以及Advisor, 而再也不用通过ProxyFactory那样的addAdvice或者addAdvisor方法一个一个 地添加了。因为该属性属于Collection类型,所以通常我们会使用配置元素<list>添加需要的 拦截器名称。该属性有两个特性需要提及,如以下所述。

如果没有通过相应的设置目标对象的方法明确为ProxyFactory Bean设览目标对象,那么可 以在interceptorNames的最后一个元素位置,放咒目标对象的bean定义名称。这是个特例, 大部分情况下,还是建议明确指定目标对象,而避免这种配置方式。

通过在指定的interceptorNames某个元素名称之后添加*通配符,可以让Factory­Bean在容器中搜寻符合条件的所有的Advisor并应用到目标对象。这些符合条件的Advisor, Spring参考文档中称之为global aclvisor。代码清单给出了这种用法的示例。

singleton  因为ProxyFactoryBean本质上是一个FactoryBean, 所以我们可以通过singleton 属性,指定每次getObject调用是返回同一个代理对象,还是返回一个新的。通常情况下是返 回同一个代理对象,即singleton为true。只有在需要返回有状态的代理对象的情况下,才 会将singleton设置为false, 如使用Introduction的场合。

要在容器中通过ProxyFactoryBean使用基于接口的代理方式,通常可以采用代码清单所示 的配置方式。

现在,从Pointcut到Advice再到Advisor, 从目标对象到相应的代理对象,全部都由IoC容器统一管 理。为ProxyFactoryBean指定目标对象、要代理的接口类型以及相应的Advisor或Advice,

ProxyFactoryBean会返回目标对象的代理对象供调用者使用。我们可以将生成的代理对象直接注 入到依赖的主体对象中,但是这里有一个初学者容易犯的错误,就是通常会将目标对象task注入依赖 的主体对象,而不是目标对象的代理对象taskProxy。通过之前有关代理模式的讲解,现在应该不会 犯这种错误了。将没有织入任何横切逻辑的目标对象,而不是代理对象注入依赖的主体对象,一定不会 产生任何拦截效果。为了避免这种问题,如果没有依赖于目标对象的依赖关系,将目标对象的bean 定义声明为内部bean, 这样,就不会出现该引用目标对象代理对象的地方,反而因不慎或者其他原因而 引用目标对象本身的情况。代码清单演示了这种好的实践方式。

因为autodetectinterfaces的默认值为true; 如果确认目标对象所实现的接口就是要代理的接 口,那么,完全可以省略通过interfaces或者proxyinterfaces明确指定代理接口的配置。代码清单的配置内容可以精简如下:

如果没有指定要代理的接口类型,并且目标对象也没有实现任何接口,那么,ProxyFactory­Bean会采用基于类的代理方式为目标对象生成代理对象。不过,即使目标对象实现了某些接口,我们 也可以强制ProxyFactoryBe却采用基于类的代理方式来生成代理对象。与ProxyFactory一样,只要 指定proxyTargetClass为true就可以了(见代码清单)

不过,现在客户端代码不能将代理对象强制转型为ITask, 而应该强制转型为目标对象的具体类型,即MockTask如下所示:

有时,我们的应用可能需要依赖于第三方库,这些库中可能有些对象是出于简单实用的 目的,就是没有进行面向接口编程,自然就没有实现任何接口.而且,我们自己设计和实现的 类,可能出于某种目的,也是没有实现接口的必要,这时,就需要通过将proxyTargetClass设为true来解决代理的问题.

说完了如何通过ProxyFactoryBean生成目标对象的代理对象(使用“基于接口的代理”方式也 好,便用“基于类的代理”方式也好)。下面该说一下Introduction的代理了,因为它一直比较特立独 行嘛!

为了演示Introduction的织入,我们引入一个ICounter妾口定义以及一个简单实现类,然后将这个 接口的行为和状态添加到ITask-ffi应实现类中。ICounter接口以及相关实现类定义见代码清单。

要将ICounter的行为添加到ITask相应实现类中,

请注意,我们将目标对象的bean定义、ProxyFactoryBean的bean定义,以及相应Introduction­Interceptor的bean定义的scope, 全部声明为prototype, 也就是singleton=false, 井且,这种 情况下,我们使用的是"taskName"而不是task来指定目标对象(使用task通过ref指定prototype 类型的依赖会有什么效果,在Spring的loC容器部分已经讲述过了。)。这样才能保证每次取得的代理 对象都持有各自独有的状态和行为,如下是调用执行的代码示例:

我们之前说过,Delegatingintroductioninterceptor是一个“伪军”,如果不是采用prototype 的scope为每一个代理对象都分配一个该类型实例,则无法保证各代理对象拥有各自的状态。不过,如 果使用DelegatePerTargetObjectintroductioninterceptor, 那么可以共用一个该类型的Advice 实例(即使用sigleton的scope) • 见代码清单

至于你的自定义Introductioninterceptor, 在应用的时候,诸根据情况设置Introduction­Interceptor的scope以保证状态的独立性。有关ProxyFactoryBean的更多配置项细节,请参照对应 的Javadoc, 这里就不赘述了。我们得加快织入的速度了,毕竞,一个一个地配置ProxyFactoryBean 可不是什么令人感到轻松、愉快的事情。

加快织入的自动化进程

在IoC容器中使用ProxyFactoryBean进行横切逻辑的织入固然不错,但是,我们都是针对每个目 标对象,然后给出它们各自所对应的ProxyFactoryBean配赏。如果目标对象就那么儿个,那还应付 得过来。但系统中那么多的业务对象可能都是目标对象,如果还是用ProxyFactoryBean一个个地进 行配置,估计得累得吐血,所以,我们得寻求更加简单快捷的方式。

注意当然,如果系统小,而且横切关注点不多,目标对象少,那么简单地使用ProxyFactoryBean也不失为合适的方式。

SpringAOP给出了自动代理(AutoProxy)机制,用以帮助我们解决使用ProxyFactoryBean配置工作量比较大的问题。

自动代理得以实现的原理

要使用自动代理机制,需要以Spring的IOC容器为依托。更进一步说,需要使用Application­Context类型的IoC容器。虽然可以通过进一步的编码,让BeanFactory也支持这一功能,但是既然要 自动,还是一步到位的好。

Spring AOP的自动代理的实现建立在IoC容器的BeanPostProcessor概念之上。还记得我们可以 使用BeanPostProcessor干预bean的实例化过程吗?通过BeanPostProcessor, 我们可以在遍历容器 中所有bean的基础上,对遍历到的bean进行一些操作。有了这个前提条件,要实现自动代理就很容易了。

其实我们不难想到,只要提供一个BeanPostProcessor, 然后在这个BeanPostProcessor内部实 现这样的逻辑,即当对象实例化的时候,为其生成代理对象并返回,而不是实例化后的目标对象本身, 从而达到代理对象自动生成的目的。该逻辑如果以伪代码演示,如代码清单所示。

而对于Object proxy = createProxyFor {bean); 这行代码,如何根据目标对象生成相应的代理 , 对象的细节,我想就不用说了吧(不就是使用ProxyFactory或者ProxyFactoryBean嘛)!我要说的 是第一行,即检查当前bean定义是否符合拦截条件。

要检查当前bean是否符合拦截条件,首先需要知道拦截条件是什么,那么我们就要通过某种方式, 告知具体的自动代理实现类都有哪些拦截条件:

可以通过外部配置文件传入这些拦截条件信息,比如我们在容器的配置文件中注册的有关 Pointcut以及Advisor等就包括这些信息;

还可以在具体类的定义文件中,通过元数据来指明具体的拦截条件是什么,比如可以通过 Jakarta Commons Attributes或者Java 5的注解,直接在代码类中标注Pointcut等拦截信息。

但不管采用什么方式来提供拦截信息,我们都可以提供相应的自动代理实现类来读取这些信息, 并为相应的目标对象自动生成代理对象。

下面,让我们先看一下Spring AOP都提供了哪些可以使用的自动代理实现类,免得做一些无用功。

可用的AutoProxyCreator

Spring AOP在org. springframework. aop. framework. autoProxy包中提供了两个常用的 AutoProxyCreator, 即BeanNameAutoProxyCreator和DefaultAdvisorAutoProxyCreator。

BeanNameAutoProxyCreator

使用BeanNameAutoProxyCreator, 我们可以通过指定一组容器内的目标对象对应的beanName, 将指定的一组拦截器应用到这些目标对象之上。代码清单给出了通常情况下使用BeanNameAuto­ProxyCreator的配置演示。

通过beanNames, 我们可以指定要对容器中的哪些bean自动生成代理对象。通过interceptor­Names, 我们可以指定将要应用到目标对象的拦截器、Advice或者Advisor等。实际上,我们可以将以 上两个BeanNameAutoProxyCreator并作一个使用。给出至少两个BeanNameAutoProxyCreator定义, 只是为了表明,我们可以使用多个BeanNameAutoProxyCreator以细化横切逻辑的织入范围。

如果一类目标对象的bean定义名称相似,如可能我们系统中服务层的bean定义名称都以service结 尾,那么我们可以如代码清单所示,在beanNames属性中指定*通配符以简化配置。

如果在使用*通配符的情况下,我们还是要指定一长串的beanNames, 那么使用一下配置诀窍吧。

对于String[]型的数组,我们也可以不用<list>元素,而是如下所示,直接使用逗号分隔数组的各 个元素即可:

当然,这是容器配置中的小诀窍,与BeanNameAutoProxyCreator可没太大的关系。因为我们可 以在任何类似场合使用这种配置方式。

DefaultAdvisorAutoProxyCreator

如果把BeanNameAutoProxyCreator比作半自动步枪的话,那么DefaultAdvisorAutoProxy­Creator可算全自动步枪啦。使用DefaultAdvisorAutoProxyCreator, 我们只需要在Application­Context的配置文件中注册一下DefaultAdvisorAutoProxyCreator的bean定义就可以了。剩下的事 情,DefaultAdvisorAutoProxyCreator全部搞定。

代码清单给出了使用DefaultAdvisorAuto­ProxyCreator的配置演示。

将DefaultAdvisorAuto­ProxyCreator注册到容器后,它就会自动搜寻容器内的所有Advisor, 然 后根据各个Advisor所提供的拦截信息,为符合条件的容器中的目标对象生成相应的代理对象。注意, DefaultAdvisorAutoProxyCreator只对Advisor有效,因为只有Advisor才既有Pointcut信息以捕捉符 合条件的目标对象,又有相应的Advice。

使用DefaultAdvisorAutoProxyCreator对容器内所有bean定义对应的对象进行自动代理之后, 我们从容器中取得的对象实例,就都是代理后已经包含了织入的横切逻辑的代理对象了,除非该对象 不符合Pointcut规定的拦截条件。

因为DefaultAdvisorAutoProxyCreator的处理范围比较大,所以,为了避免将不必要的横切逻 辑织入到不需要的目标对象之上,应该尽世细化各个Advisor的定义,或者,转而使用BeanName­AutoPro)cyCreator来进行更加细粒度的织入范围控制。

提示 某些时候,如果系统中有许多目标对象类无法采用“基于接口的代理”形式进行拦截, 那么我们就需要明确告知AOP框架,在生成代理对象的时候应该采用“基于类的代理”形式。 使用自动代理之前,我们可能需要依次为每一个ProxyFactocyBean都指定一下 proxyTargetClass属性,但是使用了自动代理之后,因为所有AutoProxyCreator的父类都 继承自ProxyConfig, 所以只需要通过具体的AutoProxyCreator指定一下proxyTargetClass 属性,然后就可以控制所有代理对象的生成是采用“基于类的代理”形式了.对于Default­AdvisorAutoProxyCreator来说,是如下这个样子:

扩展AutoProxyCreator

如果BeanNameAutoProxyC工eator和DefaultAdvisorAutoProxyCreator不能满足我们的要求, 或者我们想要一种其他方式的自动代理,比如基于元数据的方式,那么可以在Spring AOP提供的 AbstractAutoProxyCreator或者AbstractAdvisorAutoProxyCreator之上,实现相应的子类, 而不用什么都重新开始。

下面我们看一下Spring AOP框架中有关自动代理的实现架构(见图)。

所有的AutoProxyCreator都是InstantiationAwareBeanPostProcessor, 这种类型的 BeanPost Processor与普通的BeanPostProcessor有所不同。当Spring loC容器检测到有Instantia­tionAwareBeanPostProcessor类型的BeanPostProcessor的时候,会直接通过Instantiation­加areBeanPostProcessor中的逻辑构造对象实例返回,而不会走正常的对象实例化流程,也就是我 说的“短路”。这样,相应的AutoProxyCreator会直接构造目标对象的代理对象返回,而不是原来 的目标对象。

要实现自定义的AutoProxyCreator, 可以像BeanNameAutoProxyCreator那样直接继承Abstra­ctAutoProxyCreator, 也可以像DefaultAdvisorAutoProxyCreator那样继承AbstractAdvisor­AutoProxyCreator。不管如何,子类中只需要提供规则匹配一类的逻组,如果必要,也可以覆写相 应逻辑。

AspectJAwareAdvisorAutoProxyCreator是Spring 2.0之后的AutoProxyCreaotr实现,也算是 一个AutoProxyCreator的自定义实现。它还有一个子类AnnotationAwareAspectJAutoP工oxy­Creator, 可以支持根据Java5的注解捕获信息以完成自动代理。

TargetSource

通常,在使用ProxyFactory的时候,我们都是通过set Target ()方法指定具体的目标对象。使用 ProxyFactocyBean也是如此,或者ProxyFactoryBean还可以通过setTargetName ()指定目标对象在 IoC容器中的bean定义名称。但除此之外,还有一种方式没有说,那就是还可以通过setTarget Source () 来指定目标对象。

TargetSource的作用就好像是为目标对象在外面加 了一个壳,或者说,它就像是目标对象的容器。当每个针 对目标对象的方法调用经历层层拦截而到达调用链的终 点的时候,就该调用目标对象上定义的方法了。但这时, SpringAOP做了点儿手脚,它不是直接调用这个目标对象 上的方法,而是通过“插足于“调用链与实际目标对象之 间的某个TargetSource来取得具体目标对象,然后再调 用从TargetSource中取得的目标对象上的相应方法。整 个情形如图所示

在通常情况下,无论是通过setTarget () , 还是通过 setTargetName ()等方法设置的目标对象,框架内部都 会通过一个Target Source实现类对这个设置的目标对象 进行封装,也就是说,框架内部会以统一的方式处理调用 链终点的目标对象。

Target Source最主要的特性就是,每次的方法调用 掉用流程都会触发TargetSource的getTarget ()方法,getTarget()方法将从相应的Target Source实现类中 取得具体的目标对象,这样,我们就可以控制每次方法调用作用到的具体对象实例:

提供一个目标对象池,每次从TargetSource取得的目标对象都从这个目标对象池中取得。

让一个TargetSource实现类待有多个目标对象实例,然后按照某种规则,在每次方法调用时,返回相应的目标对象实例。

当然,还可以让Target Source只持有一个目标对象实例,这样,每次的方法调用就都会针对这 一个目标对象实例。其实,这就是通常ProxyFactory或者ProxyFactoryBean处理目标对象的方式, 它们内部会构造一个org.springframework.aop.target.SingletonTargetSource实例,而 SingletonTargetSource则会针对每次方法调用都返回同一个目标对象实例的引用。

可用的Target Source实现类

在深入Target Source的定义之前,我们还是先来看一下有哪些现成的Target Source实现类。如 果这些现有的Target Source实现类不能满足需求,我们再寻求自定义的TargetSource实现。以下所 有的TargetSource实现类全部位于org.springframework.aop.target包中。

singletonTargetSource

org.springframework.aop.target.SingletonTargetSource是使用最多的Target source实现 类,虽然我们可能并不知道。因为在通过ProyFactoryBean的setTarget()设置完目标对象之后, ProxyFactory Bean内部会自行使用一个SingletonTargetSource对设置的目标对象进行封装。

SingletonTargetSource的实现其实很简单,就是内部只持有一个目标对象,当每次方法调用到 达时,SingletonTargetSource都会返回这同一个目标对象。

所有的Target Source都可以通过ProyFactoryBean的setTargetSource ()方法进行设置 (ProyFactory同样可以)。为了演示Target Source的使用,我们来看一个简单的例子(见代码)。

该实例其实没有什么太大意义,因为设置一个SingletonTargetSource型的Target Source, 其 实跟直接set Target是等效的。

 PrototypeTargetSource

与SingletonTargetSource正好相反,如果为ProxyFactory或者ProxyFactoryBean设置一个 PrototypeTargetSource类型的TargetSource, 那么每次方法调用到达调用链终点,并即将调用目 标对象上的方法的时候,PrototypeTarget Source都会返回一个新的目标对象实例供调用。代码清单 给出了PrototypeTargetSource的简单使用演示。

注意,因为PrototypeTargetSource每次都需要返回新的对象实例,所以,需要注意以下两点。

(1)目标对象的bean定义声明的scope必须为prototype。

(2)通过targetBeanName属性指定目标对象的bean定义名称,而不是引用。原因我想就不用多说 了

如果对以上配置所取得的target Proxy进行单元测试,那么代码清单所示的测试代码运行之 后,应该是可以得到可爱的Green Bar的。 (不要告诉我你不知道Green Bar在TDD中意味着什么哦!)

HotSwappableTargetSource

这是我觉得比较有用的一个Target Source实现。使用HotSwappaoleTargetSource封装目标对 象可以让我们在应用程序运行的时候,根据某种特定条件,动态地替换目标对象类的具体实现,比 如,IService有多个实现类,如果程序启动之后,默认的!Service实现类出现了问题,我们可以马 上切换到IService的另一个实现上,而所有这些对于调用者来说都是透明的。

使用HotSwappableTargetSource的swap方法,可以用新的目标对象实例将旧的目标对象实例替换掉。该方法的声明如下所示:

public Object swap(Object newTarget)

该方法会返回被替换的旧的目标对象实例。

要使用HotSwappableTargetSource, 我们得在它构造的时候,就提供一个默认的目标对象实例, 如代码清单所示。

我们使用构造方法注入为HotSwappableTargetSource注入了初始的目标对象,之后,就可以在 程序中对其进行操作,如代码清单所示。

能最快记起来的使用HotSwappableTargetSource的场景,就是我曾经在2005年使用它解决了双 数据源的互换问题:在有两个数据库双机热备的情况下,如果一个数据库挂掉,则将程序迅速地切换 到另一个数据库。当时,使用了ThrowsAdvice对数据库相关异常进行捕捉,在捕捉到必要的切换信 息后,就调用HotSwappableTargetSource的swa.p方法使用新的数据源替换旧的数据源,具体详情可 以参照我的博客文章《对双数据源互换的实现的改进》(http://danmwang. blogcn.com/diacy, 101689446.shtml)。

总之,使用HotSwappableTargetSource, 我们可以在任何合适的地方、合适的时机对旧的目标 对象进行替换,比如可以在某个拦截器中持有相关HotSwappableTargetSource的引用。一旦满足相 应的条件,就可以调用swap方法动态替换新的目标对象,也可以设置一个定时任务,让它也持有一个 HotSWappableTargetSource的引用,每隔一段时间就使用新的目标对象置换掉旧的,等等诸如此类 的场景。有关liotSwappableTargetSource的使用,还是有待你自己去挖掘吧!

commonsPoolTargetSource

某些时候,我们可能不想每次都返回新的目标对象,而是想返回有限数目的目标对象实例,这些 目标对象实例的“地位”是平等的,就好像数据库连接池中的那些Connection一样,我们可以提供 一个目标对象的对象池,然后让某个Target Source实现每次都从这个目标对象池中去取得目标对象。

CommonsPoolTargetSource就是这么一个Target Source实现,它使用现有的Jakarta Commons Pool提供对象池支持。使用它,跟使用PrototypeTargetSource没什么太大差别,如代码清单所示。

在CommonsPoolTargetSource的使用上,需要注意的问题跟PrototypeTargetSource差不多, 即注意它要使用prototype型scope的bean定义。CommonsPoolTargetSource还有许多控制对象池的可配属性,比如对象池的大小、初始对象数炽等,都可以在配置中指定。详情可参照Commons Pool­Target Source的Javadoc文档。

如果CommonsPoolTargetSource不能满足要求,或者因为其他原因不能使用Jakarta Commons Pool, 那么也可以通过扩展org.springframework.aop.target.AbstractPoolingTargetSource 类,实现相应的提供对象池化功能的Target Source。有关扩展AbstractPoolingTar-getSource的更 多信息,可以参考它的Javadoc或者源码,毕竟Spring是开源的嘛!

ThreadLocalTargetSource

如果想为不同的线程调用提供不同的目标对象,那么可以使用org.springframework.aop. target.ThreaaLocalTargetSource。它可以保证各自线程上对目标对象的调用,可以被分配到当前 线程对应的那个目标对象实例上。其实,ThreadLocalTargetSource无非就是对JDK标准的Thread­Local进行了简单的封装而已。

与其他TargetSource,ThreadLocalTargetSource的使用也比较简单,见代码清单。

我想你不会忘记将目标对象的bean定义的scope设成prototype型吧。毕竟人家每个线程起码得有 自己的目标对象实例不是?不过没关系,要是非设成singleton的,SpringAOP会“给你好行”的啦, 呵呵,异常伺候!

自定义TargetSource

说了这么多可以使用的Target Source实现,大部分情况下应该够用了。不过,永远也不能排除 特殊情况,我们还得做好实现自定义Target Source的准备。

要实现自定义的TargetSource, 我们可以直接扩展Target Source接口,好在这个接口定义的方 法不多,如下所示:

从下面的方法名称上,我估计各位就能猜出个大概了。

getTargetClass ()方法返回目标对象类型;

isStatic ()用于表明是否要返回同一个目标对象实例,SingletonTargetSource的这个方法 肯定是返回true, 其他的实现根据情况,通常返回false;

getTarget ()是核心,要返回哪个目标对象实例,完全由它说了算;

具体调用过程的结束,如果isStatic()为false, 则会调用releaseTarget ()以释放当前调 用的目标对象。但是否需要释放,完全是由实现的需要决定的,大部分时候,该方法可以空 着不实现。

为了演示Target Source的特性以及如何实现一个TargetSource, 我实现了一个简单的Alterna­tiveTargetSource。它内部有一个计数器,当计数器为奇数的时候,Target Source将针对当前调用 返回第一个目标对象实例;否则,返回第二个目标对象实例。AlternativeTargetSource的定义如代 码所示。

在使用AlternativeTargetSource的时候(见代码清单), 就会发现,多次的方法调用所发 生的目标对象实际上是交错变换的。

其实,这个Target Source实现没有太大的意义,不过扩展一下就说不定了。最主要的,我们可 以根据应用的具体场景来给出特定的Target Source实现。

如果我们的TargetSource自定义实现不在乎是否依赖于Spring的IoC容器,也不妨考虑一下 org. springframework. aop. target包中的儿个抽象类,直接在这些抽象类的基础上进行扩展,可以 省却部分劳烦。

 

 

 

 

 

 

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

猜你喜欢

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