spring源码学习之三 XmlWebApplicationContext.loadBeanDefinitions源码分析

接上篇
首先查看该方法源码:

/** * Loads the bean definitions via an XmlBeanDefinitionReader. * @see org.springframework.beans.factory.xml.XmlBeanDefinitionReader * @see #initBeanDefinitionReader * @see #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.setEnvironment(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);
    }

    /** * Initialize the bean definition reader used for loading the bean * definitions of this context. Default implementation is empty. * <p>Can be overridden in subclasses, e.g. for turning off XML validation * or using a different XmlBeanDefinitionParser implementation. * @param beanDefinitionReader the bean definition reader used by this context * @see org.springframework.beans.factory.xml.XmlBeanDefinitionReader#setValidationMode * @see org.springframework.beans.factory.xml.XmlBeanDefinitionReader#setDocumentReaderClass */
    protected void initBeanDefinitionReader(XmlBeanDefinitionReader beanDefinitionReader) {
    }

    /** * Load the bean definitions with the given XmlBeanDefinitionReader. * <p>The lifecycle of the bean factory is handled by the refreshBeanFactory method; * therefore this method is just supposed to load and/or register bean definitions. * <p>Delegates to a ResourcePatternResolver for resolving location patterns * into Resource instances. * @throws IOException if the required XML document isn't found * @see #refreshBeanFactory * @see #getConfigLocations * @see #getResources * @see #getResourcePatternResolver */
    protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws IOException {
        String[] configLocations = getConfigLocations();
        if (configLocations != null) {
            for (String configLocation : configLocations) {
                reader.loadBeanDefinitions(configLocation);
            }
        }
    }

从源码及方法注释可以看出,loadBeanDefinitions方法首先创建一个XmlBeanDefinitionReader对象,然后将当前applicationContext中创建的DefaultListableBeanFactory以及Environment等对象传递给XmlBeanDefinitionReader对象,最后调用XmlBeanDefinitionReader.loadBeanDefinitions(configLocation)来完成实际BeanDefinition加载工作,那么在XmlBeanDefinitionReader.loadBeanDefinitions(configLocation)内部又是怎么一个流程?接着进入XmlBeanDefinitionReader类中的loadBeanDefinitions(configLocation)来查看源码实现:
首先上XmlBeanDefinitionReader类UML图:
《spring源码学习之三 XmlWebApplicationContext.loadBeanDefinitions源码分析》
从上面图中可以看出XmlBeanDefinitionReader实现了BeanDefinitionReader接口,该接口从名称上可以看出主要用来读取BeanDefinition对象,接口提供了以下方法:
《spring源码学习之三 XmlWebApplicationContext.loadBeanDefinitions源码分析》

  • BeanDefinitionRegistry getRegistry();获取BeanDefinition注册容器,以便将加载进来的beanDefinition存放到该容器中.查看源代码可以知道在XmlBeanDefinitionReader中,该方法返回的其实就是在XmlWebApplicationContext中创建的DefaultListableBeanFactory对象.
  • ResourceLoader getResourceLoader();返回解析资源加载器对象,该方法实现是在BeanDefinitionReader抽象实现类AbstractBeanDefinitionReader中来实现,查看源代码可以看出其实是通过this.resourceLoader = new PathMatchingResourcePatternResolver();返回一个PathMatchingResourcePatternResolver对象.
  • loadBeanDefinition提供了几个方法重载,在这几个方法中除了loadBeanDefinitions(Resource resource)是在XmlBeanDefinitionReader中实现外,其它3个是在AbstractBeanDefinitionReader中实现,其实这3个最终调用的是loadBeanDefinitions(Resource resource),只不过loadBeanDefinitions(String location)首先将location解析成对应Resource对象,而用来解析的功能即使用getResourceLoader()返回的ResourceLoader对象来完成(在AbstractBeanDefinitionReader中即为PathMatchingResourcePatternResolver).
  • loadBeanDefinitions(Resource resource)方法是在XmlBeanDefinitionReader类中来实现的,源码如下:

XmlBeanDefinitionReader.loadBeanDefinitions(Resource resource)

/** * Load bean definitions from the specified XML file. * @param resource the resource descriptor for the XML file * @return the number of bean definitions found * @throws BeanDefinitionStoreException in case of loading or parsing errors */
    @Override
    public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
        return loadBeanDefinitions(new EncodedResource(resource));
    }

    /** * Load bean definitions from the specified XML file. * @param encodedResource the resource descriptor for the XML file, * allowing to specify an encoding to use for parsing the file * @return the number of bean definitions found * @throws BeanDefinitionStoreException in case of loading or parsing errors */
    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 {
            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();
            }
        }
    }
/** * Actually load bean definitions from the specified XML file. * @param inputSource the SAX InputSource to read from * @param resource the resource descriptor for the XML file * @return the number of bean definitions found * @throws BeanDefinitionStoreException in case of loading or parsing errors * @see #doLoadDocument * @see #registerBeanDefinitions */
    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);
        }
    }

    /** * Actually load the specified document using the configured DocumentLoader. * @param inputSource the SAX InputSource to read from * @param resource the resource descriptor for the XML file * @return the DOM Document * @throws Exception when thrown from the DocumentLoader * @see #setDocumentLoader * @see DocumentLoader#loadDocument */
    protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception {
        return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler,
                getValidationModeForResource(resource), isNamespaceAware());
    }
/** * Register the bean definitions contained in the given DOM document. * Called by {@code loadBeanDefinitions}. * <p>Creates a new instance of the parser class and invokes * {@code registerBeanDefinitions} on it. * @param doc the DOM document * @param resource the resource descriptor (for context information) * @return the number of bean definitions found * @throws BeanDefinitionStoreException in case of parsing errors * @see #loadBeanDefinitions * @see #setDocumentReaderClass * @see BeanDefinitionDocumentReader#registerBeanDefinitions */
    public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
        BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
        int countBefore = getRegistry().getBeanDefinitionCount();
        documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
        return getRegistry().getBeanDefinitionCount() - countBefore;
    }

    /** * Create the {@link BeanDefinitionDocumentReader} to use for actually * reading bean definitions from an XML document. * <p>The default implementation instantiates the specified "documentReaderClass". * @see #setDocumentReaderClass */
    protected BeanDefinitionDocumentReader createBeanDefinitionDocumentReader() {
        return BeanDefinitionDocumentReader.class.cast(BeanUtils.instantiateClass(this.documentReaderClass));
    }

    /** * Create the {@link XmlReaderContext} to pass over to the document reader. */
    public XmlReaderContext createReaderContext(Resource resource) {
        return new XmlReaderContext(resource, this.problemReporter, this.eventListener,
                this.sourceExtractor, this, getNamespaceHandlerResolver());
    }
/** * Lazily create a default NamespaceHandlerResolver, if not set before. * @see #createDefaultNamespaceHandlerResolver() */
    public NamespaceHandlerResolver getNamespaceHandlerResolver() {
        if (this.namespaceHandlerResolver == null) {
            this.namespaceHandlerResolver = createDefaultNamespaceHandlerResolver();
        }
        return this.namespaceHandlerResolver;
    }

    /** * Create the default implementation of {@link NamespaceHandlerResolver} used if none is specified. * Default implementation returns an instance of {@link DefaultNamespaceHandlerResolver}. */
    protected NamespaceHandlerResolver createDefaultNamespaceHandlerResolver() {
        return new DefaultNamespaceHandlerResolver(getResourceLoader().getClassLoader());
    }

从上面源码中可以看出loadBeanDefinitions处理过程中将Resource解析成Document文档,然后具体解析Document文档的过程交由BeanDefinitionDocumentReader.registerBeanDefinitions(Document doc, XmlReaderContext readerContext)方法来完成
注意XmlReaderContext对象的创建.在上面createReaderContext方法中,将this对象传递给新创建的XmlReaderContext,因为XmlReaderContext内部需要一个BeanDefinitionRegistry和BeanDefinition关联,而通过this可以获取到关联的BeanDefinitionRegistry.
NamespaceHandlerResolver将在下篇分析

BeanDefinitionDocumentReader

该接口只有registerBeanDefinitions(Document doc, XmlReaderContext readerContext)一个方法,用来读取Document文档并使用XmlReaderContext来处理Document,注意XmlReaderContext绑定了一个XmlBeanDefinitionReader对象,该对象又关联BeanDefinitionRegistry,所以才能将处理后的BeanDefinition和BeanDefinitionRegistry关联起来.

DefaultBeanDefinitionDocumentReader

该类实现了BeanDefinitionDocumentReader接口,用来将Document文档解析成BeanDefinition对象,下面为该类部分源码:

public static final String BEAN_ELEMENT = BeanDefinitionParserDelegate.BEAN_ELEMENT;

    public static final String NESTED_BEANS_ELEMENT = "beans";

    public static final String ALIAS_ELEMENT = "alias";

    public static final String NAME_ATTRIBUTE = "name";

    public static final String ALIAS_ATTRIBUTE = "alias";

    public static final String IMPORT_ELEMENT = "import";

    public static final String RESOURCE_ATTRIBUTE = "resource";

    public static final String PROFILE_ATTRIBUTE = "profile";
    private XmlReaderContext readerContext;

    private BeanDefinitionParserDelegate delegate;

/** * This implementation parses bean definitions according to the "spring-beans" XSD * (or DTD, historically). * <p>Opens a DOM Document; then initializes the default settings * specified at the {@code <beans/>} level; then parses the contained bean definitions. */
    @Override
    public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
        this.readerContext = readerContext;
        logger.debug("Loading bean definitions");
        Element root = doc.getDocumentElement();
        doRegisterBeanDefinitions(root);
}
/** * Register each bean definition within the given root {@code <beans/>} element. */
    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 BeanDefinitionParserDelegate createDelegate(
            XmlReaderContext readerContext, Element root, BeanDefinitionParserDelegate parentDelegate) {

        BeanDefinitionParserDelegate delegate = new BeanDefinitionParserDelegate(readerContext);
        delegate.initDefaults(root, parentDelegate);
        return delegate;
    }

    /** * Parse the elements at the root level in the document: * "import", "alias", "bean". * @param root the DOM root element of the document */
    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);
        }
    }

    private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
        if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {
            importBeanDefinitionResource(ele);
        }
        else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {
            processAliasRegistration(ele);
        }
        else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {
            processBeanDefinition(ele, delegate);
        }
        else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
            // recurse
            doRegisterBeanDefinitions(ele);
        }
    }
  • 类中定义了对应xml文档中的常量值如”import”,”beans”等,这些常量对应spring配置文件元素名称.如<import>元素
  • 定义了XmlReaderContext和BeanDefinitionParserDelegate两个类型变量,XmlReaderContext前面提到过为xml读取上下文,那么BeanDefinitonParserDelegate呢?从名称上可以看出此类是一个委托类,主要用来处理bean元素,DefaultBeanDefinitionDocumentReader在处理xml元素节点时,如果节点元素对应的namespace不为”http://www.springframework.org/schema/beans“命名空间时,此时将元素委托给BeanDefinitonParserDelegate处理,如<context:component-scan>元素对应的namespace为”http://www.springframework.org/schema/context“,当处理到该元素的时候则委托给BeanDefinitonParserDelegate类处理.该部分功能在parseBeanDefinitions方法实现.
  • parseDefaultElement方法主要用来处理xml节点并且对应namespace为”http://www.springframework.org/schema/beans“的元素,如<import>,在实际应用中会在一个spring配置文件中import多个配置文件,此时在解析<import>元素时,会调用parseDefaultElement根据元素名称调用对应方法,如<import>会调用importBeanDefinitionResource方法进行处理,具体实现参考源码.

    总结

    从上面可以看出在XmlWebApplicationContext.loadBeanDefinitions方法中,首先创建一个XmlBeanDefinitionReader对象,将XmlWebApplicationContext创建的内置容器和配置文件所在位置信息传递给该对象,然后由该对象去执行读取配置加载工作.而XmlBeanDefinitionReader首先根据传递过来的配置文件路径解析成具体实际路径,然后将配置文件读取成Document,并生成一个XmlReaderContext对象后交由DefaultBeanDefinitionDocumentReader对象处理,DefaultBeanDefinitionDocumentReader在拿到Document后,只处理namespace为”http://www.springframework.org/schema/beans“下的元素,如"<bean>","<import>"等,对于其它命名空间下的元素则委托给BeanDefinitonParserDelegate类的parseCustomElement(Element ele)方法进行处理(其实该命名空间下元素具体处理过程也是委托给BeanDefinitonParserDelegate类中的方法进行处理,只不过在DefaultBeanDefinitionDocumentReader.parseDefaultElement方法中分别进行判断来调用BeanDefinitonParserDelegate的哪个方法),那么对于元素Spring如何知道使用哪个类进行处理呢?具体分析请看下一篇

    原文作者:Spring Boot
    原文地址: https://blog.csdn.net/wenjiangchun/article/details/50628728
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞