[转]使用ProxyFactoryBean建立AOP代理

http://doc.javanb.com/spring-framework-reference-zh-2-0-5/html

 

7.5. 使用ProxyFactoryBean建立AOP代理 - Spring Framework reference 2.0.5 参考手册中文版

7.5. 使用ProxyFactoryBean建立AOP代理

若是你正在使用Spring IoC容器(即ApplicationContext或BeanFactory)来管理你的业务对象--这正是你应该作的--你也许会想要使用Spring中关于AOP的FactoryBean。(记住使用工厂bean引入一个间接层以后,咱们就能够建立不一样类型的对象了)。java

注意

Spring 2.0的AOP支持也在底层使用工厂bean。spring

在Spring里建立一个AOP代理的基本方法是使用org.springframework.aop.framework.ProxyFactoryBean。这个类对应用的切入点和通知提供了完整的控制能力(包括它们的应用顺序)。然而若是你不须要这种控制,你会喜欢更简单的方式。编程

7.5.1. 基础

像其它的FactoryBean实现同样,ProxyFactoryBean引入了一个间接层。若是你定义一个名为fooProxyFactoryBean, 引用foo的对象看到的将不是ProxyFactoryBean实例自己,而是一个ProxyFactoryBean实现里getObject() 方法所建立的对象。 这个方法将建立一个AOP代理,它包装了一个目标对象。数组

使用ProxyFactoryBean或者其它IoC相关类带来的最重要的好处之一就是建立AOP代理,这意味着通知和切入点也能够由IoC来管理。这是一个强大的功能并使得某些特定的解决方案成为可能, 而这些用其它AOP框架很难作到。例如,一个通知也许自己也要引用应用程序对象(不只仅是其它AOP框架中也能够访问的目标对象),这令你能够从依赖注射的可拔插特性中获益。框架

7.5.2. JavaBean属性

一般状况下Spring提供了大多数的FactoryBean实现,ProxyFactoryBean类自己也是一个JavaBean。它的属性被用来:测试

一些主要属性从org.springframework.aop.framework.ProxyConfig里继承下来(这个类是Spring里全部AOP代理工厂的父类)。这些主要属性包括:debug

  • proxyTargetClass:这个属性为true时,目标类自己被代理而不是目标类的接口。若是这个属性值被设为true,CGLIB代理将被建立(能够参看下面名为第 7.5.3 节 “基于JDK和CGLIB的代理”的章节)。

  • optimize:用来控制经过CGLIB建立的代理是否使用激进的优化策略。除非彻底了解AOP代理如何处理优化,不然不推荐用户使用这个设置。目前这个属性仅用于CGLIB代理;对于JDK动态代理(缺省代理)无效。

  • frozen:用来控制代理工厂被配置以后,是否还容许修改通知。缺省值为false(即在代理被配置以后,不容许修改代理的配置)。

  • exposeProxy:决定当前代理是否被保存在一个ThreadLocal中以便被目标对象访问。(目标对象自己能够经过MethodInvocation来访问,所以不须要ThreadLocal。) 若是个目标对象须要获取代理并且exposeProxy属性被设为true,目标对象可使用AopContext.currentProxy()方法。

  • aopProxyFactory:使用AopProxyFactory的实现。这提供了一种方法来自定义是否使用动态代理,CGLIB或其它代理策略。 缺省实现将根据状况选择动态代理或者CGLIB。通常状况下应该没有使用这个属性的须要;它是被设计来在Spring 1.1中添加新的代理类型的。

ProxyFactoryBean中须要说明的其它属性包括:

  • proxyInterfaces:须要代理的接口名的字符串数组。若是没有提供,将为目标类使用一个CGLIB代理(也能够查看下面名为第 7.5.3 节 “基于JDK和CGLIB的代理”的章节)。

  • interceptorNamesAdvisor的字符串数组,能够包括拦截器或其它通知的名字。顺序是很重要的,排在前面的将被优先服务。就是说列表里的第一个拦截器将可以第一个拦截调用。

    这里的名字是当前工厂中bean的名字,包括父工厂中bean的名字。这里你不能使用bean的引用由于这会致使ProxyFactoryBean忽略通知的单例设置。

    你能够把一个拦截器的名字加上一个星号做为后缀(*)。这将致使这个应用程序里全部名字以星号以前部分开头的advisor都被应用。你能够在第 7.5.6 节 “使用“全局”advisor” 发现一个使用这个特性的例子。

  • 单例:工厂是否应该返回同一个对象,不论方法getObject()被调用的多频繁。多个FactoryBean实现都提供了这个方法。缺省值是true。若是你但愿使用有状态的通知--例如,有状态的mixin--能够把单例属性的值设置为false来使用原型通知。

7.5.3. 基于JDK和CGLIB的代理

这个小节做为说明性文档,解释了对于一个目标对象(须要被代理的对象),ProxyFactryBean是如何决定究竟建立一个基于JDK仍是CGLIB的代理的。

注意

ProxyFactoryBean须要建立基于JDK仍是CGLIB代理的具体行为在版本1.2.x和2.0中有所不一样。如今ProxyFactoryBean在关于自动检测接口方面使用了与TransactionProxyFactoryBean类似的语义。

若是一个须要被代理的目标对象的类(后面将简单地称它为目标类)没有实现任何接口,那么一个基于CGLIB的代理将被建立。这是最简单的场景,由于JDK代理是基于接口的,没有接口意味着没有使用JDK进行代理的可能。 在目标bean里将被插入探测代码,经过interceptorNames属性给出了拦截器的列表。注意一个基于CGLIB的代理将被建立即便ProxyFactoryBeanproxyTargetClass属性被设置为false。 (很明显这种状况下对这个属性进行设置是没有意义的,最好把它从bean的定义中移除,由于虽然这只是个多余的属性,但在许多状况下会引发混淆。)

若是目标类实现了一个(或者更多)接口,那么建立代理的类型将根据ProxyFactoryBean的配置来决定。

若是ProxyFactoryBeanproxyTargetClass属性被设为true,那么一个基于CGLIB的代理将建立。这样的规定是有意义的,遵循了最小惊讶法则(保证了设定的一致性)。 甚至当ProxyFactoryBeanproxyInterfaces属性被设置为一个或者多个全限定接口名,而proxyTargetClass属性被设置为true仍然实际使用基于CGLIB的代理。

若是ProxyFactoryBeanproxyInterfaces属性被设置为一个或者多个全限定接口名,一个基于JDK的代理将被建立。被建立的代理将实现全部在proxyInterfaces属性里被说明的接口;若是目标类实现了所有在proxyInterfaces属性里说明的接口以及一些额外接口,返回的代理将只实现说明的接口而不会实现那些额外接口。

若是ProxyFactoryBeanproxyInterfaces属性没有被设置,可是目标类实现了一个(或者更多)接口,那么ProxyFactoryBean将自动检测到这个目标类已经实现了至少一个接口, 一个基于JDK的代理将被建立。被实际代理的接口将是目标类所实现的所有接口;实际上,这和在proxyInterfaces属性中列出目标类实现的每一个接口的状况是同样的。然而,这将显著地减小工做量以及输入错误的可能性。

7.5.4. 对接口进行代理

让咱们看一个关于ProxyFactoryBean的简单例子。这个例子涉及:

  • 一个将被代理的目标bean。在下面的例子里这个bean是“personTarget”。

  • 被用来提供通知的一个advisor和一个拦截器。

  • 一个AOP代理bean的定义,它说明了目标对象(personTarget bean)以及须要代理的接口,还包括须要被应用的通知。

<bean id="personTarget" class="com.mycompany.PersonImpl">
    <property name="name"><value>Tony</value></property>
    <property name="age"><value>51</value></property>
</bean>

<bean id="myAdvisor" class="com.mycompany.MyAdvisor">
    <property name="someProperty"><value>Custom string property value</value></property>
</bean>

<bean id="debugInterceptor" class="org.springframework.aop.interceptor.DebugInterceptor">
</bean>

<bean id="person" 
    class="org.springframework.aop.framework.ProxyFactoryBean">
    <property name="proxyInterfaces"><value>com.mycompany.Person</value></property>

    <property name="target"><ref local="personTarget"/></property>
    <property name="interceptorNames">
        <list>
            <value>myAdvisor</value>
            <value>debugInterceptor</value>
        </list>
    </property>
</bean>

注意interceptorNames属性接受一组字符串:当前工厂中拦截器或advisorbean的名字。拦截器,advisor,前置, 后置和异常通知对象均可以在这里被使用。这里advisor的顺序是很重要的。

注意

你也许很奇怪为何这个列表不保存bean的引用。理由是若是ProxyFactoryBean的singleton属性被设置为false,它必须返回独立的代理实例。若是任何advisor自己是一个原型,则每次都返回一个独立实例,所以它必须可以从工厂里得到原型的一个实例;保存一个引用是不够的。

上面“person” bean的定义能够被用来取代一个Person接口的实现,就像下面这样:

Person person = (Person) factory.getBean("person");

在同一个IoC上下文中其它的bean能够对这个bean有基于类型的依赖,就像对一个普通的Java对象那样:

<bean id="personUser" class="com.mycompany.PersonUser">
  <property name="person"><ref local="person" /></property>
</bean>

这个例子里的PersonUser类将暴露一个类型为Person的属性。就像咱们关心的那样,AOP代理能够透明地取代一个“真实”的person接口实现。然而,它的类将是一个动态代理类。 它能够被转型成Advised接口(将在下面讨论)。

就像下面这样,你可使用一个匿名内部bean来隐藏目标和代理之间的区别。仅仅ProxyFactoryBean的定义有所不一样;通知的定义只是因为完整性的缘由而被包括进来:

<bean id="myAdvisor" class="com.mycompany.MyAdvisor">
  <property name="someProperty"><value>Custom string property value</value></property>
</bean>

<bean id="debugInterceptor" class="org.springframework.aop.interceptor.DebugInterceptor"/>

<bean id="person" class="org.springframework.aop.framework.ProxyFactor Bean">
  <property name="proxyInterfaces"><value>com.mycompany.Person</value></property>
  <!-- Use inner bean, not local reference to target -->
  <property name="target">
    <bean class="com.mycompany.PersonImpl">
      <property name="name"><value>Tony</value></property>
      <property name="age"><value>51</value></property>
    </bean>
  </property>
  <property name="interceptorNames">
    <list>
      <value>myAdvisor</value>
      <value>debugInterceptor</value>
    </list>
  </property>
</bean>

对于只须要一个Person类型对象的状况,这是有好处的:若是你但愿阻止应用程序上下文的用户获取一个指向未通知对象的引用或者但愿避免使用Spring IoC 自动织入 时的混淆。 按理说ProxyFactoryBean定义还有一个优势是它是自包含的。然而,有时可以从工厂里获取未通知的目标也是一个优势:例如,在某些测试场景里。

7.5.5. 对类进行代理

若是你须要代理一个类而不是代理一个或是更多接口,那么状况将是怎样?

想象在咱们上面的例子里,不存在Person接口:咱们须要通知一个叫作Person的类,它没有实现任何业务接口。在这种状况下,你能够配置Spring使用CGLIB代理,而不是动态代理。 这只需简单地把上面ProxyFactoryBean的proxyTargetClass属性设为true。虽然最佳方案是面向接口编程而不是类,但在与遗留代码一块儿工做时,通知没有实现接口的类的能力是很是有用的。(一般状况下,Spring没有任何规定。它只是让你很容易根据实际状况选择最好的解决方案,避免强迫使用特定方式)。

也许你但愿你可以在任何状况下都强制使用CGLIB,甚至在你使用接口的时候也这样作。

CGLIB经过在运行时生成一个目标类的子类来进行代理工做。Spring配置这个生成的子类对原始目标对象的方法调用进行托管:子类实现了装饰器(Decorator)模式,把通知织入。

CGLIB的代理活动应当对用户是透明的。然而,有一些问题须要被考虑:

  • Final方法不能够被通知,由于它们不能被覆盖。

  • 你须要在你的类路径里有CGLIB 2的库;使用动态代理的话只须要JDK。

在CGLIB代理和动态代理之间的速度差异是很小的。在Spring 1.0中,动态代理会快一点点。但这点可能在未来被改变。这种状况下,选择使用何种代理时速度不该该成为决定性的理由。

7.5.6. 使用“全局”advisor

经过在一个拦截器名后添加一个星号,全部bean名字与星号以前部分相匹配的通知都将被加入到advisor链中。这让你很容易添加一组标准的“全局”advisor:

<bean id="proxy" class="org.springframework.aop.framework.ProxyFactoryBean">
  <property name="target" ref="service"/>
  <property name="interceptorNames">
    <list>
      <value>globa *</value>
    </list>
  </property>
</bean>

<bean id="global_debug" class="org.springframework.aop.interceptor.DebugInterceptor"/>
<bean id="global_performance" class="org.springframework.aop.interceptor.PerformanceMonitorInterceptor"/>
相关文章
相关标签/搜索