spring bean加載過程源碼分析

不管是spring Test還是Spring mvc最終都會(huì)調(diào)用到refresh()方法匙隔,下面我們重點(diǎn)看下refresh做了些什么

Spring test是在AbstractGenericContextLoader這個(gè)類中調(diào)用refresh()方法绪颖,并且會(huì)在refresh()方法之前裝載bean信息loadBeanDefinitions(context, mergedConfig);

@Override
    public final ConfigurableApplicationContext loadContext(MergedContextConfiguration mergedConfig) throws Exception {
        if (logger.isDebugEnabled()) {
            logger.debug(String.format("Loading ApplicationContext for merged context configuration [%s].",
                mergedConfig));
        }

        validateMergedContextConfiguration(mergedConfig);

        GenericApplicationContext context = new GenericApplicationContext();

        ApplicationContext parent = mergedConfig.getParentApplicationContext();
        if (parent != null) {
            context.setParent(parent);
        }
        prepareContext(context);
        prepareContext(context, mergedConfig);
        customizeBeanFactory(context.getDefaultListableBeanFactory());
        loadBeanDefinitions(context, mergedConfig);
        AnnotationConfigUtils.registerAnnotationConfigProcessors(context);
        customizeContext(context);
        context.refresh();
        context.registerShutdownHook();
        return context;
    }

Spring mvc在servelet裝載是會(huì)調(diào)用init()方法,自然會(huì)調(diào)DispatcherServlet的init方法欢策,最終會(huì)調(diào)用父類的org.springframework.web.servlet.HttpServletBean的init()方法

@Override
    public final void init() throws ServletException {
        if (logger.isDebugEnabled()) {
            logger.debug("Initializing servlet '" + getServletName() + "'");
        }

        // Set bean properties from init parameters.
        try {
            PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
            BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
            ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
            bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
            initBeanWrapper(bw);
            bw.setPropertyValues(pvs, true);
        }
        catch (BeansException ex) {
            logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
            throw ex;
        }

        // Let subclasses do whatever initialization they like.
        initServletBean();

        if (logger.isDebugEnabled()) {
            logger.debug("Servlet '" + getServletName() + "' configured successfully");
        }
    }

真正加載bean的方法是initServletBean()跟進(jìn)去會(huì)發(fā)現(xiàn)這個(gè)方法是在類FrameworkServlet中實(shí)現(xiàn)

@Override
    protected final void initServletBean() throws ServletException {
        getServletContext().log("Initializing Spring FrameworkServlet '" + getServletName() + "'");
        if (this.logger.isInfoEnabled()) {
            this.logger.info("FrameworkServlet '" + getServletName() + "': initialization started");
        }
        long startTime = System.currentTimeMillis();

        try {
            this.webApplicationContext = initWebApplicationContext();
            initFrameworkServlet();
        }
        catch (ServletException ex) {
            this.logger.error("Context initialization failed", ex);
            throw ex;
        }
        catch (RuntimeException ex) {
            this.logger.error("Context initialization failed", ex);
            throw ex;
        }

        if (this.logger.isInfoEnabled()) {
            long elapsedTime = System.currentTimeMillis() - startTime;
            this.logger.info("FrameworkServlet '" + getServletName() + "': initialization completed in " +
                    elapsedTime + " ms");
        }
    }

內(nèi)部是通過調(diào)用initWebApplicationContext()來初始化一個(gè)WebApplicationContext

protected WebApplicationContext initWebApplicationContext() {
        WebApplicationContext wac = findWebApplicationContext();
        if (wac == null) {
            // No fixed context defined for this servlet - create a local one.
            WebApplicationContext parent =
                    WebApplicationContextUtils.getWebApplicationContext(getServletContext());
            wac = createWebApplicationContext(parent);
        }

        if (!this.refreshEventReceived) {
            // Apparently not a ConfigurableApplicationContext with refresh support:
            // triggering initial onRefresh manually here.
            onRefresh(wac);
        }

        if (this.publishContext) {
            // Publish the context as a servlet context attribute.
            String attrName = getServletContextAttributeName();
            getServletContext().setAttribute(attrName, wac);
            if (this.logger.isDebugEnabled()) {
                this.logger.debug("Published WebApplicationContext of servlet '" + getServletName() +
                        "' as ServletContext attribute with name [" + attrName + "]");
            }
        }

        return wac;
    }

因?yàn)閯傞_始wac為空,主要看createWebApplicationContext這個(gè)方法

protected WebApplicationContext createWebApplicationContext(ApplicationContext parent) {
        Class<?> contextClass = getContextClass();
        if (this.logger.isDebugEnabled()) {
            this.logger.debug("Servlet with name '" + getServletName() +
                    "' will try to create custom WebApplicationContext context of class '" +
                    contextClass.getName() + "'" + ", using parent context [" + parent + "]");
        }
        if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
            throw new ApplicationContextException(
                    "Fatal initialization error in servlet with name '" + getServletName() +
                    "': custom WebApplicationContext class [" + contextClass.getName() +
                    "] is not of type ConfigurableWebApplicationContext");
        }
        ConfigurableWebApplicationContext wac =
                (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);

        // Assign the best possible id value.
        ServletContext sc = getServletContext();
        if (sc.getMajorVersion() == 2 && sc.getMinorVersion() < 5) {
            // Servlet <= 2.4: resort to name specified in web.xml, if any.
            String servletContextName = sc.getServletContextName();
            if (servletContextName != null) {
                wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX + servletContextName +
                        "." + getServletName());
            }
            else {
                wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX + getServletName());
            }
        }
        else {
            // Servlet 2.5's getContextPath available!
            wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX + sc.getContextPath() +
                    "/" + getServletName());
        }

        wac.setParent(parent);
        wac.setServletContext(getServletContext());
        wac.setServletConfig(getServletConfig());
        wac.setNamespace(getNamespace());
        wac.setConfigLocation(getContextConfigLocation());
        wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));

        postProcessWebApplicationContext(wac);
        wac.refresh();

        return wac;
    }

這個(gè)方法前面會(huì)設(shè)置一些相關(guān)參數(shù)赏淌,例如踩寇,設(shè)置web容器,容器配置信息六水,然后會(huì)調(diào)用一個(gè)refresh()方法俺孙。refresh()方法如下:

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();
            }

            catch (BeansException ex) {
                // Destroy already created singletons to avoid dangling resources.
                destroyBeans();

                // Reset 'active' flag.
                cancelRefresh(ex);

                // Propagate exception to caller.
                throw ex;
            }
        }
    }

obtainFreshBeanFactory()方法的描述:

protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
        refreshBeanFactory();
        ConfigurableListableBeanFactory beanFactory = getBeanFactory();
        if (logger.isDebugEnabled()) {
            logger.debug("Bean factory for " + getDisplayName() + ": " + beanFactory);
        }
        return beanFactory;
    }

這里refreshBeanFactory()方法有兩個(gè)實(shí)現(xiàn)GenericApplicationContext中的實(shí)現(xiàn)如下:

@Override
    protected final void refreshBeanFactory() throws IllegalStateException {
        if (this.refreshed) {
            throw new IllegalStateException(
                    "GenericApplicationContext does not support multiple refresh attempts: just call 'refresh' once");
        }
        this.beanFactory.setSerializationId(getId());
        this.refreshed = true;
    }

因?yàn)镚enericApplicationContext在初始化的時(shí)候已經(jīng)創(chuàng)建好了beanFactory了,所以這refreshBeanFactory()方法沒有實(shí)質(zhì)性的操作缩擂。另一個(gè)實(shí)現(xiàn)是在AbstractRefreshableApplicationContext這個(gè)類鼠冕,它是AbstractApplicationContext的子類,不論XmlWebApplicationContext胯盯、還是ClassPathXmlApplicationContext都繼承了它懈费,因此都能調(diào)用到這個(gè)一樣的初始化方法,來看看body部分的代碼

@Override
    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);
        }
    }

bean的加載是在loadBeanDefinitions(beanFactory)方法中博脑,它由AbstractXmlApplicationContext類中的方法實(shí)現(xiàn)憎乙,web項(xiàng)目中將會(huì)由類:XmlWebApplicationContext來實(shí)現(xiàn)票罐,其實(shí)差不多,主要是看啟動(dòng)文件是在那里而已泞边,如果在非web類項(xiàng)目中沒有自定義的XmlApplicationContext该押,那么其實(shí)功能可以參考XmlWebApplicationContext,可以認(rèn)為是一樣的功能阵谚。那么看看loadBeanDefinitions方法如下:

@Override
    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.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);
    }
    
    protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {
        Resource[] configResources = getConfigResources();
        if (configResources != null) {
            reader.loadBeanDefinitions(configResources);
        }
        String[] configLocations = getConfigLocations();
        if (configLocations != null) {
            reader.loadBeanDefinitions(configLocations);
        }
    }

XmlBeanDefineitionReader蚕礼,是讀取XML中spring的相關(guān)信息(也就是解析SpringContext.xml的),這里通過getConfigLocations()獲取到的就是這個(gè)或多個(gè)文件的路徑梢什,會(huì)循環(huán)奠蹬,

通過XmlBeanDefineitionReader來解析,跟蹤到loadBeanDefinitions方法里面嗡午,會(huì)發(fā)現(xiàn)方法實(shí)現(xiàn)體在XmlBeanDefineitionReader的父類:AbstractBeanDefinitionReader中囤躁,代碼如下:

public int loadBeanDefinitions(String location, Set<Resource> actualResources) throws BeanDefinitionStoreException {
        ResourceLoader resourceLoader = getResourceLoader();
        if (resourceLoader == null) {
            throw new BeanDefinitionStoreException(
                    "Cannot import bean definitions from location [" + location + "]: no ResourceLoader available");
        }

        if (resourceLoader instanceof ResourcePatternResolver) {
            // Resource pattern matching available.
            try {
                Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);
                int loadCount = loadBeanDefinitions(resources);
                if (actualResources != null) {
                    for (Resource resource : resources) {
                        actualResources.add(resource);
                    }
                }
                if (logger.isDebugEnabled()) {
                    logger.debug("Loaded " + loadCount + " bean definitions from location pattern [" + location + "]");
                }
                return loadCount;
            }
            catch (IOException ex) {
                throw new BeanDefinitionStoreException(
                        "Could not resolve bean definition resource pattern [" + location + "]", ex);
            }
        }
        else {
            // Can only load single resources by absolute URL.
            Resource resource = resourceLoader.getResource(location);
            int loadCount = loadBeanDefinitions(resource);
            if (actualResources != null) {
                actualResources.add(resource);
            }
            if (logger.isDebugEnabled()) {
                logger.debug("Loaded " + loadCount + " bean definitions from location [" + location + "]");
            }
            return loadCount;
        }
    }

到目前為止只解析到springContext.xml在哪里,但是還沒解析到springContext.xml的內(nèi)容荔睹,可能有多個(gè)spring配置文件狸演,這里會(huì)出現(xiàn)多個(gè)Resource。接下來有很多層調(diào)用僻他,會(huì)以此調(diào)用AbstractBeanDefinitionReader.loadBeanDefinitions(Resource... resources)調(diào)用方法:XmlBeanDefinitionReader.loadBeanDefinitions(Resource resource)開始解析XML

public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
        Assert.notNull(encodedResource, "EncodedResource must not be null");
        if (logger.isInfoEnabled()) {
            logger.info("Loading XML bean definitions from " + encodedResource.getResource());
        }

        Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();
        if (currentResources == null) {
            currentResources = new HashSet<EncodedResource>(4);
            this.resourcesCurrentlyBeingLoaded.set(currentResources);
        }
        if (!currentResources.add(encodedResource)) {
            throw new BeanDefinitionStoreException(
                    "Detected cyclic loading of " + encodedResource + " - check your import definitions!");
        }
        try {
          //從中獲取已經(jīng)封裝的Resource對(duì)象并再次從Resource中獲取其中的InputStream
            InputStream inputStream = encodedResource.getResource().getInputStream();
            try {
                InputSource inputSource = new InputSource(inputStream);
                if (encodedResource.getEncoding() != null) {
                    inputSource.setEncoding(encodedResource.getEncoding());
                }
                return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
            }
            finally {
                inputStream.close();
            }
        }
        catch (IOException ex) {
            throw new BeanDefinitionStoreException(
                    "IOException parsing XML document from " + encodedResource.getResource(), ex);
        }
        finally {
            currentResources.remove(encodedResource);
            if (currentResources.isEmpty()) {
                this.resourcesCurrentlyBeingLoaded.remove();
            }
        }
    }

數(shù)據(jù)準(zhǔn)備階段的邏輯:首先對(duì)傳入的resource參數(shù)做封裝宵距,目的是考慮到Resource可能存在編碼要求的情況,其次中姜,通過SAX讀取XML文件的方式來準(zhǔn)備InputSource對(duì)象消玄,最后將準(zhǔn)備的數(shù)據(jù)通過參數(shù)傳入真正的核心處理部分doLoadBeanDefinitions(inputSource,encodedResource.getResource())

protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
            throws BeanDefinitionStoreException {
        try {
            Document doc = doLoadDocument(inputSource, resource);
            return registerBeanDefinitions(doc, resource);
        }
        catch (BeanDefinitionStoreException ex) {
            throw ex;
        }
        catch (SAXParseException ex) {
            throw new XmlBeanDefinitionStoreException(resource.getDescription(),
                    "Line " + ex.getLineNumber() + " in XML document from " + resource + " is invalid", ex);
        }
        catch (SAXException ex) {
            throw new XmlBeanDefinitionStoreException(resource.getDescription(),
                    "XML document from " + resource + " is invalid", ex);
        }
        catch (ParserConfigurationException ex) {
            throw new BeanDefinitionStoreException(resource.getDescription(),
                    "Parser configuration exception parsing XML from " + resource, ex);
        }
        catch (IOException ex) {
            throw new BeanDefinitionStoreException(resource.getDescription(),
                    "IOException parsing XML document from " + resource, ex);
        }
        catch (Throwable ex) {
            throw new BeanDefinitionStoreException(resource.getDescription(),
                    "Unexpected exception parsing XML document from " + resource, ex);
        }
    }

上面的代碼主要是根據(jù)xml文件的驗(yàn)證模式,加載xml丢胚,并得到document翩瓜,最后根據(jù)返回的document注冊(cè)bean信息。

protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception {
       return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler,
     getValidationModeForResource(resource), isNamespaceAware());
    }

這里ValidationModeForResource(resource)方法是獲取xml文件的驗(yàn)證模式携龟,比較常用的xml文件的驗(yàn)證模式有兩種:DTD和XSD的區(qū)別:

DTD(Document Type Definition)即文檔類型定義兔跌,是一種XML約束模式語言,是XML文件的驗(yàn)證機(jī)制峡蟋,屬于XML文件組成的一部分坟桅。DTD是一種保證XML文檔格式正確的有效方法,可以通過比較XML文檔和DTD文件來看文檔是否符合規(guī)范蕊蝗,元素和標(biāo)簽使用是否正確仅乓。一個(gè)DTD文檔包含:元素的定義規(guī)則,元素間關(guān)系的定義規(guī)則蓬戚,元素可使用的屬性夸楣,可使用的實(shí)體或符合規(guī)則

使用DTD驗(yàn)證模式的時(shí)候需要在XML文件的頭部聲明,以下是在Spring中使用DTD聲明方式的代碼:


\<?xml version="1.0" encoding="UTF-8"?\>

\<!DOCTYPE beans PUBLIC "-//Spring//DTD BEAN 2.0//EN" "http://www.Springframework.org/dtd/Spring-beans-2.0.dtd"\>

...

XML Schema語言就是XSD(XML Schemas Definition)。XML Schema描述了XML文檔的結(jié)構(gòu)豫喧,可以用一個(gè)指定的XML Schema來驗(yàn)證某個(gè)XML文檔石洗,以檢查該XML文檔是否符合其要求,文檔設(shè)計(jì)者可以通過XML Schema指定一個(gè)XML文檔所允許的結(jié)構(gòu)和內(nèi)容紧显,并可據(jù)此檢查一個(gè)XML文檔是否是有效的

在使用XML Schema文檔對(duì)XML實(shí)例文檔進(jìn)行檢驗(yàn)讲衫,除了要聲明名稱空間外(xmlns=http://www.Springframework.org/schema/beans),還必須指定該名稱空間所對(duì)應(yīng)的XML Schema文檔的存儲(chǔ)位置孵班,通過schemaLocation屬性來指定名稱空間所對(duì)應(yīng)的XML Schema文檔的存儲(chǔ)位置涉兽,它包含兩個(gè)部分,一部分是名稱空間的URI篙程,另一部分就該名稱空間所標(biāo)識(shí)的XML Schema文件位置或URL地址

\<?xml version="1.0" encoding="UTF-8"?\>

\<beans xmlns="http://www.Springframework.org/schema/beans"

 xmlns:xsi="http://www.w3.org/2001/XMLSchema/beans"

 xsi:schemaLocation="http://www.Springframework.org/schema/beans http://www.Springframework.org/schema/beans/Spring-beans.xsd"\>

....

\</beans\>

驗(yàn)證模式的讀然ㄍ帧:

protected int getValidationModeForResource(Resource resource) {
        int validationModeToUse = getValidationMode();
        //如果手動(dòng)指定了驗(yàn)證模式則使用指定的驗(yàn)證模式
        if (validationModeToUse != VALIDATION_AUTO) {
            return validationModeToUse;
        }
        //使用自動(dòng)檢驗(yàn)?zāi)J?        int detectedMode = detectValidationMode(resource);
        if (detectedMode != VALIDATION_AUTO) {
            return detectedMode;
        }
        // Hmm, we didn't get a clear indication... Let's assume XSD,
        // since apparently no DTD declaration has been found up until
        // detection stopped (before finding the document's root tag).
        return VALIDATION_XSD;
    }

自動(dòng)檢測(cè)驗(yàn)證模式的功能是在函數(shù)detectValidationMode方法中實(shí)現(xiàn)的,在detectValidationMode函數(shù)中又將自動(dòng)檢測(cè)驗(yàn)證模式的工作委托給了專門處理類XmlValidationModeDetector的detectValidationMode方法

public int detectValidationMode(InputStream inputStream) throws IOException {
        // Peek into the file to look for DOCTYPE.
        BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
        try {
            boolean isDtdValidated = false;
            String content;
            while ((content = reader.readLine()) != null) {
                content = consumeCommentTokens(content);
                if (this.inComment || !StringUtils.hasText(content)) {
                    continue;
                }
                if (hasDoctype(content)) {
                    isDtdValidated = true;
                    break;
                }
                if (hasOpeningTag(content)) {
                    // End of meaningful data...
                    break;
                }
            }
            return (isDtdValidated ? VALIDATION_DTD : VALIDATION_XSD);
        }
        catch (CharConversionException ex) {
            // Choked on some character encoding...
            // Leave the decision up to the caller.
            return VALIDATION_AUTO;
        }
        finally {
            reader.close();
        }
    }

Spring通過判斷是否包含DOCTYPE,判斷采用哪種驗(yàn)證方式房午,包含就是DTD,否則就是XSD

獲取document

經(jīng)過了驗(yàn)證模式準(zhǔn)備的步驟就可以進(jìn)行Document加載了丹允,對(duì)于文檔的讀取委托給了DocumentLoader去執(zhí)行郭厌,這里的DocumentLoader是個(gè)接口,而真正調(diào)用的是DefaultDocumentLoader雕蔽,解析代碼如下

@Override
    public Document loadDocument(InputSource inputSource, EntityResolver entityResolver,
            ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception {

        DocumentBuilderFactory factory = createDocumentBuilderFactory(validationMode, namespaceAware);
        if (logger.isDebugEnabled()) {
            logger.debug("Using JAXP provider [" + factory.getClass().getName() + "]");
        }
        DocumentBuilder builder = createDocumentBuilder(factory, entityResolver, errorHandler);
        return builder.parse(inputSource);
    }

首選創(chuàng)建DocumentBuildFactory折柠,再通過DocumentBuilderFactory創(chuàng)建DocumentBuilder,進(jìn)而解析InputSource來返回Document對(duì)象批狐。對(duì)于參數(shù)entityResolver扇售,傳入的是通過getEntityResolver()函數(shù)獲取的返回值,代碼如下

protected EntityResolver getEntityResolver() {
        if (this.entityResolver == null) {
            // Determine default EntityResolver to use.
            ResourceLoader resourceLoader = getResourceLoader();
            if (resourceLoader != null) {
                this.entityResolver = new ResourceEntityResolver(resourceLoader);
            }
            else {
                this.entityResolver = new DelegatingEntityResolver(getBeanClassLoader());
            }
        }
        return this.entityResolver;
    }

EntityResolver用法

如果SAX應(yīng)用程序需要實(shí)現(xiàn)自定義處理外部實(shí)體嚣艇,則必須實(shí)現(xiàn)此接口并使用setEntityResolver方法向SAX驅(qū)動(dòng)器注冊(cè)一個(gè)實(shí)例承冰。也就是說,對(duì)于解析一個(gè)XML食零,SAX首先讀取該XML文檔上的聲明困乒,根據(jù)聲明去尋找相應(yīng)的DTD定義,以便對(duì)文檔進(jìn)行一個(gè)驗(yàn)證贰谣,默認(rèn)的尋找規(guī)則娜搂,即通過網(wǎng)絡(luò)(實(shí)現(xiàn)上就是聲明DTD的URI地址)來下載相應(yīng)的DTD聲明,并進(jìn)行認(rèn)證吱抚。下載的過程是一個(gè)漫長的過程百宇,而且當(dāng)網(wǎng)絡(luò)中斷或不可用時(shí),這里會(huì)報(bào)錯(cuò)秘豹,就是因?yàn)橄鄳?yīng)的DTD聲明沒有被找到的原因携御。

EntityResolver的作用是項(xiàng)目本身就可以提供一個(gè)如何尋找DTD聲明的方法,即由程序來實(shí)現(xiàn)尋找DTD聲明的過程,比如將DTD文件放到項(xiàng)目中某處因痛,在實(shí)現(xiàn)時(shí)直接將此文檔讀取并返回給SAX即可婚苹,entityResolver的接口方法聲明:

InputSource resolveEntity(String publicId, String systemId)

這里它接收兩個(gè)參數(shù)publicId,systemId并返回InputSource對(duì)象鸵膏,以特定配置文件來解析

(1)如果在解析驗(yàn)證模式為XSD的配置文件膊升,代碼如下:

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

....

\</beans\>

讀取到兩個(gè)參數(shù):

(2)如果解析驗(yàn)證模式為DTD的配置文件,代碼如下:

    <?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//Spring//DTD BEAN 2.0//EN" "http://www.Springframework.org/dtd/Spring-beans-2.0.dtd">
....
</beans>

讀取到以下兩個(gè)參數(shù)

根據(jù)之前Spring中通過getEntityResolver()方法對(duì)EntityResolver的獲取谭企,我們知道廓译,Spring中使用DelegatingEntityResolver類為EntityResolver的實(shí)現(xiàn)類,resolveEntity實(shí)現(xiàn)方法如下:

@Override
    public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException {
        if (systemId != null) {
            if (systemId.endsWith(DTD_SUFFIX)) {
                return this.dtdResolver.resolveEntity(publicId, systemId);
            }
            else if (systemId.endsWith(XSD_SUFFIX)) {
                return this.schemaResolver.resolveEntity(publicId, systemId);
            }
        }
        return null;
    }

對(duì)不同的驗(yàn)證模式债查,Spring使用了不同的解析器解析非区,比如加載DTD類型的BeansDtdResolver的resolveEntity是直接截取systemId最后的xx.dtd然后去當(dāng)前路徑下尋找,而加載XSD類型的PluggableSchemaResolver類的resolveEntity是默認(rèn)到META-INF/Spring.schemas文件中找到systemId所對(duì)應(yīng)的XSD文件并加載盹廷。

解析并注冊(cè)BeanDefinition

當(dāng)把文件轉(zhuǎn)換成Document后征绸,接下來就是對(duì)bean的提取及注冊(cè),當(dāng)程序已經(jīng)擁有了XML文檔文件的Document實(shí)例對(duì)象時(shí)俄占,就會(huì)被引入下面這個(gè)方法

public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
  //使用DefaultBeanDefinitionDocumentReader實(shí)例化BeanDefinitionDocumentReader
        BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
        int countBefore = getRegistry().getBeanDefinitionCount();
        //加載及注冊(cè)bean
        documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
        return getRegistry().getBeanDefinitionCount() - countBefore;
    }

BeanDefinitionDocumentReader是一個(gè)接口管怠,而實(shí)例化的工作是在createBeanDefinitionDocumentReader()中完成的,而通過此方法缸榄,BeanDefinitionDocumentReader真正的類型其實(shí)已經(jīng)是DefaultBeanDefinitionDocumentReader了渤弛,進(jìn)入DefaultBeanDefinitionDocumentReader后,發(fā)現(xiàn)這個(gè)方法的重要目的之一就是提取root甚带,以便于再次將root作為參數(shù)繼續(xù)BeanDefinition的注冊(cè)

@Override
    public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
        this.readerContext = readerContext;
        logger.debug("Loading bean definitions");
        Element root = doc.getDocumentElement();
        doRegisterBeanDefinitions(root);
    }
    protected void doRegisterBeanDefinitions(Element root) {
        // Any nested <beans> elements will cause recursion in this method. In
        // order to propagate and preserve <beans> default-* attributes correctly,
        // keep track of the current (parent) delegate, which may be null. Create
        // the new (child) delegate with a reference to the parent for fallback purposes,
        // then ultimately reset this.delegate back to its original (parent) reference.
        // this behavior emulates a stack of delegates without actually necessitating one.
        BeanDefinitionParserDelegate parent = this.delegate;
        this.delegate = createDelegate(getReaderContext(), root, parent);

        if (this.delegate.isDefaultNamespace(root)) {
            String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
            if (StringUtils.hasText(profileSpec)) {
                String[] specifiedProfiles = StringUtils.tokenizeToStringArray(
                        profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
                if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {
                    return;
                }
            }
        }

        preProcessXml(root);
        parseBeanDefinitions(root, this.delegate);
        postProcessXml(root);

        this.delegate = parent;
    }
    
    
    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);
        }
    }

在Spring的XML配置里面有兩大類Bean聲明她肯,一個(gè)是默認(rèn)的,如:

<bean id="test" class="test.TestBean"/>

另一種是自定義的鹰贵,如:

<context:component-scan base-package="com.lufax.spring"/>

而這兩種方式的讀取及解析差別是非常大的晴氨,如果采用Spring默認(rèn)的配置,Spring當(dāng)然知道該怎么做碉输,但如果是自定義的瑞筐,那么就需要用戶實(shí)現(xiàn)一些接口及配置了。對(duì)于根節(jié)點(diǎn)或子節(jié)點(diǎn)如果是默認(rèn)命名空間的話采用parseDefaultElement方法進(jìn)行解析腊瑟,否則使用delegate.parseCustomElement方法對(duì)自定義命名空間進(jìn)行解析聚假。而判斷是否默認(rèn)命名空間還是自定義命名空間的辦法其實(shí)是使用node.getNamespaceURI()獲取命名空間,并與Spring中固定的命名空間http://www.springframework.org/schema/beans進(jìn)行對(duì)比闰非,如果一致則認(rèn)為是默認(rèn)膘格,否則就認(rèn)為是自定義

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市财松,隨后出現(xiàn)的幾起案子瘪贱,更是在濱河造成了極大的恐慌纱控,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,265評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件菜秦,死亡現(xiàn)場(chǎng)離奇詭異甜害,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)球昨,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,078評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門尔店,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人主慰,你說我怎么就攤上這事嚣州。” “怎么了共螺?”我有些...
    開封第一講書人閱讀 156,852評(píng)論 0 347
  • 文/不壞的土叔 我叫張陵该肴,是天一觀的道長。 經(jīng)常有香客問我藐不,道長匀哄,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,408評(píng)論 1 283
  • 正文 為了忘掉前任雏蛮,我火速辦了婚禮拱雏,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘底扳。我一直安慰自己,他們只是感情好贡耽,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,445評(píng)論 5 384
  • 文/花漫 我一把揭開白布衷模。 她就那樣靜靜地躺著,像睡著了一般蒲赂。 火紅的嫁衣襯著肌膚如雪阱冶。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,772評(píng)論 1 290
  • 那天滥嘴,我揣著相機(jī)與錄音木蹬,去河邊找鬼。 笑死若皱,一個(gè)胖子當(dāng)著我的面吹牛镊叁,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播走触,決...
    沈念sama閱讀 38,921評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼晦譬,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了互广?” 一聲冷哼從身側(cè)響起敛腌,我...
    開封第一講書人閱讀 37,688評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤卧土,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后像樊,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體尤莺,經(jīng)...
    沈念sama閱讀 44,130評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,467評(píng)論 2 325
  • 正文 我和宋清朗相戀三年生棍,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了颤霎。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,617評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡足绅,死狀恐怖捷绑,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情氢妈,我是刑警寧澤粹污,帶...
    沈念sama閱讀 34,276評(píng)論 4 329
  • 正文 年R本政府宣布,位于F島的核電站首量,受9級(jí)特大地震影響壮吩,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜加缘,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,882評(píng)論 3 312
  • 文/蒙蒙 一鸭叙、第九天 我趴在偏房一處隱蔽的房頂上張望耀态。 院中可真熱鬧俺猿,春花似錦濒生、人聲如沸仿贬。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,740評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽咙好。三九已至渡蜻,卻和暖如春辑莫,著一層夾襖步出監(jiān)牢的瞬間学歧,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,967評(píng)論 1 265
  • 我被黑心中介騙來泰國打工各吨, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留枝笨,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,315評(píng)論 2 360
  • 正文 我出身青樓揭蜒,卻偏偏與公主長得像横浑,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子屉更,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,486評(píng)論 2 348

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