32--aspectj-autoproxy解析及Spring解析自定義標簽

前面的章節(jié)已經(jīng)介紹了AOP的相關(guān)概念和一些知識點,接下來我們就要開始分析SpringAOP的源碼了,接下來的分析都基于@AspectJ注解。

1.aspectj-autoproxy標簽簡介

使用注解方式應(yīng)用aop需要在配置文件中配置<aop:aspectj-autoproxy/>, 而該標簽有兩個屬性,proxy-target-classexpose-proxy钝域。

  • proxy-target-class
    如果被代理的目標對象至少實現(xiàn)了一個接口,則會使用JDK動態(tài)代理锭魔,所有實現(xiàn)該目標類實現(xiàn)的接口都將被代理例证;如果該目標對象沒有實現(xiàn)任何接口,則創(chuàng)建CGLIB動態(tài)代理迷捧。但是可以通過proxy-target-class屬性強制指定使用CGLIB代理织咧。如果指定了proxy-target-class="true"則將強制開啟CGLIB動態(tài)代理。
  • expose-proxy
    解決目標對象內(nèi)部的自我調(diào)用無法實施切面增強的問題漠秋。
2.aspectj-autoproxy標簽解析

在第十七小節(jié)笙蒙,講解了Spring對默認標簽的解析過程,但是沒有對默認標簽解析進行分析庆锦,今天就來分析一下Spring是如何解析自定義標簽aspectj-autoproxy的捅位。順便通過對該標簽的解析,補充十七節(jié)欠缺的分析搂抒。

  • 自定義標簽解析入口
    打開DefaultBeanDefinitionDocumentReader類的parseBeanDefinitions方法
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
    // 1艇搀、解析默認命名空間
    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)) {
                    parseDefaultElement(ele, delegate);
                }
                // 自定義命名空間
                else {
                    delegate.parseCustomElement(ele);
                }
            }
        }
    }
    // 2、解析自定義命名空間
    else {
        delegate.parseCustomElement(root);
    }
}

通過parseCustomElement解析自定義標簽

  • 自定義標簽解析過程
/**
 * 解析自定義標簽
 * @param ele
 * @param containingBd
 * @return
 */
@Nullable
public BeanDefinition parseCustomElement(Element ele, @Nullable BeanDefinition containingBd) {
    // 1求晶、獲取namespaceUri
    String namespaceUri = getNamespaceURI(ele);
    if (namespaceUri == null) {
        return null;
    }
    // 2中符、根據(jù)namespaceUri得到命名空間解析器
    NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
    if (handler == null) {
        error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele);
        return null;
    }
    // 2、使用命名空間解析器解析自定義標簽
    return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
}

對自定義標簽的解析交給了NamespaceHandler接口誉帅,如果我們想在Spring中實現(xiàn)自己的自定義標簽,那么就需要實現(xiàn)NamespaceHandler接口,并通過重寫其中的方法蚜锨,來完成對自定義標簽的解析档插。

  • 獲取NamespaceHandler
public NamespaceHandler resolve(String namespaceUri) {
    // 1.獲取所有的namespaceUri,NamespaceHandler鍵值對map集合并得到
    // 當(dāng)前namespaceUri對應(yīng)的NamespaceHandler類
    Map<String, Object> handlerMappings = getHandlerMappings();
    Object handlerOrClassName = handlerMappings.get(namespaceUri);
    if (handlerOrClassName == null) {
        return null;
    }
    else if (handlerOrClassName instanceof NamespaceHandler) {
        return (NamespaceHandler) handlerOrClassName;
    }
    else {
        // 2亚再、通過BeanUtils實例化NamespaceHandler并調(diào)用其init方法進行初始化操作
        String className = (String) handlerOrClassName;
        try {
            Class<?> handlerClass = ClassUtils.forName(className, this.classLoader);
            if (!NamespaceHandler.class.isAssignableFrom(handlerClass)) {
                throw new FatalBeanException("Class [" + className + "] for namespace [" + namespaceUri +
                        "] does not implement the [" + NamespaceHandler.class.getName() + "] interface");
            }
            NamespaceHandler namespaceHandler = (NamespaceHandler) BeanUtils.instantiateClass(handlerClass);
            namespaceHandler.init();
            handlerMappings.put(namespaceUri, namespaceHandler);
            return namespaceHandler;
        }
        catch (ClassNotFoundException ex) {
            throw new FatalBeanException("Could not find NamespaceHandler class [" + className +
                    "] for namespace [" + namespaceUri + "]", ex);
        }
        catch (LinkageError err) {
            throw new FatalBeanException("Unresolvable class definition for NamespaceHandler class [" +
                    className + "] for namespace [" + namespaceUri + "]", err);
        }
    }
}

該方法非常簡單郭膛,首先根據(jù)namespaceUri從所有的Map<namespaceUri,NamespaceHandler>中獲取對應(yīng)的NamespaceHandler實現(xiàn)類,然后通過BeanUtils.instantiateClass實例化該類氛悬,最后調(diào)用NamespaceHandler實例的init方法则剃。

  • AopNamespaceHandler的init方法
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());
}

在該方法中我們已經(jīng)看到了aspectj-autoproxy標簽,該方法也很簡單如捅,不做過多的分析棍现。接下來分析aspectj-autoproxy解析的過程。

3.通過NamespaceHandler的parse方法解析aspectj-autoproxy標簽
public BeanDefinition parse(Element element, ParserContext parserContext) {
    BeanDefinitionParser parser = findParserForElement(element, parserContext);
    return (parser != null ? parser.parse(element, parserContext) : null);
}

具體的轉(zhuǎn)換工作委托給了BeanDefinitionParser實例:

public BeanDefinition parse(Element element, ParserContext parserContext) {
    // 1镜遣、注冊AnnotationAwareAspectJAutoProxyCreator
    AopNamespaceUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(parserContext, element);
    // 2己肮、處理子標簽<aop:include/>, 指定@Aspect類悲关,支持正則表達式谎僻,符合該表達式的切面類才會被應(yīng)用
    extendBeanDefinition(element, parserContext);
    return null;
}

該轉(zhuǎn)換過程主要完成兩個功能,注冊AnnotationAwareAspectJAutoProxyCreator和解析標簽<aop:aspectj-autoproxy/>寓辱,并在第二步解析了<aop:include/>子標簽艘绍,我們逐步分析一下:

3.1 注冊AnnotationAwareAspectJAutoProxyCreator
public static void registerAspectJAnnotationAutoProxyCreatorIfNecessary(ParserContext parserContext, Element sourceElement) {

    // 1、注冊AnnotationAwareAspectJAutoProxyCreator
    BeanDefinition beanDefinition = AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(
            parserContext.getRegistry(),
            parserContext.extractSource(sourceElement));
    // 2秫筏、解析子標簽 proxy-target-class 和 expose-proxy
    useClassProxyingIfNecessary(parserContext.getRegistry(), sourceElement);
    // 3诱鞠、注冊組件并發(fā)送組件注冊事件
    registerComponentIfNecessary(beanDefinition, parserContext);
}
  • 注冊AnnotationAwareAspectJAutoProxyCreator
private static BeanDefinition registerOrEscalateApcAsRequired(Class<?> cls, BeanDefinitionRegistry registry,
            @Nullable Object source) {
    // cls --> org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator
    // AUTO_PROXY_CREATOR_BEAN_NAME --> org.springframework.aop.config.internalAutoProxyCreator
    if (registry.containsBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME)) {
        // 如果registry已經(jīng)包含了internalAutoProxyCreator,
        BeanDefinition apcDefinition = registry.getBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME);
        // 如果已經(jīng)注冊的internalAutoProxyCreator不是AnnotationAwareAspectJAutoProxyCreator跳昼,則需要判斷優(yōu)先級并決定使用哪個
        if (!cls.getName().equals(apcDefinition.getBeanClassName())) {
            int currentPriority = findPriorityForClass(apcDefinition.getBeanClassName());
            int requiredPriority = findPriorityForClass(cls);
            if (currentPriority < requiredPriority) {
                apcDefinition.setBeanClassName(cls.getName());
            }
        }
        // 如果已經(jīng)注冊的internalAutoProxyCreator是AnnotationAwareAspectJAutoProxyCreator般甲,則無需特殊處理
        return null;
    }

    RootBeanDefinition beanDefinition = new RootBeanDefinition(cls);
    beanDefinition.setSource(source);
    // HIGHEST_PRECEDENCE --> 指定最高優(yōu)先級
    beanDefinition.getPropertyValues().add("order", Ordered.HIGHEST_PRECEDENCE);
    // ROLE_INFRASTRUCTURE --> 表示Spring的內(nèi)部bean
    beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
    // 注冊BeanDefinition
    registry.registerBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME, beanDefinition);
    return beanDefinition;
}
  • 解析子標簽 proxy-target-class 和 expose-proxy
private static void useClassProxyingIfNecessary(BeanDefinitionRegistry registry, @Nullable Element sourceElement) {
    if (sourceElement != null) {
        // PROXY_TARGET_CLASS_ATTRIBUTE --> proxy-target-class
        boolean proxyTargetClass = Boolean.parseBoolean(sourceElement.getAttribute(PROXY_TARGET_CLASS_ATTRIBUTE));
        if (proxyTargetClass) {
            AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
        }
        // EXPOSE_PROXY_ATTRIBUTE --> expose-proxy
        boolean exposeProxy = Boolean.parseBoolean(sourceElement.getAttribute(EXPOSE_PROXY_ATTRIBUTE));
        if (exposeProxy) {
            AopConfigUtils.forceAutoProxyCreatorToExposeProxy(registry);
        }
    }
}

該過程最核心的作用就是注冊AnnotationAwareAspectJAutoProxyCreator,關(guān)于該類的作用鹅颊,我們會在下面的章節(jié)分析敷存。

3.2 處理子標簽<aop:include/>

<aop:include/>標簽的作用:例如有下面的配置文件,并指定了<aop:include name="aspectJTest"></aop:include>堪伍,則aspectJTest切面類會被應(yīng)用锚烦,且name支持正則表達式,如果配置文件中的切面類不符合name的表達式帝雇,則切面類不會被應(yīng)用涮俄。

<?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">

    <!--
        1、proxy-target-class
            如果被代理的目標對象至少實現(xiàn)了一個接口尸闸,則會使用JDK動態(tài)代理彻亲,所有實現(xiàn)該目標類實現(xiàn)的接口都將被代理
            如果該目標對象沒有實現(xiàn)任何接口孕锄,則創(chuàng)建CGLIB動態(tài)代理。
            但是可以通過proxy-target-class屬性強制指定使用CGLIB代理苞尝,
        2畸肆、expose-proxy
            解決目標對象內(nèi)部的自我調(diào)用無法實施切面增強的問題
    -->
    <aop:aspectj-autoproxy proxy-target-class="true" expose-proxy="true">
        <!-- 指定@Aspect類,支持正則表達式宙址,符合該表達式的切面類才會被應(yīng)用-->
        <aop:include name="aspectJTest"></aop:include>
    </aop:aspectj-autoproxy>

    <bean id="aopTestBean" class="com.lyc.cn.v2.day04.AopTestBean"></bean>

    <bean name="aspectJTest" class="com.lyc.cn.v2.day04.AspectJTest"></bean>
</beans>

了解了標簽的作用之后轴脐,我們來看其解析過程,比較簡單就不附加注釋了:

private void extendBeanDefinition(Element element, ParserContext parserContext) {
    BeanDefinition beanDef = parserContext.getRegistry().getBeanDefinition(AopConfigUtils.AUTO_PROXY_CREATOR_BEAN_NAME);
    if (element.hasChildNodes()) {
        addIncludePatterns(element, parserContext, beanDef);
    }
}

private void addIncludePatterns(Element element, ParserContext parserContext, BeanDefinition beanDef) {
    ManagedList<TypedStringValue> includePatterns = new ManagedList<>();
    NodeList childNodes = element.getChildNodes();
    for (int i = 0; i < childNodes.getLength(); i++) {
        Node node = childNodes.item(i);
        if (node instanceof Element) {
            Element includeElement = (Element) node;
            TypedStringValue valueHolder = new TypedStringValue(includeElement.getAttribute("name"));
            valueHolder.setSource(parserContext.extractSource(includeElement));
            includePatterns.add(valueHolder);
        }
    }
    if (!includePatterns.isEmpty()) {
        includePatterns.setSource(parserContext.extractSource(element));
        beanDef.getPropertyValues().add("includePatterns", includePatterns);
    }
}

那么到這里<aop:aspectj-autoproxy/>標簽的解析工作也就完成了抡砂。

4.總結(jié)

本節(jié)主要分析了<aop:aspectj-autoproxy/>標簽的解析大咱,而該標簽也是Spring的自定義標簽,所以注益,如果大家有自己的自定義標簽時碴巾,可以參考借鑒Spring的源碼,寫出自己的標簽解析過程聊浅。通過繼承NamespaceHandler實現(xiàn)自己的命名空間解析器即可餐抢。

在解析<aop:aspectj-autoproxy/>的過程中,除了對標簽的解析工作之外低匙,還注冊了AnnotationAwareAspectJAutoProxyCreator旷痕,該類的作用是什么呢,我們留在下節(jié)分析顽冶。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末欺抗,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子强重,更是在濱河造成了極大的恐慌绞呈,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,482評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件间景,死亡現(xiàn)場離奇詭異佃声,居然都是意外死亡,警方通過查閱死者的電腦和手機倘要,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,377評論 2 382
  • 文/潘曉璐 我一進店門圾亏,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人封拧,你說我怎么就攤上這事志鹃。” “怎么了泽西?”我有些...
    開封第一講書人閱讀 152,762評論 0 342
  • 文/不壞的土叔 我叫張陵曹铃,是天一觀的道長。 經(jīng)常有香客問我捧杉,道長陕见,這世上最難降的妖魔是什么秘血? 我笑而不...
    開封第一講書人閱讀 55,273評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮淳玩,結(jié)果婚禮上直撤,老公的妹妹穿的比我還像新娘。我一直安慰自己蜕着,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 64,289評論 5 373
  • 文/花漫 我一把揭開白布红柱。 她就那樣靜靜地躺著承匣,像睡著了一般。 火紅的嫁衣襯著肌膚如雪锤悄。 梳的紋絲不亂的頭發(fā)上韧骗,一...
    開封第一講書人閱讀 49,046評論 1 285
  • 那天,我揣著相機與錄音零聚,去河邊找鬼袍暴。 笑死,一個胖子當(dāng)著我的面吹牛隶症,可吹牛的內(nèi)容都是我干的政模。 我是一名探鬼主播,決...
    沈念sama閱讀 38,351評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼蚂会,長吁一口氣:“原來是場噩夢啊……” “哼淋样!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起胁住,我...
    開封第一講書人閱讀 36,988評論 0 259
  • 序言:老撾萬榮一對情侶失蹤趁猴,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后彪见,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體儡司,經(jīng)...
    沈念sama閱讀 43,476評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,948評論 2 324
  • 正文 我和宋清朗相戀三年余指,在試婚紗的時候發(fā)現(xiàn)自己被綠了捕犬。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,064評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡浪规,死狀恐怖或听,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情笋婿,我是刑警寧澤誉裆,帶...
    沈念sama閱讀 33,712評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站缸濒,受9級特大地震影響足丢,放射性物質(zhì)發(fā)生泄漏粱腻。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,261評論 3 307
  • 文/蒙蒙 一斩跌、第九天 我趴在偏房一處隱蔽的房頂上張望绍些。 院中可真熱鬧,春花似錦耀鸦、人聲如沸柬批。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,264評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽氮帐。三九已至,卻和暖如春洛姑,著一層夾襖步出監(jiān)牢的瞬間上沐,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,486評論 1 262
  • 我被黑心中介騙來泰國打工楞艾, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留参咙,地道東北人。 一個月前我還...
    沈念sama閱讀 45,511評論 2 354
  • 正文 我出身青樓硫眯,卻偏偏與公主長得像蕴侧,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子舟铜,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,802評論 2 345

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

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理戈盈,服務(wù)發(fā)現(xiàn),斷路器谆刨,智...
    卡卡羅2017閱讀 134,599評論 18 139
  • Spring Web MVC Spring Web MVC 是包含在 Spring 框架中的 Web 框架塘娶,建立于...
    Hsinwong閱讀 22,313評論 1 92
  • 本文是我自己在秋招復(fù)習(xí)時的讀書筆記,整理的知識點痊夭,也是為了防止忘記刁岸,尊重勞動成果,轉(zhuǎn)載注明出處哦她我!如果你也喜歡虹曙,那...
    波波波先森閱讀 12,276評論 6 86
  • 用兩張圖告訴你,為什么你的 App 會卡頓? - Android - 掘金 Cover 有什么料番舆? 從這篇文章中你...
    hw1212閱讀 12,693評論 2 59
  • 俗話說得好:勤梳頭酝碳,勤洗臉,有點兒倒霉也不顯恨狈。 光潔的臉龐不僅透露出積極向上的精神面貌疏哗,也是外在美最直接的表現(xiàn)形式...
    枕邊音樂哦閱讀 764評論 0 13