6.4.4. 代理接口

考虑一个简单的proxyfactorybean实例。这个例子涉及:

  • 代理的目标bean。这是示例中的PersonTarget bean定义。
  • 用来提供通知的顾问和拦截器。

AOP代理bean定义,用于指定目标对象( personTarget bean)、代理接口和要应用的通知。
下面的列表显示了示例:

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

<bean id="myAdvisor" class="com.mycompany.MyAdvisor">
    <property name="someProperty" value="Custom string property value"/>
</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"/>

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

注意,interceptornames属性采用一个字符串列表,其中包含当前工厂中拦截器或顾问的bean名称。您可能使用advisors, interceptors, before, after returning和throws通知。顾问的顺序也是意义重大。

您可能想知道为什么列表不包含bean引用。原因是,如果ProxyFactoryBean的singleton属性设置为false,它必须能够返回独立的代理实例。如果任何顾问本身是原型,则需要返回一个独立的实例,因此需要能够从工厂获取原型的实例。持有证明文件是不够的。

前面显示的PersonBean定义可以用来代替Person实现,如下所示:

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

在同一个IOC上下文中的其他bean可以表示一个强类型的依赖关系,就像普通的Java对象一样。以下示例显示了如何执行此操作:

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

此示例中的PersonUser类公开Person类型的属性。就其而言,可以透明地使用AOP代理来代替“真实的”person实现。但是,它的类将是动态代理类。可以将其转换为通知的接口(稍后讨论)。
您可以使用匿名内部bean隐藏目标和代理之间的区别。只有ProxyFactoryBean定义不同。该通知仅用于完整性。下面的示例演示如何使用匿名内部bean:

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

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

<bean id="person" class="org.springframework.aop.framework.ProxyFactoryBean">
    <property name="proxyInterfaces" value="com.mycompany.Person"/>
    <!-- 使用内部bean,而不是对目标的本地引用-->
    <property name="target">
        <bean class="com.mycompany.PersonImpl">
            <property name="name" value="Tony"/>
            <property name="age" value="51"/>
        </bean>
    </property>
    <property name="interceptorNames">
        <list>
            <value>myAdvisor</value>
            <value>debugInterceptor</value>
        </list>
    </property>
</bean>

使用匿名内部bean的优点是只有一个Person类型的对象。如果我们希望防止应用程序上下文的用户获得对未通知对象的引用,或者需要避免Spring IOC自动装配的任何模糊性,那么这是非常有用的。也可以说,ProxyFactoryBean定义是独立的,这是一个优势。但是,有时能够从工厂获得未通知的目标实际上是一种需要(例如,在某些测试场景中)。

6.4.5. 代理类

如果您需要代理一个类,而不是一个或多个接口呢?

假设在前面的示例中,没有 Person 接口。我们需要通知一个名为person的类,该类没有实现任何业务接口。在这种情况下,可以将spring配置为使用CGLIB代理,而不是动态代理。为此,请将前面显示的ProxyFactoryBean上的proxyTargetClass属性设置为true。虽然最好是编程到接口,但是在处理遗留代码时,通知不实现接口的类情况还是有。(一般来说,Spring不是规定性的。虽然它使应用好的实践变得容易,但它避免了强制使用特定的方法。)
如果您愿意,您可以在任何情况下强制使用CGLIB,即使您确实有接口。
CGLIB代理通过在运行时生成目标类的子类来工作。Spring将生成的子类配置为将方法调用委托给原始目标。子类用于实现装饰器模式,织入到通知中。
CGLIB代理通常对用户是透明的。但是,有一些问题需要考虑:

  • 无法通知final方法,因为它们不能被重写。
  • 不需要CGLIB添加到类路径中。从Spring3.2开始,CGLIB被重新包装并包含在Spring核心Jar包中。换句话说,基于CGLIB的AOP和JDK动态代理一样“开箱即用”。

CGLIB代理和动态代理的性能差别不大。在这种情况下,性能不应是决定性的考虑因素。

6.4.6.使用“全局”顾问

通过向拦截器名称添加星号,所有具有与星号之前部分匹配的bean名称的顾问都将添加到顾问链中。如果您需要添加一组标准的“全局”顾问,这将非常有用。以下示例定义了两个全局顾问:

<bean id="proxy" class="org.springframework.aop.framework.ProxyFactoryBean">
    <property name="target" ref="service"/>
    <property name="interceptorNames">
        <list>
            <value>global*</value>
        </list>
    </property>
</bean>
<bean id="global_debug" class="org.springframework.aop.interceptor.DebugInterceptor"/>
<bean id="global_performance" class="org.springframework.aop.interceptor.PerformanceMonitorInterceptor"/>