什么是AOP?
AOP:Aspect-Oriented Programming 面向方面编程或面向切面
Aspect是一种新的模块化机制,用来描述分散在对象、类或函数中的横切关注点(crosscutting concern)。从关注点中分离出横切关注点是面向切面的程序设计的核心概念。分离关注点使解决特定领域问题的代码从业务逻辑中独立出来,业务逻辑的代码中不再含有针对特定领域问题代码的调用,业务逻辑同特定领域问题的关系通过切面来封装、维护,这样原本分散在整个应用程序中的变动就可以很好地管理起来。
AOP技术:
- AspectJ:源代码和字节码级别的编织器,用户需要使用不同于Java的新语言。
- AspectWerkz:AOP框架,使用字节码动态编码器和XML配置。
- JBoss-AOP:基于拦截器和元数据的AOP框架,运行在Jboss应用服务器上。以及在AOP中用到的一些相关的技术实现。
- BCEL(Byte-Code Engineering Library):Java字节码操作类库。
- Javassist:Java字节码操作类库,JBoss的一个子项目
AOP联盟定义的AOP体系结构
“基础”(base)可以视为待增强对象或者说目标对象;
“切面”(aspect)通常包含对于基础的增强应用;
“配置”(configuration)可以看成是一种编织,通过在AOP体系中提供这个配置环境,可以把基础和切面结合起来,从而完成面对目标对象的编织实现。
在Spring AOP实现中,使用Java语言来实现增强对象与切面增强应用,并为这两者的结合提供了配置环境。对于编织配置,毫无疑问,可以使用IOC容器来完成;对于POJO对象的配置,本来就是Spring的核心IOC容器的强项。因此,对于Spring的AOP开发而言,使用POJO就能完成AOP任务。
对Spring平台或者说生态系统来说,AOP是Spring框架的核心功能模块之一。AOP和IOC容器的结合使用,为应用开发或Spring自身功能的扩展都是提供了许多便利。Spring AOP的实现和其他特性的实现一样,除了可以使用Spring本身提供的AOP实现之外,还封装了业界优秀的AOP解决方案AspectJ来供应用使用。
Advice通知
advice(通知)定义在连接点做什么,为切面增强提供织入接口。在Spring AOP中,它主要描述Spring AOP围绕方法调用而注入的切面行为。Advice是AOP联盟定义的一个接口,具体的接口定义在org.aopalliance.aop.Advice中。在Spring AOP的实现中,使用了这个统一接口,并通过这个接口,为AOP切面增强的织入功能做了更多的细化和扩展,比如提供了更具体的通知类型,如BeforeAdvice、AfterAdvice、ThrowsAdvice等。作为Spring AOP定义的接口类,具体的切面增强可以通过这些接口集成到AOP框架中发挥作用。
从BeforeAdvice开始:
在BeforeAdvice的继承关系中,定义了为待增强的目标方法设置的前置增强接口MethodBeforeAdvice,使用这个前置接口需要实现一个回调函数:
作为一个回调函数,before方法的实现在Advice中被配置到目标方法后,会在调用目标方法时被调用。具体的调用参数有:Method对象,这个参数是目标方法的反射对象;Object[]对象数组,这个对象数组中包含目标方法的输入参数。以CountingBeforeAdvice为例来说明BeforeAdvice的具体使用,CountingBeforeAdvice是接口MethodBeforeAdvice的具体实现。完成的工作是统计被调用的方法次数。作为切面增强实现,它会根据调用方法的方法名进行统计,把统计结果根据方法名和调用次数作为键值对放入一个map中。
这里调用count方法,使用了目标方法的反射对象作为参数,完成对调用方法名的统计工作。count方法在CountingBeforeAdvice的基类MethodCounter中实现。这个切面增强完成的统计实现并不复杂,它在对象中维护一个哈希表,依赖存储统计数据。在统计过程中,首先通过目标方法的反射对象得到方法名,然后进行累加,把统计结果放到维护的哈希表中。如果需要统计数据,就到这个哈希表中根据key来获取。
afterReturning方法也是一个回调函数,AOP应用需要在这个接口实现中提供切面增强的具体设计,在这个Advice通知被正确配置以后,在目标方法调用结束并成功返回的时候,接口会被Spring AOP回调。对于回调参数,有目标方法的返回结果、反射对象以及调用参数(AOP把这些参数都封装在一个对象数组中传递进来)等。与前面分析BeforeAdvice一样,在Spring AOP的包中,同样可以看到一个CountingAfterReturningAdvice,作为熟悉AfterReturningAdvice使用的例子,它的实现基本上与CountingBeforeAdvice是一样的。
Pointcut切点
Pointcut(切点)决定Advice通知应该作用于哪个连接点,也就是说通过Pointcut来定义需要增强的方法的集合,这些集合的选取可以按照一定的规则来完成。在这种情况下,Pointcut通常意味着标识方法,例如,这些需要增强的地方可以由某个正则表达式进行标识,或根据某个方法名进行匹配等。
切点在Spring AOP中的类继承体系
在Pointcut的基本接口定义中可以看到,需要返回一个MethodMatcher。对于Point的匹配判断功能,具体是由这个返回的MethodMatcher来完成的,也就是说,由这个MethodMatcher来判断是否需要对当前方法调用进行增强,或者是否需要对当前调用方法应用配置好的Advice通知。在Pointcut的类继承关系中,以正则表达式切点JdkRegexpMethodPointcut类完成通过正则表达式对方法名进行匹配的功能。在JdkRegexpMethodPointcut的基类StaticMethodMatcherPointcut的实现中可以看到,设置MethodMatcher为StaticMethodMatcher,同时JdkRegexpMethodPointcut也是这个MethodMatcher的子类。
可以看到,在Pointcut中,通过这样的类继承关系,MethodMatcher对象实际上是可以被配置成JdkRegexpMethodPointcut来完成方法的匹配判断的。在JdkRegexpMethodPointcut中,可以看到一个matches方法,这个matches方法是MethodMatcher定义的接口方法。在JdkRegexpMethodPointcut的实现中,这个matches方法就是使用正则表达式来对方法名进行匹配的地方。关于在AOP框架中对matches方法的调用,会在下面的Spring AOP实现中介绍,这里只是先简单提一下。要了解matches在AOP框架中的调用位置,比较简单的方法就是以matches方法作为起始点,对它的方法调用关系进行追溯。
在对matches方法的调用关系中可以看到,是在JdkDynamicAopProxy的invoke方法中触发了对matches方法的调用。invoke回调的实现是使用JDK动态代理完成AOP功能的一部分,关于这部分的实现原理,在下面AOP的实现分析中有详细的阐述,这里就不进行太多的说明了。
在JdkRegexpMethodPointcut中,通过JDK来实现正则表达式的匹配。
在Spring AOP中,还提供其他的MethodPointcut,比如通过方法名匹配进行Advice匹配的NameMatchMethodPointcut。
Advisor通知器
完成对目标方法的切面增强设计(Advice)和关注点的设计(Pointcut)以后,需要一个对象把它们结合起来,完成这个作用的就是Advisor(通知器)。通过Advisor,可以定义应该使用哪个通知并在哪个关注点使用它,也就是说通过Advisor,把Advice和Pointcut结合起来,这个结合为使用IOC来配置AOP应用,或者说即开即用地使用AOP基础设施,提供了便利。在Spring AOP中,我们以一个Advisor的实现(DefaultPointcutAdvisor)为例,来了解Advisor的工作原理。在DefaultPointcutAdvisor中,有两个属性,分别是advice和pointcut。通过这两个属性,可以配置Advice和Pointcut。
Spring AOP的设计与实现
JVM的动态代理特性
在spring aop实现中,使用的核心技术是动态代理,而这种动态代理实际上是JDK的一个特性。通过JDK的动态代理特性,可以为任意Java对象创建代理对象,对于具体使用来说,这个特性是通过Java Reflection API来完成的。
Spring AOP的设计分析
以动态代理技术为基础,设计出了一系列AOP的横切实现,比如前置通知,返回通知、异常通知等。同时,Spring AOP的内部设计可以看到,为了让AOP起作用,需要完成一系列过程,比如,需要为目标对象建立代理对象,这个代理对象可以通过使用JDK的Proxy来完成,也可以通过第三方的类生成器CGLIB来完成。然后,还需要启动代理对象的拦截器来完成各种横切面的织入,这一系列的织入设计是通过一系列Adapter来实现。通过一系列Adapter的设计,可以把AOP的横切面设计和Proxy模式有机地结合起来,从而实现在AOP中定义好的各种织入方式。
Spring AOP的应用场景
Spring AOP为IOC的使用提供更多便利,一方面,应用可以直接使用AOP的功能,设计应用的横切关注点,把跨越应用程序多个模块的功能抽象出来,并通过简单的AOP的使用,灵活地编制到模块中,比如可以通过AOP实现应用程序中的日志功能。另一方面,在Spring内部,一些支持模块也通过Spring AOP来实现的,比如后面将要详细介绍的事务处理。从两个角度就可以看到Spring AOP的核心地位了。
建立AOPProxy代理对象
设计原理
在spring的AOP模块中,一个主要的部分是代理对象的生成,而对于Spring应用,可以看到,是通过配置和调用Spring的ProxyFactoryBean来完成这个任务的。在ProxyFactoryBean中,封装了主要代理对象的生成过程。在这个生成过程中,可以使用JDK的Proxy和GCLIB两种生成方式。
类继承关系
在这个类继承关系中,可以看到完成AOP应用的类,比如AspectJProxyFactory、ProxyFactory和ProxyFactoryBean,它们都在同一个类的继承体系下,都是ProxyConfig、AdvisedSupport和ProxyCreatorSupport的子类。作为共同基类,可以将ProxyConfig看成是一个数据基类,这个数据基类为ProxyFactoryBean这样的子类提供了配置属性;在另一个基类AdvisedSupport的实现中,封装了AOP对通知和通知器的相关操作,这些操作对于不同的AOP的代理对象的生成都是一样的,但对于具体的AOP代理对象的创建,AdvisedSupport把它交给它的子类们去完成;对于ProxyCreatorSupport,可以将它看成是其子类创建AOP代理对象的生成,根据不同的需要分别由ProxyFactoryBean、AspectJProxyFactory和ProxyFactory来完成。对于需要使用AspectJ的AOP应用,AspectJProxyFactory起到集成Spring和AspectJ的作用;对于使用Spring AOP的应用,ProxyFactoryBean和ProxyFactory都提供了AOP功能的封装,只是使用ProxyFactoryBean,可以在IOC容器中完成声明式配置,而使用ProxyFactory,则需要编程式地使用Spring AOP的功能。
配置ProxyFactoryBean
1)定义使用的通知器Advisor,这个通知器应该作为一个Bean来定义。很重要的一点是,这个通知器的实现定义了需要对目标对象进行增强的切面行为,也就是Advice通知。
2)定义ProxyFactoryBean,把它作为另一个Bean来定义,它是封装AOP功能的主要类。在配置ProxyFactoryBean时,需要设定与AOP实现相关的重要属性,比如proxyInterface、interceptorNames和target等。从属性名称可以看出,interceptorNames属性的值往往设置为需要定义的通知器,因为这些通知器在ProxyFactoryBean的AOP配置下,是通过使用代理对象的拦截器机制起作用的。
3)定义target属性,作为target属性注入的Bean,是需要用AOP通知器中的切面应用来增强的对象,也就是前面提到的base对象。
ProxyFactoryBean生成AOPProxy代理对象
在ProxyFactoryBean中,通过interceptorNames属性来配置通知器的地方。在ProxyFactoryBean中,需要为target目标对象生成Proxy代理对象,从而为AOP横切面的编织做好工作。
ProxyFactoryBean的AOP实现需要依赖JDK或者CGLIB提供的Proxy特性。从FactoryBean中获取对象,是以getObject()方法作为入口完成的;ProxyFactoryBean实现中的getObject方法,是FactoryBean需要实现的接口。对ProxyFactoryBean来说,把需要对target目标对象增加的增强处理,都通过getObject方法进行封装了,这些增强处理是为了AOP功能的实现提供服务的。getObject方法首先对通知器链进行初始化,通知器链封装了一系列的拦截器,这些拦截器都要从配置中读取,然后为代理对象的生成做好准备。在生成代理对象时,因为Spring中有Singleton类型和prototype类型这两种不同的Bean,所以要对代理对象的生成做一个区分。
AOPProxy的生成过程
生成Singleton的代理对象在getSingletonInstance()的代码中完成,这个方法是ProxyFactoryBean生成AOpProxy代理对象的调用入口。代理对象会封装对target目标对象的调用,也就是说针对target对象的方法调用行为会被这里生成的代理对象所拦截。具体的生成过程是,首先读取ProxyFactoryBean中的配置,为生成代理对象做好必要的准备,比如设置代理的方法调用接口等。Spring通过AOPProxy类来具体生成代理对象。
AOPProxy是一个接口,它由两个子类实现,一个是Cglib2AOPProxy,另一个是JdkDynamicProxy。顾名思义,对这两个AopProxy接口的子类的实现,Spring分别通过CGLIB和JDK来生成需要的Proxy代理对象。
具体的代理对象的生成,是在ProxyFactoryBean的基类AdvisedSupport的实现中借助AopProxyFactory完成的,这个代理对象要么从JDK中生成,要么借助CGLIB获得。因为ProxyFactoryBean本身就是AdvisedSupport的子类,所以在ProxyFactoryBean中获得AopProxy是很方便的,可以在ProxyCreatorSupport中看到,具体的AopProxy是通过AOpProxyFactory来生成的。至于需要生成什么样的代理对象,所有信息都封装在AdvisedSupport里,这个对象也是生成AopProxy的方法的输入参数,这里设置为this本身,因为ProxyCreatorSupport本身就是AdvisedSupport的子类。
JDK生成AopProxy代理对象
生成代理对象时,需要指明三个参数,一是类装载器、一个是代理接口、另外一个就是Proxy回调方法所在的对象,这个对象需要实现InvocationHandler接口。
CGLIB生成AopProxy代理对象
使用的是设置好的callback回调,这是由GCLIB的使用来决定的。
Spring AOP拦截器的实现
设计原理
通过JDK或者GCLIB的方式生成代理对象,相关的拦截器已经配置到代理对象中去了,拦截器在代理对象中起作用是通过对这些方法的回调来完成的。
如果使用JDK的Proxy来生成代理对象,那么需要通过InvocationHandler来设置拦截器回调;而如果使用CGLIB来生成代理对象,就需要根据CGLIB的使用要求,通过DynamicAdvisedInterceptor来完成回调。
jdkDynamicAOPProxy的invoke拦截
在Spring中通过ProxyFactoryBean实现AOP功能的第一步,得到AopProxy代理对象的基本过程,以及通过使用JDK和CGLIB最终产生AopProxy代理对象的实现原理。
Cglib2AOPProxy的intercept拦截
目标对象方法的调用
对于jdkDynamicAopProxy代理对象,这个目标对象的方法调用是通过AopUtils使用反射机制在AopUtils.invokeJoinpointUsingRefelection方法中实现的。在这个调用中,首先得到方法的反射方法,然后使用invoke启动对方法反射对象的调用。
Aop拦截器链的调用
对拦截器链的调用都是在ReflectionMethodInvocation中通过proceed方法的实现。在proceed方法中,会逐个运行拦截器的拦截方法。
配置通知器
这个interceptorOrInterceptionAdvice是获得的拦截器,它通过拦截器机制对目标对象的行为增强起作用。为了提高取得拦截器链的效率,还为这个拦截器链设置了缓存。
在ProxyFactoryBean的getObject方法中对advisor进行初始化时,从XML配置中获取了advisor通知器。
Actice通知的实现
一是调用Adapter的support方法,通过这个方法来判断取得的advice属于什么类型的advice通知,从而根据不同的advice类型来注册不同的AdviceInterceptor,也就是前面看到的那些拦截器;
二是这些AdviceInterceptor都是Spring AOP框架设计好了,是为了实现不同的advice功能提供服务。
ProxyFactory实现AOP
由ProxyFactory的getProxy方法取得AopProxy代理对象,getProxy方法的实现使用ProxyFactory的基类ProxyCreatorSupport的createProxy方法来生成AopProxy代理对象,而AopProxy代理对象的生成是由AopProxyFactory来完成的,会生成JDK或者CGLIB的代理对象。
Spring Aop的高级特性
Spring提供许多现成的TargetSource实现,比如HotSwappableTargetSource,HotSwappableTargetSource使用户可以以线程安全的方式切换目标对象,提供所谓的热交换功能。这个特性是很有用的,尽管它的开启需要AOP应用进行显示的配置,但配置不复杂,在使用时,只需要把HotSwappableTargetSource配置到ProxyFactoryBean的target属性就可以了,在需要真正的目标对象时,调用HotSwappableTargetSource的swap方法就可以完成。由此可见,对HotSwappableTargetSource的热交换功能的使用,是需要触发swap方法调用的。这个swap方法的实现很简单,它完成target对象的替换,也就是说,它使用新的target对象来替换原有的target对象。