Spring AOP (四)

前面我们学习了AOP的代理模式,匹配连接点,以及创建,配置,匹配Proxy bean,今天我们学习使用IOC来配置AOP的框架类。

所有的Spring的代理工厂都继承了org.springframework.aop.ProxyConfig,一个这样的配置可以生成多个AOP代理,而所有的AOP代理都包含一个队配置对象的引用。事实上,ProxyConfig的子类通常也可以作为代理工厂。ProxyConfig下面有一些重要的类:

  • AdvisedSupport : extends ProxyConfig,同时实现了Advised接口,在一个单独的TargetSource对象中保留了对象的配置信息。

Spring源代码声明:

//org.springframework.aop.framework.AopProxy
public class AdvisedSupport extends ProxyConfig implements Advised {...}
  • ProxyFactoryBean : extends ProxyCreatorSupport 用于在Spring中以声明编程方式定义代理,是一个工厂Bean。

Spring源代码声明:

public class ProxyFactoryBean extends ProxyCreatorSupport
        implements FactoryBean<Object>, BeanClassLoaderAware, BeanFactoryAware {...}

ProxyFactoryBean实现了FactoryBean,BeanClassLoaderAware,BeanFactoryAware接口,用于创建工厂以及处理。并开放了开发者连接接口。

  • ProxyFactory : 功能同ProxyFactoryBean,用于编程方式创建代理。
public class ProxyFactory extends ProxyCreatorSupport {...}

ProxyFactory 继承了ProxyCreatorSupport,用于创建代理支持。实际中我们使用IOC创建代理的方式应当是ProxyFactoryBean,ProxyCreatorSupport继承了AdvisedSupport和AdvisedSupport的属性,而且还包含有自己的属性。

使用ProxyFactoryBean

配置一个ProxyFactoryBean的步骤:

  1. 定义Advisor或独立的Pointcut与Advice
  2. 定义interceptorNames

具体xml的配置如下:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

        <!-- 定义前置通知Bean -->
        <bean id="advice" class="boy.aop.advice.BeforeMethod">

        </bean>

        <bean id="advisor" class="org.springframework.aop.support.DefaultPointcutAdvisor">
            <property name="advice" ref="advice"></property>
            <property name="pointcut">
                <bean class="org.springframework.aop.support.JdkRegexpMethodPointcut">
                    <property name="pattern" >
                        <value>.*get.*</value>
                    </property>
                </bean>
            </property>
        </bean>

        <bean id="person" class="org.springframework.aop.framework.ProxyFactoryBean">
            <!-- 我们不希望其他Bean单独方法,所以这里的target使用内部bean -->
            <property name="target">
                <bean class="boy.aop.pointcut.PointcutMethod">
                    <property name="age"><value>21</value></property>
                    <property name="name"><value>Boy</value></property>
                    <property name="title"><value>Winner</value></property>
                </bean>
            </property>

            <!-- 我们使用interceptorNames拦截器来连接通知/通知者 -->
            <!-- 属性的list集合里面只能放 通知/通知者。-->
            <property name="interceptorNames">
                <list>
                    <value>advisor</value>
                </list>
            </property>
        </bean>

</beans>

简单测试类

package boy.aop.pointcut;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;

/**
 * Created by Boy on 2016/12/14 21:46.
 */
public class ProxyFactoryBeanTest {

    public static void main(String[] args){
        ApplicationContext ac = new FileSystemXmlApplicationContext("/src/main/resources/SpringProxy.xml");
        PointcutMethod pm = (PointcutMethod) ac.getBean("person");
        pm.getAge();
        pm.getName();
        pm.getTitle();

    }

}

运行结果

BeforeMethod : now we start before!
我们处理 getAge 方法
BeforeMethod : now we start before!
我们处理 getName 方法
BeforeMethod : now we start before!
我们处理 getName 方法

Process finished with exit code 0

可以看到,我们成功执行,同前面的例子我们的代码简洁了好多。现在我们再考虑一下,假如我们需要代理另外一个或者多个target,它的Advisor与上面的完全相同,只是target不同,我们是否要写成下面的样子?

    <bean id="person" class="org.springframework.aop.framework.ProxyFactoryBean">
        <!-- 我们不希望其他Bean单独方法,所以这里的target使用内部bean -->
        <property name="target">
            <bean class="boy.aop.pointcut.PointcutMethod">
                <property name="age"><value>21</value></property>
                <property name="name"><value>Boy</value></property>
                <property name="title"><value>ZMM Love ZKK</value></property>
            </bean>
        </property>

        <!-- 我们使用interceptorNames拦截器来连接通知/通知者 -->
        <!-- 属性的list集合里面只能放 通知/通知者。-->
        <property name="interceptorNames">
            <list>
                <value>advisor</value>
            </list>
        </property>
    </bean>

    <bean id="person2" class="org.springframework.aop.framework.ProxyFactoryBean">
        <!-- 我们不希望其他Bean单独方法,所以这里的target使用内部bean -->
        <property name="target">
            <bean class="boy.aop.pointcut.PointcutMethod">
                <property name="age"><value>19</value></property>
                <property name="name"><value>ZMM</value></property>
                <property name="title"><value>ZKK Love ZMM</value></property>
            </bean>
        </property>

        <!-- 我们使用interceptorNames拦截器来连接通知/通知者 -->
        <!-- 属性的list集合里面只能放 通知/通知者。-->
        <property name="interceptorNames">
            <list>
                <value>advisor</value>
            </list>
        </property>
    </bean>

可以看到代码的冗余量就体现了,假如有100个,我们的配置文件几乎不能看了,如何避免呢?答案就是使用Bean定义的abstract和parent。通过使用abstract属性可以将ProxyFactoryBean的共有部分作为一个抽象Bean,而需要使用公用属性的子Bean只需要继承这个抽象Bean极客。我们修改下上面的配置文件:

   <bean id="proxyCommon" class="org.springframework.aop.framework.ProxyFactoryBean" abstract="true">
        <property name="interceptorNames">
            <list>
                <value>advisor</value>
            </list>
        </property>
    </bean>

    <bean id="person" parent="proxyCommon">
        <!-- 使用内部Bean -->
        <property name="target">
            <bean class="boy.aop.pointcut.PointcutMethod">
                <property name="age" value="21"></property>
                <property name="name" value="Boy"></property>
                <property name="title" value="ZKK Love ZMM"></property>
            </bean>
        </property>
    </bean>

    <bean id="person1" parent="proxyCommon">
        <!-- 使用内部Bean -->
        <property name="target">
            <bean class="boy.aop.pointcut.PointcutMethod">
                <property name="age" value="19"></property>
                <property name="name" value="ZMM"></property>
                <property name="title" value="ZMM Love ZKK"></property>
            </bean>
        </property>
    </bean>

测试类

    ApplicationContext ac = new FileSystemXmlApplicationContext("/src/main/resources/SpringProxy.xml");
    PointcutMethod pm = (PointcutMethod) ac.getBean("person");
    pm.getAge();
    pm.getName();
    pm.getTitle();

    PointcutMethod pm1 = (PointcutMethod) ac.getBean("person1");
    pm1.getAge();
    pm1.getName();
    pm1.getTitle();

运行结果

23:06:38.062 [main] DEBUG org.springframework.aop.framework.CglibAopProxy - Method is declared on Advised interface: public abstract boolean org.springframework.aop.framework.Advised.isInterfaceProxied(java.lang.Class)
23:06:38.062 [main] DEBUG org.springframework.aop.framework.CglibAopProxy - Method is declared on Advised interface: public abstract java.lang.Class org.springframework.aop.TargetClassAware.getTargetClass()
BeforeMethod : now we start before!
我们处理 getAge 方法
BeforeMethod : now we start before!
我们处理 getName 方法
BeforeMethod : now we start before!
我们处理 getName 方法
23:06:38.120 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Returning cached instance of singleton bean 'person1'
23:06:38.120 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Returning cached instance of singleton bean 'advisor'
23:06:38.121 [main] DEBUG org.springframework.aop.framework.ProxyFactoryBean - Advice has changed; recaching singleton instance
23:06:38.121 [main] DEBUG org.springframework.aop.framework.CglibAopProxy - Creating CGLIB proxy: target source is SingletonTargetSource for target object [boy.aop.pointcut.PointcutMethod@3c87521]
BeforeMethod : now we start before!
我们处理 getAge 方法
BeforeMethod : now we start before!
我们处理 getName 方法
BeforeMethod : now we start before!
我们处理 getName 方法

Process finished with exit code 0

我们对配置文件进行了优化,成功实现了,减少了冗余,但是我们还有继续优化的可能性,明天再写吧。今天就先写到这里,这两天也要考试了,还是需要应付应付的~~

最后由 不一样的少年 编辑于2016年12月16日 23:12