1. 程式人生 > >Spring學習筆記 —— AOP標籤詳解()

Spring學習筆記 —— AOP標籤詳解()

引言

但是,除了面向切面程式設計之外,AOP的名字空間中還包含了一些重要的標籤,比如”scoped-proxy”。這篇文章就會詳細介紹這個標籤的作用,以及它的實現方式分析。

scoped-proxy 標籤介紹

Spring學習筆記 —— 從IOC說起,我們介紹過,Spring中的Bean是有Scope屬性的,代表著bean的生存週期。而Spring中預設的Scope分為”singleton”和”prototype”兩種。

那麼,問題就來了,如果在一個singleton的Bean中引用了一個prototype的Bean,結果會怎樣呢?——在預設情況下,單例會永遠持有一開始構造所賦給它的值。

所以,為了讓我們在每次呼叫這個Bean的時候都能夠得到具體scope中的值,比如prototype,那麼我們希望每次在單例中呼叫這個Bean的時候,得到的都是一個新的prototype,Spring中AOP名字空間中引入了這個標籤。
xml
<aop:scoped-proxy/>

示例

舉個例子。
PrototypeBean.java這個類在初始化的時候會得到當前時間的時間戳,它的scope為prototype(每次獲取都會重新生成一個)。

public class PrototypeBean {
    private Long timeMilis;

    public
PrototypeBean(){ timeMilis = (new Date()).getTime(); } public void printTime() { System.out.println(timeMilis+""); } }

SingletonBean.java這個單例的Bean持有一個PrototypeBean,同時它的printTime()方法輸出PrototypeBean的時間戳。

public class SingletonBean {
    private PrototypeBean prototype;

    public
void printTime() { prototype.printTime(); } public void setPrototype(PrototypeBean prototype) { this.prototype = prototype; } }

scopedProxyBean.xml

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
    http://www.springframework.org/schema/context
    http://www.springframework.org/schema/context/spring-context-3.0.xsd
    http://www.springframework.org/schema/aop 
    http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">

    <bean id="protyotypBean" class="com.study.scopedProxy.PrototypeBean" scope="prototype">
    <aop:scoped-proxy/>
    </bean>

    <bean id="singletonBean" class="com.study.scopedProxy.SingletonBean">
        <property name="prototype">
            <ref bean="protyotypBean" />  
        </property>
    </bean>

</beans>

ScopedProxyMain.java

public class ScopedProxyMain {
    public static void main(String args[]) {
        ApplicationContext app = new ClassPathXmlApplicationContext("scopedProxyBean.xml");

        SingletonBean singleton = app.getBean(SingletonBean.class);

        singleton.printTime();
        //1477958215532
        singleton.printTime();
        //1477958215571
    }
}

如果不加上<aop:scoped-proxy/>這個標籤,那麼兩次將會輸出同樣的時間戳。而我們加上標籤之後,每次呼叫這個Bean的時候,系統就會先取一遍這個Bean,確保我們得到的Bean是在當前的scope當中的。

原始碼解析

那麼具體在程式碼層面,是如何完成這個實現的呢?

我們還是從AopNamespaceHandler先著手。

AopNamespaceHandler.init裡面是對各個標籤進行解析類註冊,我們能看到,scoped-proxy對應的註冊類就是ScopedProxyBeanDefinitionDecorator

registerBeanDefinitionDecorator("scoped-proxy", new ScopedProxyBeanDefinitionDecorator());

而再到這個類裡面,發現它實現了BeanDefinitionDecorator介面,於是檢視其decorate方法。

BeanDefinitionHolder holder =
                ScopedProxyUtils.createScopedProxy(definition, parserContext.getRegistry(), proxyTargetClass);

createScopedProxy

String originalBeanName = definition.getBeanName();
        BeanDefinition targetDefinition = definition.getBeanDefinition();
        String targetBeanName = getTargetBeanName(originalBeanName);

        // 建立一個ScopedProxyFactoryBean,這個Bean中儲存了目標Bean的名稱,
        // 同時在內部儲存了目標Bean定義的引用。注意並沒有對BeanDefinition設定scope,
        //因此這個代理bean的scope就預設是singleton了。
        RootBeanDefinition proxyDefinition = new RootBeanDefinition(ScopedProxyFactoryBean.class);
        proxyDefinition.setDecoratedDefinition(new BeanDefinitionHolder(targetDefinition, targetBeanName));
        proxyDefinition.setOriginatingBeanDefinition(targetDefinition);
        proxyDefinition.setSource(definition.getSource());
        proxyDefinition.setRole(targetDefinition.getRole());

        proxyDefinition.getPropertyValues().add("targetBeanName", targetBeanName);
        // 預設情況下proxyTargetClass都是True
        if (proxyTargetClass) {
            targetDefinition.setAttribute(AutoProxyUtils.PRESERVE_TARGET_CLASS_ATTRIBUTE, Boolean.TRUE);

        }
        else {
            proxyDefinition.getPropertyValues().add("proxyTargetClass", Boolean.FALSE);
        }

        proxyDefinition.setAutowireCandidate(targetDefinition.isAutowireCandidate());
        proxyDefinition.setPrimary(targetDefinition.isPrimary());
        if (targetDefinition instanceof AbstractBeanDefinition) {
            proxyDefinition.copyQualifiersFrom((AbstractBeanDefinition) targetDefinition);
        }


        targetDefinition.setAutowireCandidate(false);
        targetDefinition.setPrimary(false);

        // 因為在後面還是會用到目標Bean,因此也需要將它的定義註冊到Registry中
        registry.registerBeanDefinition(targetBeanName, targetDefinition);

        //將新的Bean返回
        return new BeanDefinitionHolder(proxyDefinition, originalBeanName, definition.getAliases());

對於BeanDefintion的解析,到這裡就可以看作結束了。那麼,這個ScopedProxyFactoryBean,又會在什麼時候建立,怎麼建立呢?

關鍵就是要看ScopedProxyFactoryBean.setBeanFactory

@Override
    public void setBeanFactory(BeanFactory beanFactory) {
        if (!(beanFactory instanceof ConfigurableBeanFactory)) {
            throw new IllegalStateException("Not running in a ConfigurableBeanFactory: " + beanFactory);
        }
        ConfigurableBeanFactory cbf = (ConfigurableBeanFactory) beanFactory;

        this.scopedTargetSource.setBeanFactory(beanFactory);

        ProxyFactory pf = new ProxyFactory();
        pf.copyFrom(this);
        //這個這個代理Bean設定Bean的來源,很重要。這個方法決定了,
        //每次取target的時候,都會呼叫beanFactory.getBean。
        pf.setTargetSource(this.scopedTargetSource);

        Class<?> beanType = beanFactory.getType(this.targetBeanName);
        if (beanType == null) {
            throw new IllegalStateException("Cannot create scoped proxy for bean '" + this.targetBeanName +
                    "': Target type could not be determined at the time of proxy creation.");
        }
        if (!isProxyTargetClass() || beanType.isInterface() || Modifier.isPrivate(beanType.getModifiers())) {
            pf.setInterfaces(ClassUtils.getAllInterfacesForClass(beanType, cbf.getBeanClassLoader()));
        }

        // 構建一個introduction,這個introduction只實現了ScopedObject的所有介面
        ScopedObject scopedObject = new DefaultScopedObject(cbf, this.scopedTargetSource.getTargetBeanName());
        //將scopedObject作為通知加入到proxy中,DelegatingIntroductionInterceptor作為通知的攔截器,
        //實際上是使得所有在proxyBean上呼叫的'getTargetObject`方法都被代理到了`DefaultScopedObject`中。
        //在增加通知的同時會生成DefaultIntroductionAdvisor 作為advisor,
        pf.addAdvice(new DelegatingIntroductionInterceptor(scopedObject));

        pf.addInterface(AopInfrastructureBean.class);

        this.proxy = pf.getProxy(cbf.getBeanClassLoader());
    }

而在後面,根據advisor生成相應的代理類,在之前已經解釋過了,就不再贅述了。

而關於Introduction,這裡有一個很好的例子。你也可以將cglib生成的class檔案儲存,就能夠發現最後的Bean中的確存在著getTargetObject這個方法。

個人認為註冊Advice的作用並不是很大,因為我們已經為這個ProxyBean設定好它的target source了,對於scoped-proxy的目的也就達到了。

最後來看一下真正被使用的地方把。

DynamicAdvisedInterceptor.intercept,前文也說過了,這個是CglibAOP的攔截類。所有被攔截的方法都會首先呼叫這裡的intercept。

public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
            Object oldProxy = null;
            boolean setProxyContext = false;
            Class<?> targetClass = null;
            Object target = null;
            try {
                if (this.advised.exposeProxy) {
                    // Make invocation available if necessary.
                    oldProxy = AopContext.setCurrentProxy(proxy);
                    setProxyContext = true;
                }
                //最重要的就是這裡,getTarget,
                //這會到之前設定過的TargetSource中去獲取TargetObject,
                //也就是SimpleBeanTarget的getTarget方法,
                //到這一步,scoped的proxy已經完成。
                target = getTarget();
                //省略後續呼叫
        }

小結

在這裡我們介紹了AOP的一個重要標籤,scoped-proxy。它是由ScopedProxyFactoryBean進行建立的,在建立的時候,為生成代理Bean的ProxyFactory指定了TargetSource,因此在每次攔截方法,進行呼叫之前,首先都會到指定的TargetSouce,也就是SimpleBeanTargetSource中獲取對應Scoped下的Bean。

此外,ScopedProxyFactory還為這個Bean增加了getTargetObject的方法(使用Introduction),因此所有帶上了這個標籤的Bean,也就預設實現了ScopedObject的介面,可以呼叫getTargetObject方法。這個方法的意義在於,因為代理Bean的scope是預設singleton的,這也就意味著,我們每次呼叫applicationContext.getBean方法,總是返回同一個代理bean,如果我們想要獲得scope下真正的bean的話,就可以呼叫getTargetObject方法了。