7.9. 使用“自动代理(autoproxy)”功能

到目前为止我们已经考虑了如何使用ProxyFactoryBean或者类似的工厂bean来显式创建AOP代理。

Spring也允许我们使用“自动代理”的bean定义,可以自动对被选中的bean定义进行代理。 这建立在Spring的“bean post processor”功能上,后者允许在容器加载时修改任何bean的定义。

在这个模型下,你在你的XML bean定义文件中建立一些特定的bean定义来配置自动代理功能。 这允许你仅仅声明那些将被自动代理的适当目标:你不需要使用ProxyFactoryBean

有两种方式可以做到这点:

7.9.1. 自动代理bean定义

org.springframework.aop.framework.autoproxy包提供了下列标准自动代理创建器。

7.9.1.1. BeanNameAutoProxyCreator

BeanNameAutoProxyCreator为名字匹配字符串或者通配符的bean自动创建AOP代理。

<bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
                    <property name="beanNames"><value>jdk*,onlyJdk</value></property>
                    <property name="interceptorNames">
                    <list>
                    <value>myInterceptor</value>
                    </list>
                    </property>
                </bean>

ProxyFactoryBean一样,这里有一个interceptorNames属性而不是一个拦截器的列表,这允许使用原型(prototype)通知器。这里的“拦截器”可以是通知器或任何通知类型。

与通常的自动代理一样,使用BeanNameAutoProxyCreator的主要目的是把相同的配置一致地应用到多个对象, 并且使用最少量的配置。一个流行的选择是把声明式事务应用到多个对象上。

那些名字匹配的Bean定义,例如上面的例子里的“jdkMyBean”和“onlyJdk”,本身只是目标类的普通bean定义。一个AOP对象将被BeanNameAutoProxyCreator自动创建。 相同的通知将被应用到全部匹配的bean上。注意如果通知器被使用(而不是像上面例子里那样使用拦截器),对于不同bean可以应用不同的切入点。

7.9.1.2. DefaultAdvisorAutoProxyCreator

一个更加通用而且强大得多的自动代理创建器是DefaultAdvisorAutoProxyCreator。它自动应用当前上下文中适当的通知器,无需在自动代理通知器的bean定义中包括bean的名字。 比起BeanNameAutoProxyCreator,它提供了同样关于一致性配置的优点而避免了前者的重复性。

使用这个功能将涉及:

  • Specifying a DefaultAdvisorAutoProxyCreator bean definition.

    说明一个 DefaultAdvisorAutoProxyCreator的bean定义

  • 在同一个或者相关的上下文中说明任意数量的通知器。注意这些必须是通知器而不仅仅是拦截器或者其它通知。 这点是必要的因为必须有一个切入点被评估,以便检查每个通知候选bean定义的合适性。

DefaultAdvisorAutoProxyCreator将自动评估包括在每个通知器中的切入点, 来看看它应当应用哪个(如果有的话)通知到每个业务对象(例如例子里的“businessObject1”和“businessObject2”)。

这意味着可以向每个业务对象应用任意数量的通知器。 对于一个业务对象,如果所有通知器中的切入点都无法匹配它的任意方法,这个对象将不会被代理。当为新的业务对象加入bean定义时,如果有必要它们将自动被代理。

通常自动代理的好处是它让调用者或者被依赖对象不能得到一个没有通知过的对象。 在这个ApplicationContext上调用getBean("businessObject1")将返回一个AOP代理,而不是目标业务对象。(前面显示的“内部bean”也提供了同样的优点。)

<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"/>
                    
                    <bean class="org.springframework.transaction.interceptor.TransactionAttributeSourceAdvisor">
                    <property name="transactionInterceptor" ref="transactionInterceptor"/>
                    </bean>
                    
                    <bean id="customAdvisor" class="com.mycompany.MyAdvisor"/>
                    
                    <bean id="businessObject1" class="com.mycompany.BusinessObject1">
                    <!-- Properties omitted -->
                    </bean>
                    
                    <bean id="businessObject2" class="com.mycompany.BusinessObject2"/>
                

如果你想要把相同的通知一致性地应用到许多业务对象上,DefaultAdvisorAutoProxyCreator是非常有用的。 一旦框架的定义已经完成,你可以简单地加入新的业务对象而不必包括特定的代理配置。你也可以很容易的去掉额外的切面--例如,跟踪或者性能监视切面--仅仅对配置作很小的修改。

DefaultAdvisorAutoProxyCreator支持过滤(通过使用一个命名约定让只有特定的通知器被评估,允许在同一个工厂里使用多个不同配置的AdvisorAutoProxyCreator)和排序。通知器可以实现org.springframework.core.Ordered接口来确保以正确的顺序被应用。 上面例子里的TransactionAttributeSourceAdvisor 有一个可配置的序号值;缺省情况下是没有排序的。

7.9.1.3. AbstractAdvisorAutoProxyCreator

这是DefaultAdvisorAutoProxyCreator的父类。如果在某些情况下框架提供的DefaultAdvisorAutoProxyCreator不能满足你的需要,你可以通过继承这个类来创建你自己的自动代理创建器。

7.9.2. 使用元数据驱动的自动代理

一个非常重要的自动代理类型是由元数据驱动的。这提供了一种和.NET ServicedComponents相似的编程模型。 作为使用类似EJB里的XML描述符的替代,对于事务管理和其它企业服务的配置都将被保存在源代码级别的属性里。

在这个情况下,你使用DefaultAdvisorAutoProxyCreator和可以理解元数据属性的通知器。 元数据被保存在候选通知器的切入点部分中,而不是在自动代理创建类本身。

这是一个DefaultAdvisorAutoProxyCreator的特殊例子,它本身没有什么特别。(元数据的相关代码保存在通知器的切入点里,而不是AOP框架里)。

JPetStore示例应用程序的/attributes 目录显示了如何使用参数驱动的自动代理。在这个例子里,不需要使用TransactionProxyFactoryBean。因为使用了元数据相关的切入点, 所以简单在业务对象上定义事务属性就足够了。在/WEB-INF/declarativeServices.xml里的bean定义包括了下面的片断,注意这是通用的,可以被用在JPetStore以外的地方:

<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"/>
                
                <bean class="org.springframework.transaction.interceptor.TransactionAttributeSourceAdvisor">
                <property name="transactionInterceptor" ref="transactionInterceptor"/>
                </bean>
                
                <bean id="transactionInterceptor"
                class="org.springframework.transaction.interceptor.TransactionInterceptor">
                <property name="transactionManager" ref="transactionManager"/>
                <property name="transactionAttributeSource">
                <bean class="org.springframework.transaction.interceptor.AttributesTransactionAttributeSource">
                <property name="attributes" ref="attributes"/>
                </bean>
                </property>
                </bean>
                
            <bean id="attributes" class="org.springframework.metadata.commons.CommonsAttributes"/>

DefaultAdvisorAutoProxyCreator bean定义(名字是不重要的,因此甚至可以在定义里省略它)将在当前应用程序上下文中查找所有合适的切入点。在这个例子里,TransactionAttributeSourceAdvisor类型的“transactionAdvisor”bean定义将应用到带有一个事务属性的类或方法上。 TransactionAttributeSourceAdvisor的构造器依赖于一个TransactionInterceptor。这个例子里通过自动织入解决了这个问题。AttributesTransactionAttributeSource依赖于一个org.springframework.metadata.Attributes接口的实现。 在这个代码片断里,“attributes”bean使用Jakarta Commons Attributes API来获取属性信息以满足这个要求。(应用程序代码必须已经使用Commons Attribut来es的编译任务编译过了。)

JPetStore示例应用程序的 /annotation 目录包括了一个由JDK 1.5+注解驱动的自动代理的模拟例子。 下面的配置允许自动检测Spring的Transactional注解,这可以为包含注解的bean提供隐式代理:

<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"/>
                
                <bean class="org.springframework.transaction.interceptor.TransactionAttributeSourceAdvisor">
                <property name="transactionInterceptor" ref="transactionInterceptor"/>
                </bean>
                
                <bean id="transactionInterceptor"
                class="org.springframework.transaction.interceptor.TransactionInterceptor">
                <property name="transactionManager" ref="transactionManager"/>
                <property name="transactionAttributeSource">
                <bean class="org.springframework.transaction.annotation.AnnotationTransactionAttributeSource"/>
                </property>
            </bean>

这里定义的TransactionInterceptor依赖于一个PlatformTransactionManager定义, 后者没有被包括在这个通用的文件里(虽然它可以被包括在这里)因为它在应用程序的事务需求规范中指定(在这个例子里使用JTA,而在其它情况下,可以是Hibernate,JDO或者JDBC):

<bean id="transactionManager" 
        class="org.springframework.transaction.jta.JtaTransactionManager"/>

提示

如果你只需要声明式事务管理,使用这些通用的XML定义将导致Spring自动代理所有带有事务属性的类或者方法。你将不需要直接使用AOP工作, 这个编程模型和.NET的ServicedComponents相似。

这个架构是可以扩展的。可以在自定义属性的基础上进行自动代理。你所需要做的是:

  • 定义你自己的自定义属性

  • 使用必要的通知说明一个通知器,也包括一个切入点,后者可以被类或者方法上的自定义属性触发。 你也许能够使用已有的通知,而仅仅实现一个能够处理自定义属性的静态切入点。

可以让这些通知器对于每个被通知对象(例如,mixins)都是唯一的:仅仅需要在bean定义中被定义为原型而不是单例。 例如,在上面所显示的Spring测试集中的LockMixin引入拦截器可以和一个属性驱动的切入点联合定位一个mixin, 像这里显示的这样。我们使用通用的DefaultPointcutAdvisor,使用JavaBean属性进行配置:

<bean id="lockMixin" class="org.springframework.aop.LockMixin"
                scope="prototype"/>
                
                <bean id="lockableAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor"
                scope="prototype">
                <property name="pointcut" ref="myAttributeAwarePointcut"/>
                <property name="advice" ref="lockMixin"/>
                </bean>
                
            <bean id="anyBean" class="anyclass" ...

如果参数相关的切入点匹配anyBean或其它bean定义里的任何方法,mixin将被应用。 注意lockMixinlockableAdvisor的定义都是原型。 myAttributeAwarePointcut切入点可以是个单例,因为它没有为单个被通知对象保持状态。