AOP(Aspect-Oriented Programming):面向切面的编程。OOP(Object-Oriented Programming)面向对象的编程。对于OOP我们已经再熟悉不过了,对于AOP,可能我们会觉得是一种新特性,其实AOP是对OOP的一种补充,OOP面向的是纵向编程,继承、封装、多态是其三大特性,而AOP是面向横向的编程。
面向切面编程(AOP)通过提供另外一种思考程序结构的途经来弥补面向对象编程(OOP)的不足。在OOP中模块化的关键单元是类(classes),而在AOP中模块化的单元则是切面。切面能对关注点进行模块化,例如横切多个类型和对象的事务管理。
AOP框架是Spring的一个重要组成部分。但是Spring IoC容器并不依赖于AOP,这意味着你有权利选择是否使用AOP,AOP做为Spring IoC容器的一个补充,使它成为一个强大的中间件解决方案。
AOP在Spring Framework中的作用
学习AOP,当然得先了解一下其众多的概念性术语:
IsModified
接口,以便简化缓存机制。 通知类型:
环绕通知是最常用的通知类型。和AspectJ一样,Spring提供所有类型的通知,我们推荐你使用尽可能简单的通知类型来实现需要的功能。例如,如果你只是需要一个方法的返回值来更新缓存,最好使用后置通知而不是环绕通知,尽管环绕通知也能完成同样的事情。用最合适的通知类型可以使得编程模型变得简单,并且能够避免很多潜在的错误。比如,你不需要在 JoinPoint
上调用用于环绕通知的 proceed()
方法,就不会有调用的问题。
在这里,基于@AspectJ的AOP我就不多写了,因为我更青睐于Spring中使用ProxyFactoryBean创建AOP代理。
在Spring里创建一个AOP代理的基本方法是使用 org.springframework.aop.framework.ProxyFactoryBean 。 这个类对应用的切入点和通知提供了完整的控制能力(包括它们的应用顺序)。像其它的 FactoryBean
实现一样, ProxyFactoryBean
引入了一个间接层。如果你定义一个名为 foo
的 ProxyFactoryBean
, 引用 foo
的对象看到的将不是 ProxyFactoryBean
实例本身,而是一个 ProxyFactoryBean
实现里 getObject()
方法所创建的对象。 这个方法将创建一个AOP代理,它包装了一个目标对象。
ProxyFactoryBean
类本身也是一个JavaBean,其属性主要有如下用途:
一些主要属性从 org.springframework.aop.framework.ProxyConfig
里继承下来(这个类是Spring里所有AOP代理工厂的父类)。这些主要属性包括:
proxyTargetClass
:这个属性为 true
时,目标类本身被代理而不是目标类的接口。如果这个属性值被设为 true
,CGLIB代理将被创建。 optimize
:用来控制 通过CGLIB创建 的代理是否使用激进的优化策略。 除非完全了解AOP代理如何处理优化,否则不推荐用户使用这个设置。目前这个属性仅用于CGLIB代理; 对于JDK动态代理(缺省代理)无效。 frozen
:如果一个代理配置是 frozen
的,就不允许对该配置进行修改。 这在简单优化和不希望调用者在代理创建后操作代理(通过 Advised
接口) 时很有用。缺省值为 false
,即可以进行类似添加附加通知的操作。 exposeProxy
:决定当前代理是否被暴露在一个 ThreadLocal
中以便被目标对象访问。如果目标对象需要获取代理而且 exposeProxy
属性被设为 true
,目标对象可以使用 AopContext.currentProxy()
方法。 aopProxyFactory
:使用 AopProxyFactory
的实现。这提供了一种方法来自定义是否使用动态代理,CGLIB或其它代理策略。 缺省实现将根据情况选择动态代理或者CGLIB。一般情况下应该没有使用这个属性的需要;它是被设计来在Spring 1.1中添加新的代理类型的。 ProxyFactoryBean
中需要说明的其它属性包括:
proxyInterfaces
:需要代理的接口名的字符串数组。 如果没有提供,将为目标类使用一个CGLIB代理。 interceptorNames
: Advisor
的字符串数组,可以包括拦截器或其它通知的名字。 顺序是很重要的,排在前面的将被优先服务。就是说列表里的第一个拦截器将能够第一个拦截调用。这里的名字是当前工厂中bean的名字,包括父工厂中bean的名字。这里你不能使用bean的引用因为这会导致 ProxyFactoryBean
忽略通知的单例设置。你可以把一个拦截器的名字加上一个星号作为后缀( *
)。这将导致这个应用程序里所有名字以星号之前部分开头的通知器都被应用。 getObject()
被调用的多频繁。 多个 FactoryBean
实现都提供了这个方法。缺省值是 true
。 如果你希望使用有状态的通知–例如,有状态的mixin–可以把单例属性的值设置为 false
来使用原型通知。 3.基于JDK和CGLIB的代理:
如果一个需要被代理的目标对象的类(后面将简单地称它为目标类)没有实现任何接口,那么一个基于CGLIB的代理将被创建。 这是最简单的场景,因为JDK代理是基于接口的,没有接口意味着没有使用JDK进行代理的可能. 如果 ProxyFactoryBean
的 proxyTargetClass
属性被设为 true
,那么一个基于CGLIB的代理将创建。 这样的规定是有意义的,遵循了最小惊讶法则(保证了设定的一致性)。甚至当 ProxyFactoryBean
的 proxyInterfaces
属性被设置为一个或者多个全限定接口名, 而 proxyTargetClass
属性被设置为 true
仍然 将 实际使用基于CGLIB的代理。
如果 ProxyFactoryBean
的 proxyInterfaces
属性被设置为一个或者多个全限定接口名,一个基于JDK的代理将被创建。 被创建的代理将实现所有在 proxyInterfaces
属性里被说明的接口; 如果目标类实现了全部在 proxyInterfaces
属性里说明的接口以及一些额外接口,返回的代理将只实现说明的接口而不会实现那些额外接口。
如果 ProxyFactoryBean
的 proxyInterfaces
属性 没有 被设置, 但是目标类 实现了一个(或者更多) 接口,那么 ProxyFactoryBean
将自动检测到这个目标类已经实现了至少一个接口, 一个基于JDK的代理将被创建。被实际代理的接口将是目标类所实现的 全部 接口; 实际上,这和在 proxyInterfaces
属性中列出目标类实现的每个接口的情况是一样的。 然而,这将显著地减少工作量以及输入错误的可能性。