Spring源碼學(xué)習(xí)(6) —— 基于Schema的aop配置加載流程分析

1.引子

通過前面幾篇的學(xué)習(xí)藐鹤,我們已經(jīng)知道了aop實(shí)現(xiàn)的基本原理,但是定義一個切面的過程是比較繁瑣的杂靶,我們需要自己是實(shí)現(xiàn)特定的接口持搜。事實(shí)上,Spring提供了更加簡單的方式來定義切面辞居,比如說使用@AspectJ注解或者基于Schema配置的方式楷怒。今天我們就基于Schema的方式,看一下這種方式下aop的配置時(shí)如何被加載的瓦灶。話不多說鸠删,先上一個例子看看如何基于Schema配置一個切面。

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

    <bean id="adviceMethods" class="com.youzan.shys.advisor.AdviceMethods" />
    <bean id="waiterTarget" class="com.youzan.shys.advisor.NaiveWaiter" />

    <aop:config proxy-target-class="true">
        <aop:aspect ref="adviceMethods">
            <aop:before method="preGreeting" pointcut="target(com.youzan.shys.advisor.NaiveWaiter) and execution(* greetTo(..))" />
        </aop:aspect>
    </aop:config>
</beans>

在這個例子中贼陶,我們通過切點(diǎn)表達(dá)式刃泡,對NaiveWaiter類的greetTo()方法添加了一個前置增強(qiáng),具體增強(qiáng)為AdviceMethods類的preGreeting()方法碉怔。接下來我們主要就來看看這些配置是如何被加載的烘贴。

2.配置文件讀取流程

Location ——> ResourceLoader.getResource
        Resource ——> DocumentLoader.loadDocument
            Document/Element ——> BeanDefinitonParser
                    BeanDefinition
2.1獲取命名空間

熟悉Spring IOC的朋友都知道,Spring從配置文件中讀取bean配置并將其轉(zhuǎn)換成BeanDefinition大致上需要經(jīng)過以上幾個步驟撮胧,本文重點(diǎn)關(guān)注最后一步桨踪,即Spring是如何將Element轉(zhuǎn)化為BeanDefinition的,代碼直接定位到DefaultBeanDefinitionDocumentReader#parseBeanDefinitions:

protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
    // 是否為默認(rèn)的命名空間趴樱,即判斷root的命名空間uri是否為空或者等于http://www.springframework.org/schema/beans
    if (delegate.isDefaultNamespace(root)) {
        NodeList nl = root.getChildNodes();
        for (int i = 0; i < nl.getLength(); i++) {
            Node node = nl.item(i);
            if (node instanceof Element) {
                Element ele = (Element) node;
                if (delegate.isDefaultNamespace(ele)) {
                    // 默認(rèn)命名空間馒闷,比如<bean id="xxx" />
                    parseDefaultElement(ele, delegate);
                }
                else {
                    // 非默認(rèn)命名空間酪捡,比如<aop:config />
                    delegate.parseCustomElement(ele);
                }
            }
        }
    }
    else {
        delegate.parseCustomElement(root);
    }
}

在解析bean定義的時(shí)候,根據(jù)命名空間分成了兩個分支纳账。顯然逛薇,由于在我們的例子中不是默認(rèn)命名空間,因此會進(jìn)入第二個分支疏虫,進(jìn)去看一看:

public BeanDefinition parseCustomElement(Element ele, @Nullable BeanDefinition containingBd) {
    // 獲取命名空間uri永罚,即在xml頭部中配置的這一串內(nèi)容xmlns:aop="http://www.springframework.org/schema/aop"
    String namespaceUri = getNamespaceURI(ele);
    if (namespaceUri == null) {
        return null;
    }

    // 這里比較簡單,就是根據(jù)namespaceUri實(shí)例化對應(yīng)的namespaceHandler卧秘,在這里就是AopNamespaceHandler
    NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
    if (handler == null) {
        error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele);
        return null;
    }
    return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
}

在解析自定義元素的時(shí)候呢袱,首先獲取命名空間uri,根據(jù)uri獲取對應(yīng)的hander翅敌,然后再進(jìn)行解析羞福。AopNamespaceHandler中針對每一個標(biāo)簽,都有一個具體的解析器來負(fù)責(zé)解析蚯涮。

public void init() {
    // In 2.0 XSD as well as in 2.1 XSD.
    registerBeanDefinitionParser("config", new ConfigBeanDefinitionParser());
    registerBeanDefinitionParser("aspectj-autoproxy", new AspectJAutoProxyBeanDefinitionParser());
    registerBeanDefinitionDecorator("scoped-proxy", new ScopedProxyBeanDefinitionDecorator());

    // Only in 2.0 XSD: moved to context namespace as of 2.1
    registerBeanDefinitionParser("spring-configured", new SpringConfiguredBeanDefinitionParser());
}
2.2 解析具體標(biāo)簽

可以看到治专,AopNamespace中一共只有4個標(biāo)簽,在我們的例子中遭顶,首先就是config標(biāo)簽张峰,于是定位到ConfigBeanDefinitionParser#parse:

public BeanDefinition parse(Element element, ParserContext parserContext) {
    CompositeComponentDefinition compositeDef =
            new CompositeComponentDefinition(element.getTagName(), parserContext.extractSource(element));
    parserContext.pushContainingComponent(compositeDef);

    // 注冊一個名為org.springframework.aop.config.internalAutoProxyCreator的bean,默認(rèn)實(shí)現(xiàn)為AspectJAwareAdvisorAutoProxyCreator
    // 同時(shí)根據(jù)配置設(shè)置一些代理屬性棒旗,比如proxy-target-class和expose-proxy
    configureAutoProxyCreator(parserContext, element);

    // 獲取config的子標(biāo)簽喘批,并分別進(jìn)行解析
    List<Element> childElts = DomUtils.getChildElements(element);
    for (Element elt: childElts) {
        String localName = parserContext.getDelegate().getLocalName(elt);
        if (POINTCUT.equals(localName)) {
            parsePointcut(elt, parserContext);
        }
        else if (ADVISOR.equals(localName)) {
            parseAdvisor(elt, parserContext);
        }
        else if (ASPECT.equals(localName)) {
            parseAspect(elt, parserContext);
        }
    }

    parserContext.popAndRegisterContainingComponent();
    return null;
}

config一共只有3種類型的子標(biāo)簽,分別為advisor铣揉、pointcut和aspect饶深。在我們的例子中,首先解析的應(yīng)該就是aspect老速,于是進(jìn)入parseAspect方法粥喜,該方法主要做了兩件事:

  1. 加載增強(qiáng)節(jié)點(diǎn)bean定義(6種增強(qiáng)凸主,包括引介增強(qiáng))
  2. 獲取aspect的所有子標(biāo)簽并進(jìn)行解析
private void parseAspect(Element aspectElement, ParserContext parserContext) {
    String aspectId = aspectElement.getAttribute(ID);
    String aspectName = aspectElement.getAttribute(REF);

    try {
        // ParseState是一個基于LinkedList的結(jié)構(gòu)橘券,每個LinkedList里存放的是Entry類型的對象
        // 在解析bean的過程中,每次操作開始時(shí)將一個Entry入棧卿吐,每次操作結(jié)束將Entry出棧
        this.parseState.push(new AspectEntry(aspectId, aspectName));
        List<BeanDefinition> beanDefinitions = new ArrayList<>();
        List<BeanReference> beanReferences = new ArrayList<>();

        // 如果配置了引介增強(qiáng)旁舰,獲取其bean定義
        List<Element> declareParents = DomUtils.getChildElementsByTagName(aspectElement, DECLARE_PARENTS);
        for (int i = METHOD_INDEX; i < declareParents.size(); i++) {
            Element declareParentsElement = declareParents.get(i);
            beanDefinitions.add(parseDeclareParents(declareParentsElement, parserContext));
        }

        // 獲取aspect的所有子標(biāo)簽并進(jìn)行解析
        NodeList nodeList = aspectElement.getChildNodes();
        boolean adviceFoundAlready = false;
        for (int i = 0; i < nodeList.getLength(); i++) {
            Node node = nodeList.item(i);
            // 如果是增強(qiáng)節(jié)點(diǎn),將aspect節(jié)點(diǎn)對應(yīng)的bean引用加入beanReferences列表
            if (isAdviceNode(node, parserContext)) {
                if (!adviceFoundAlready) {
                    adviceFoundAlready = true;
                    if (!StringUtils.hasText(aspectName)) {
                        parserContext.getReaderContext().error(
                                "<aspect> tag needs aspect bean reference via 'ref' attribute when declaring advices.",
                                aspectElement, this.parseState.snapshot());
                        return;
                    }
                    beanReferences.add(new RuntimeBeanReference(aspectName));
                }

                // 解析增強(qiáng)節(jié)點(diǎn)
                AbstractBeanDefinition advisorDefinition = parseAdvice(
                        aspectName, i, aspectElement, (Element) node, parserContext, beanDefinitions, beanReferences);
                beanDefinitions.add(advisorDefinition);
            }
        }

        AspectComponentDefinition aspectComponentDefinition = createAspectComponentDefinition(
                aspectElement, aspectId, beanDefinitions, beanReferences, parserContext);
        parserContext.pushContainingComponent(aspectComponentDefinition);

        // aspect節(jié)點(diǎn)下可以配置pointcut屬性嗡官,這里獲取所有的切點(diǎn)并進(jìn)行解析
        List<Element> pointcuts = DomUtils.getChildElementsByTagName(aspectElement, POINTCUT);
        for (Element pointcutElement : pointcuts) {
            parsePointcut(pointcutElement, parserContext);
        }

        parserContext.popAndRegisterContainingComponent();
    }
    finally {
        this.parseState.pop();
    }
}
2.3 advice標(biāo)簽解析

下面分別看下這里涉及到的幾個主要方法箭窜,也就是isAdviceNode、parseAdvice和parsePointcut衍腥。
isAdviceNode方法很簡單磺樱,就是用來判斷該節(jié)點(diǎn)是否是5中增強(qiáng)節(jié)點(diǎn)之一:

private boolean isAdviceNode(Node aNode, ParserContext parserContext) {
    if (!(aNode instanceof Element)) {
        return false;
    }
    else {
        String name = parserContext.getDelegate().getLocalName(aNode);
        return (BEFORE.equals(name) || AFTER.equals(name) || AFTER_RETURNING_ELEMENT.equals(name) ||
                AFTER_THROWING_ELEMENT.equals(name) || AROUND.equals(name));
    }
}

parseAdvice方法邏輯其實(shí)也很簡單:先根據(jù)增強(qiáng)類型生成bean定義纳猫,然后將增強(qiáng)封裝成切面,最后將切面注冊到beanFactory中竹捉。

private AbstractBeanDefinition parseAdvice(
        String aspectName, int order, Element aspectElement, Element adviceElement, ParserContext parserContext,
        List<BeanDefinition> beanDefinitions, List<BeanReference> beanReferences) {

    try {
        this.parseState.push(new AdviceEntry(parserContext.getDelegate().getLocalName(adviceElement)));

        // create the method factory bean
        RootBeanDefinition methodDefinition = new RootBeanDefinition(MethodLocatingFactoryBean.class);
        methodDefinition.getPropertyValues().add("targetBeanName", aspectName);
        methodDefinition.getPropertyValues().add("methodName", adviceElement.getAttribute("method"));
        methodDefinition.setSynthetic(true);

        // create instance factory definition
        RootBeanDefinition aspectFactoryDef =
                new RootBeanDefinition(SimpleBeanFactoryAwareAspectInstanceFactory.class);
        aspectFactoryDef.getPropertyValues().add("aspectBeanName", aspectName);
        aspectFactoryDef.setSynthetic(true);

        // 根據(jù)增強(qiáng)類型生成bean定義
        AbstractBeanDefinition adviceDef = createAdviceDefinition(
                adviceElement, parserContext, aspectName, order, methodDefinition, aspectFactoryDef,
                beanDefinitions, beanReferences);

        // 將advice封裝成advisor芜辕,同時(shí)設(shè)置order屬性
        RootBeanDefinition advisorDefinition = new RootBeanDefinition(AspectJPointcutAdvisor.class);
        advisorDefinition.setSource(parserContext.extractSource(adviceElement));
        advisorDefinition.getConstructorArgumentValues().addGenericArgumentValue(adviceDef);
        if (aspectElement.hasAttribute(ORDER_PROPERTY)) {
            advisorDefinition.getPropertyValues().add(
                    ORDER_PROPERTY, aspectElement.getAttribute(ORDER_PROPERTY));
        }

        // 將advisor注冊到DefaultListableBeanFactory中
        parserContext.getReaderContext().registerWithGeneratedName(advisorDefinition);

        return advisorDefinition;
    }
    finally {
        this.parseState.pop();
    }
}
2.3.1 生成增強(qiáng)的bean定義

根據(jù)增強(qiáng)類型生成bean定義主要包含兩部分內(nèi)容:
1.根據(jù)增強(qiáng)類型如<aop:before />選擇對應(yīng)的增強(qiáng)類對象,這部分在getAdviceClass中完成块差;
2.解析<aop:before />標(biāo)簽中的pointcut屬性侵续,這部分在parsePointcutProperty中完成。

private AbstractBeanDefinition createAdviceDefinition(
        Element adviceElement, ParserContext parserContext, String aspectName, int order,
        RootBeanDefinition methodDef, RootBeanDefinition aspectFactoryDef,
        List<BeanDefinition> beanDefinitions, List<BeanReference> beanReferences) {

    // 根據(jù)增強(qiáng)類型生成bean定義
    RootBeanDefinition adviceDefinition = new RootBeanDefinition(getAdviceClass(adviceElement, parserContext));
    adviceDefinition.setSource(parserContext.extractSource(adviceElement));

    ......中間是一堆屬性設(shè)置憨闰,代碼省略......

    // 解析pointcut屬性
    pointcut = parsePointcutProperty(adviceElement, parserContext);
    if (pointcut instanceof BeanDefinition) {
        cav.addIndexedArgumentValue(POINTCUT_INDEX, pointcut);
        beanDefinitions.add((BeanDefinition) pointcut);
    }
    else if (pointcut instanceof String) {
        RuntimeBeanReference pointcutRef = new RuntimeBeanReference((String) pointcut);
        cav.addIndexedArgumentValue(POINTCUT_INDEX, pointcutRef);
        beanReferences.add(pointcutRef);
    }

    cav.addIndexedArgumentValue(ASPECT_INSTANCE_FACTORY_INDEX, aspectFactoryDef);

    return adviceDefinition;
}

getAdviceClass代碼如下状蜗,一共5種增強(qiáng)類型,與前面isAdviceNode方法一致

private Class<?> getAdviceClass(Element adviceElement, ParserContext parserContext) {
    String elementName = parserContext.getDelegate().getLocalName(adviceElement);
    if (BEFORE.equals(elementName)) {
        return AspectJMethodBeforeAdvice.class;
    }
    else if (AFTER.equals(elementName)) {
        return AspectJAfterAdvice.class;
    }
    else if (AFTER_RETURNING_ELEMENT.equals(elementName)) {
        return AspectJAfterReturningAdvice.class;
    }
    else if (AFTER_THROWING_ELEMENT.equals(elementName)) {
        return AspectJAfterThrowingAdvice.class;
    }
    else if (AROUND.equals(elementName)) {
        return AspectJAroundAdvice.class;
    }
    else {
        throw new IllegalArgumentException("Unknown advice kind [" + elementName + "].");
    }
}

parsePointcutProperty方法如下鹉动,從這里可以看出轧坎,在aspect標(biāo)簽中,必須且只能設(shè)置pointcut泽示、pointcut-ref中的一個屬性眶根。

private Object parsePointcutProperty(Element element, ParserContext parserContext) {
    // 不能同時(shí)設(shè)置pointcut屬性和pointcut-ref屬性
    if (element.hasAttribute(POINTCUT) && element.hasAttribute(POINTCUT_REF)) {
        parserContext.getReaderContext().error(
                "Cannot define both 'pointcut' and 'pointcut-ref' on <advisor> tag.",
                element, this.parseState.snapshot());
        return null;
    }
    else if (element.hasAttribute(POINTCUT)) {
        // Create a pointcut for the anonymous pc and register it.
        String expression = element.getAttribute(POINTCUT);
        AbstractBeanDefinition pointcutDefinition = createPointcutDefinition(expression);
        pointcutDefinition.setSource(parserContext.extractSource(element));
        return pointcutDefinition;
    }
    else if (element.hasAttribute(POINTCUT_REF)) {
        String pointcutRef = element.getAttribute(POINTCUT_REF);
        if (!StringUtils.hasText(pointcutRef)) {
            parserContext.getReaderContext().error(
                    "'pointcut-ref' attribute contains empty value.", element, this.parseState.snapshot());
            return null;
        }
        return pointcutRef;
    }
    // pointcut屬性和pointcut-ref屬性必須設(shè)置其中一個
    else {
        parserContext.getReaderContext().error(
                "Must define one of 'pointcut' or 'pointcut-ref' on <advisor> tag.",
                element, this.parseState.snapshot());
        return null;
    }
}
2.3.2 將增強(qiáng)bean定義封裝成切面bean定義

這一步比較簡單,只是把a(bǔ)dviceDef用AspectJPointcutAdvisor類型重新包裝了一下边琉,同時(shí)設(shè)置了order屬性

2.3.3 將切面bean注冊到BeanFactory中

這就是parseAdvice()最后一步的工作

public String registerWithGeneratedName(BeanDefinition beanDefinition) {
    // 獲取bean注冊的名字
    String generatedName = generateBeanName(beanDefinition);
    // 注冊bean定義
    getRegistry().registerBeanDefinition(generatedName, beanDefinition);
    return generatedName;
}

獲取bean名稱時(shí)属百,如果有重復(fù)的bean名稱存在,默認(rèn)會在原始bean名稱后面通過增加“#+數(shù)字”的方式來生成新的bean名稱以示區(qū)分变姨。具體可參考我的另外一篇文章Spring配置文件id/name重復(fù)定義問題

    public static String generateBeanName(
            BeanDefinition definition, BeanDefinitionRegistry registry, boolean isInnerBean)
            throws BeanDefinitionStoreException {

        ......

        String id = generatedBeanName;
        if (isInnerBean) {
            // Inner bean: generate identity hashcode suffix.
            id = generatedBeanName + GENERATED_BEAN_NAME_SEPARATOR + ObjectUtils.getIdentityHexString(definition);
        }
        else {
            // Top-level bean: use plain class name.
            // Increase counter until the id is unique.
            int counter = -1;
            while (counter == -1 || registry.containsBeanDefinition(id)) {
                counter++;
                id = generatedBeanName + GENERATED_BEAN_NAME_SEPARATOR + counter;
            }
        }
        return id;
    }

至此族扰,parseAdvice分析完成,另外兩個標(biāo)簽的解析parseAdvisor和parsePointCut思路類似定欧,大家可以自行分析??

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末渔呵,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子砍鸠,更是在濱河造成了極大的恐慌扩氢,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,651評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件爷辱,死亡現(xiàn)場離奇詭異录豺,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)饭弓,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,468評論 3 392
  • 文/潘曉璐 我一進(jìn)店門双饥,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人弟断,你說我怎么就攤上這事咏花。” “怎么了阀趴?”我有些...
    開封第一講書人閱讀 162,931評論 0 353
  • 文/不壞的土叔 我叫張陵昏翰,是天一觀的道長苍匆。 經(jīng)常有香客問我,道長棚菊,這世上最難降的妖魔是什么锉桑? 我笑而不...
    開封第一講書人閱讀 58,218評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮窍株,結(jié)果婚禮上民轴,老公的妹妹穿的比我還像新娘。我一直安慰自己球订,他們只是感情好后裸,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,234評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著冒滩,像睡著了一般微驶。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上开睡,一...
    開封第一講書人閱讀 51,198評論 1 299
  • 那天因苹,我揣著相機(jī)與錄音,去河邊找鬼篇恒。 笑死扶檐,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的胁艰。 我是一名探鬼主播款筑,決...
    沈念sama閱讀 40,084評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼腾么!你這毒婦竟也來了奈梳?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,926評論 0 274
  • 序言:老撾萬榮一對情侶失蹤解虱,失蹤者是張志新(化名)和其女友劉穎攘须,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體殴泰,經(jīng)...
    沈念sama閱讀 45,341評論 1 311
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡于宙,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,563評論 2 333
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了艰匙。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片限煞。...
    茶點(diǎn)故事閱讀 39,731評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖员凝,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情奋献,我是刑警寧澤健霹,帶...
    沈念sama閱讀 35,430評論 5 343
  • 正文 年R本政府宣布旺上,位于F島的核電站,受9級特大地震影響糖埋,放射性物質(zhì)發(fā)生泄漏宣吱。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,036評論 3 326
  • 文/蒙蒙 一瞳别、第九天 我趴在偏房一處隱蔽的房頂上張望征候。 院中可真熱鬧,春花似錦祟敛、人聲如沸疤坝。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,676評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽跑揉。三九已至,卻和暖如春埠巨,著一層夾襖步出監(jiān)牢的瞬間历谍,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,829評論 1 269
  • 我被黑心中介騙來泰國打工辣垒, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留望侈,地道東北人。 一個月前我還...
    沈念sama閱讀 47,743評論 2 368
  • 正文 我出身青樓勋桶,卻偏偏與公主長得像甜无,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子哥遮,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,629評論 2 354

推薦閱讀更多精彩內(nèi)容