Spring AOP的织入
各个模块我们已经实现好了,剩下的工作,就是拼装各个模块。
要进行织入,AspectJ采用ajc
编译器作为它的织入器;JBoss AOP使用自定义的ClassLoader
作为它的织入器;而在SpringAOP中,使用类org.springframework.aop.framework.ProxyFactory
作为织入器。
如何与ProxyFactory打交道
首先需要声明的是,ProxyFactory
并非SpringAOP中唯一可用的织入器,而是最基本的一个织入器实现,所以,我们就从最基本的这个织入器开始,来窥探一下SpringAOP的织入过程到底是一个什么样子。
使用ProxyFactory
来进行横切逻辑的织入很简单。我们知道,SpringAOP是基于代理模式的AOP实现,织入过程完成后,会返回织入了横切逻辑的目标对象的代理对象。为ProxyFactory
提供必要的“生产原材料”之后,ProxyFactory
就会返回那个织入完成的代理对象(如以下代码所示):
使用ProxyFactory只需要指定如下两个最基本的东西。
-
第一个是要对其进行织入的目标对象。我们可以通过
ProxyFactory
的构造方法直接传入,也可以在ProxyFactory
构造完成之后,通过相应的setter方法进行设置。 -
第二个是将要应用到目标对象的
Aspect
。哦,在Spring里面叫做Advisor
。不过,除了可以指定相应的Advisor
之外,还可以使用weaver.addAdvice(...);
来直接指定各种类型的Advice。
对于Introduction
之外的Advice类型,ProxyFactory
内部就会为这些Advice构造相应的Advisor,只不过在为它们构造的Advisor中使用的Pointcut为Pointcut.TRUE,
即这些“没穿衣服”的Advice将被应用到系统中所有可识别的Joinpoint处;
而如果添加的Advice类型是Introduction
类型,则会根据该Introduction
的具体类型进行区分:如果是IntroductionInfo
的子类实现,因为它本身包含了必要的描述信息,框架内部会为其构造一个DefaultIntroductionAdvisor;
而如果是DynamicIntroductionAdvice
的子类实现,框架内部将抛出AopConfigException
异常(因为无法从DynamicIntroductionAdvice
取得必要的目标对象信息)。
但是,在不同的应用场景下,我们可以指定更多ProxyFactory
的控制属性,以便让ProxyFactory
帮我们生成必要的代理对象。我们知道,SpringAOP在使用代理模式实现AOP的过程中采用了动态代理和CGLIB两种机制,分别对实现了某些接口的目标类和没有实现任何接口的目标类进行代理,所以,在使用ProxyFactory
对目标类进行代理的时候,会通过ProxyFactory
的某些行为控制属性对这两种情况进行区分。
在继续下面内容之前,有必要先设定一个简单的场景,以便大家结合实际情况来查看和分析在不同场景下,ProxyFactory
在使用方式上的细微差异。假设我们的目标类型定义如下:
有了要拦截的目标类,还得有织入到Joinpoint处的横切逻辑,也就是要用到某个Advice实现。我们就把之前的PerformanceMethodInterceptor
先拿来一用(见下方代码)。
有了这些之后,让我们来看一下使用ProxyFactory
对实现了ITask接口的目标类,以及没有实现任何接口的目标类如何进行代理。
1. 基于接口的代理
MockTask实现了ITask接口,要对这种实现了某些接口的目标类进行代理,我们可以为ProxyFactory
明确指定代理的接口类型,如下所示:
通过setInterfaces()
方法可以明确告知ProxyFactory
,我们要对ITask接口类型进行代理。另外,在这里,我们通过NameMatchMethodPointcutAdvisor
来指定Pointcut和相应的Advice(Perfor-
manceMethodInterceptor)。至于什么类型的Pointcut、Advice以及Advisor,我们完全可以根据个人的喜好或者具体场景来使用,举一反三嘛!
不过,如果没有其他行为属性的千预,我们也可以不使用setInterfaces()
方法明确指定具体的接口类型。这样,默认情况下,ProxyFactory只要检测到目标类实现了相应的接口,也会对目标类进行基于接口的代理,如下所示:
简单点儿说,如果目标类实现了至少一个接口,不管我们有没有通过ProxyFactory
的setInterIfaces()
方法明确指定要对特定的接口类型进行代理,只要不将ProxyFactory
的optimize
和proxyTargetClass
两个属性的值设置为true(这两个属性稍后将谈到),那么ProxyFactory都会按照面向接口进行代理。
2. 基于类的代理
如果目标类没有实现任何接口,那么,默认情况下,ProxyFactory
会对目标类进行基于类的代理,即使用CGLIB。假设我们现在有一个对象,定义如下:
如果使用Executable
作为目标对象类,那么,ProxyFactory
就会对其进行基于类的代理,如下代码演示了使用ProxyFactory
对Executable
进行织入的过程:
从输出结果可以看出来,最终的代理对象是基于CGLIB的:
但是,即使目标对象类实现了至少一个接口,我们也可以通过proxyTargetClass
属性强制ProxyFactory
采用基于类的代理。
以MockTask
为例,它实现了ITask
接口,默认情况下ProxyFactory
对其会采用基于接口的代理,但是,通过proxyTargetClass
,我们可以改变这种默认行为(见如下代码):
除此之外,如果将ProxyFactory
的optimize
属性设定为true的话,ProxyFactory
也会采用基于类的代理机制。关于optimize
属性的更多信息,我们将在后面给出。
总地来说,如果满足以下列出的三种情况中的任何一种,ProxyFactory 将对目标类进行基于类的代理 。
-
如果目标类没有实现任何接口,不管
proxyTargetClass
的值是什么,ProxyFactory会采用基于类的代理。 -
如果ProxyFactory的
proxyTargetClass
属性值被设置为true,ProxyFactory会采用基于类的代理。 -
如果ProxyFactory的
optimize
属性值被设置为true,ProxyFactory会采用基于类的代理。
3. Introduction的织入
不做介绍 有兴趣可以看原文
看清ProxyFactory的本质
不做介绍 有兴趣可以看原文
容器中的织入器——ProxyFactoryBean
不做介绍 有兴趣可以看原文
加快织入的自动化进程
不做介绍 有兴趣可以看原文
TargetSource
通常,在使用ProxyFactory
的时候,我们都是通过setTarget()
方法指定具体的目标对象。使用ProxyFactoryBean
也是如此,或者ProxyFactoryBean
还可以通过setTargetName()
指定目标对象在IoC容器中的bean定义名称。
但除此之外,还有一种方式没有说,那就是还可以通过setTargetSource()
来指定目标对象。
不做更多介绍 有兴趣可以看原文
本章小结
本章我们详尽剖析了SpringAOP中的各种概念和实现原理,这些概念和实现原理是SpringAOP发布之初就确定的,是整个框架的基础。纵使框架版本如何升级,甚至为SpringAOP加入更多的特性,在升级和加入更多更多特性的过程中,也将一直秉承SpringAOP的这些理念。
了解SpringAOP框架发布之初就确立的各种概念和原理,可以帮助我们更好地理解和使用SpringAOP。甚至,可以帮助我们去扩展SpringAOP。而接下来要讲述的,就是Spring2.0之后对SpringAOP进行的扩展。