spring利用spring.handlers解析自定義配置

一赃份、問(wèn)題

我們?cè)趕pring的xml配置文件里經(jīng)常定義各種各樣的配置(tx饿这、bean抛丽、mvc、bean等等)晌缘。以及集成第三方框架時(shí),也會(huì)看到一些spring之外的配置,例如dubbo的配置齐莲、security的配置、redis的配置等等磷箕。spring作為一個(gè)龐大的容器,到底是怎么工作的呢选酗?這篇文章將簡(jiǎn)單的介紹一下其實(shí)現(xiàn)機(jī)制,具體細(xì)節(jié)朋友們可以自己查看相關(guān)代碼。

二岳枷、代碼分析

稍微了解一點(diǎn)spring的朋友都知道spring的入口方法就是refresh(),所以我們直接進(jìn)入refresh()方法【不清楚的可以查看一下ClassPathXmlApplicationContext,一步一步跟蹤進(jìn)去】芒填。

public void refresh() throws BeansException, IllegalStateException {
        synchronized (this.startupShutdownMonitor) {
            // Prepare this context for refreshing.
            prepareRefresh();

            // Tell the subclass to refresh the internal bean factory.
            ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

            // Prepare the bean factory for use in this context.
            prepareBeanFactory(beanFactory);

            try {
                // Allows post-processing of the bean factory in context subclasses.
                postProcessBeanFactory(beanFactory);

                // Invoke factory processors registered as beans in the context.
                invokeBeanFactoryPostProcessors(beanFactory);

                // Register bean processors that intercept bean creation.
                registerBeanPostProcessors(beanFactory);

                // Initialize message source for this context.
                initMessageSource();

                // Initialize event multicaster for this context.
                initApplicationEventMulticaster();

                // Initialize other special beans in specific context subclasses.
                onRefresh();

                // Check for listener beans and register them.
                registerListeners();

                // Instantiate all remaining (non-lazy-init) singletons.
                finishBeanFactoryInitialization(beanFactory);

                // Last step: publish corresponding event.
                finishRefresh();
            }
...由于篇幅,我省略了一部分

而解析spring配置的入口就在obtainFreshBeanFactory().refreshBeanFactory()中:

protected final void refreshBeanFactory() throws BeansException {
        if (hasBeanFactory()) {
            destroyBeans();
            closeBeanFactory();
        }
        try {
            DefaultListableBeanFactory beanFactory = createBeanFactory();
            beanFactory.setSerializationId(getId());
            customizeBeanFactory(beanFactory);
            loadBeanDefinitions(beanFactory);//就是這里
            synchronized (this.beanFactoryMonitor) {
                this.beanFactory = beanFactory;
            }
        }
        catch (IOException ex) {
            throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
        }
    }

我們首先會(huì)告訴spring的配置文件所在路徑,即setLocations(),spring創(chuàng)建完BeanFactory,接下來(lái)要做的就是解析配置,完成其他各種屬性的封裝(BeanDefinition空繁、各種代理殿衰、映射關(guān)系等等)。這里的loadBeanDefinitions就是解析配置的核心方法盛泡。

protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
        // Create a new XmlBeanDefinitionReader for the given BeanFactory.
        XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);

        // Configure the bean definition reader with this context's
        // resource loading environment.
        beanDefinitionReader.setEnvironment(this.getEnvironment());
        beanDefinitionReader.setResourceLoader(this);
        beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));

        // Allow a subclass to provide custom initialization of the reader,
        // then proceed with actually loading the bean definitions.
        initBeanDefinitionReader(beanDefinitionReader);
        loadBeanDefinitions(beanDefinitionReader);
    }

DefaultListableBeanFactory 作為BeanFactory的一個(gè)核心實(shí)例,且實(shí)現(xiàn)了BeanDefinitionRegistry接口,在解析之前當(dāng)然是要給配置文件解析器(XmlBeanDefinitionReader)設(shè)置一個(gè)存放BeanDefinition的容器(即將注冊(cè)bean的功能委托給BeanFactory)闷祥。然后配置文件解析器就開(kāi)始它的解析工作了。

我們直接定位到XmlBeanDefinitionReader的doLoadBeanDefinitions方法:

try {
            Document doc = doLoadDocument(inputSource, resource);
            return registerBeanDefinitions(doc, resource);
        }
        catch (BeanDefinitionStoreException ex) {
            throw ex;
        }

利用w3c的DOM將配置文件包裝成了一個(gè)Document對(duì)象,接下來(lái)就是利用Dom來(lái)解析childNode相關(guān)屬性傲诵。

接下來(lái),我們沿著代碼定位到DefaultBeaDefinitionDocumentReader中的的parseDefinitions方法:

protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
        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);
                    }
                }
            }
        }
        else {
            delegate.parseCustomElement(root);
        }
    }

核心部分來(lái)了,spring迭代根節(jié)點(diǎn)<beans/>,一個(gè)一個(gè)的解析其子節(jié)點(diǎn).如果是<beans/>節(jié)點(diǎn),則走parseDefaultElement方法,我們這里關(guān)注的是parseCustomElement():

public BeanDefinition parseCustomElement(Element ele, BeanDefinition containingBd) {
        String namespaceUri = getNamespaceURI(ele);
        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));
    }

根據(jù)元素的namespaceUri(也就是xml文件頭那一串schema)找到對(duì)應(yīng)的NamespaceHandler.【這里就是擴(kuò)展自定義配置的入口】

我們接下來(lái)看一下resolve()方法:

public NamespaceHandler resolve(String namespaceUri) {
        Map<String, Object> handlerMappings = getHandlerMappings();
        Object handlerOrClassName = handlerMappings.get(namespaceUri);
        if (handlerOrClassName == null) {
            return null;
        }
        else if (handlerOrClassName instanceof NamespaceHandler) {
            return (NamespaceHandler) handlerOrClassName;
        }
        else {
            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("NamespaceHandler class [" + className + "] for namespace [" +
                        namespaceUri + "] not found", ex);
            }
            catch (LinkageError err) {
                throw new FatalBeanException("Invalid NamespaceHandler class [" + className + "] for namespace [" +
                        namespaceUri + "]: problem with handler class file or dependent class", err);
            }
        }
    }

對(duì)應(yīng)的NamespaceHandler就是根據(jù)handlerMappings找到匹配關(guān)系的,所以我們只需要看DefaultNamespaceHandlerResolver.getHandlerMappings()即可凯砍。

private Map<String, Object> getHandlerMappings() {
        if (this.handlerMappings == null) {
            synchronized (this) {
                if (this.handlerMappings == null) {
                    try {
                        Properties mappings =
                                PropertiesLoaderUtils.loadAllProperties(this.handlerMappingsLocation, this.classLoader);//handlerMappingsLocation默認(rèn)的路徑就是META-INF/spring.handlers
                        if (logger.isDebugEnabled()) {
                            logger.debug("Loaded NamespaceHandler mappings: " + mappings);
                        }
                        Map<String, Object> handlerMappings = new ConcurrentHashMap<String, Object>(mappings.size());
                        CollectionUtils.mergePropertiesIntoMap(mappings, handlerMappings);
                        this.handlerMappings = handlerMappings;
                    }
                    catch (IOException ex) {
                        throw new IllegalStateException(
                                "Unable to load NamespaceHandler mappings from location [" + this.handlerMappingsLocation + "]", ex);
                    }
                }
            }
        }
        return this.handlerMappings;
    }

spring將classpath下的META-INF/spring.handlers中的配置加載到Map中.這個(gè)spring.handler文件就是以NamespaceUrl作為key,對(duì)應(yīng)的Handler作為value的鍵值對(duì),例如:
···
http://www.springframework.org/schema/context=org.springframework.context.config.ContextNamespaceHandler
http://www.springframework.org/schema/jee=org.springframework.ejb.config.JeeNamespaceHandler
http://www.springframework.org/schema/lang=org.springframework.scripting.config.LangNamespaceHandler
http://www.springframework.org/schema/task=org.springframework.scheduling.config.TaskNamespaceHandler
http://www.springframework.org/schema/cache=org.springframework.cache.config.CacheNamespaceHandler

···
當(dāng)我們解析到<context/>配置的時(shí)候,我們會(huì)根據(jù)context的schema,也就是[http://www.springframework.org/schema/context]作為key找到對(duì)應(yīng)的ContextNamespacehandler進(jìn)行解析的箱硕,一切都清楚了。

三悟衩、總結(jié)

spring這點(diǎn)做得很強(qiáng)大,利用這種類(lèi)似于Serviceloader的功能來(lái)平等對(duì)待第三方剧罩,方便別人擴(kuò)展集成。我們也要停下來(lái)想一想,當(dāng)我們自己開(kāi)發(fā)這種核心組件的時(shí)候一定要做到"平等對(duì)待第三方"以及核心足夠小,依賴(lài)少座泳。這也是一種修行惠昔!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市钳榨,隨后出現(xiàn)的幾起案子舰罚,更是在濱河造成了極大的恐慌纽门,老刑警劉巖薛耻,帶你破解...
    沈念sama閱讀 218,640評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異赏陵,居然都是意外死亡饼齿,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,254評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)蝙搔,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)缕溉,“玉大人,你說(shuō)我怎么就攤上這事吃型≈づ福” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,011評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵勤晚,是天一觀的道長(zhǎng)枉层。 經(jīng)常有香客問(wèn)我,道長(zhǎng)赐写,這世上最難降的妖魔是什么鸟蜡? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,755評(píng)論 1 294
  • 正文 為了忘掉前任,我火速辦了婚禮挺邀,結(jié)果婚禮上揉忘,老公的妹妹穿的比我還像新娘。我一直安慰自己端铛,他們只是感情好泣矛,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,774評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著禾蚕,像睡著了一般您朽。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上夕膀,一...
    開(kāi)封第一講書(shū)人閱讀 51,610評(píng)論 1 305
  • 那天虚倒,我揣著相機(jī)與錄音美侦,去河邊找鬼。 笑死魂奥,一個(gè)胖子當(dāng)著我的面吹牛菠剩,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播耻煤,決...
    沈念sama閱讀 40,352評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼具壮,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了哈蝇?” 一聲冷哼從身側(cè)響起棺妓,我...
    開(kāi)封第一講書(shū)人閱讀 39,257評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎炮赦,沒(méi)想到半個(gè)月后怜跑,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,717評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡吠勘,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,894評(píng)論 3 336
  • 正文 我和宋清朗相戀三年性芬,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片剧防。...
    茶點(diǎn)故事閱讀 40,021評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡植锉,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出峭拘,到底是詐尸還是另有隱情俊庇,我是刑警寧澤,帶...
    沈念sama閱讀 35,735評(píng)論 5 346
  • 正文 年R本政府宣布鸡挠,位于F島的核電站辉饱,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏宵凌。R本人自食惡果不足惜鞋囊,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,354評(píng)論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望瞎惫。 院中可真熱鬧溜腐,春花似錦、人聲如沸瓜喇。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,936評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)乘寒。三九已至望众,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背烂翰。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,054評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工夯缺, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人甘耿。 一個(gè)月前我還...
    沈念sama閱讀 48,224評(píng)論 3 371
  • 正文 我出身青樓踊兜,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親佳恬。 傳聞我的和親對(duì)象是個(gè)殘疾皇子捏境,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,974評(píng)論 2 355

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