Spring源码分析专题 —— IOC容器启动过程(中篇)

栏目: 编程工具 · 发布时间: 5年前

内容简介:声明1.建议先阅读2.强烈建议阅读过程中要参照调用过程图,每篇都有对应的调用过程图

声明

1.建议先阅读 《Spring源码分析专题 —— 阅读指引》

2.强烈建议阅读过程中要参照调用过程图,每篇都有对应的调用过程图

3.写文不易,转载请标明出处

前言

在上文《Spring源码分析专题 —— IOC容器启动过程(上篇)》中我们已经寻找到 Spring IOC 启动过程的核心方法 refresh() ,本篇我们将详细讲解启动流程 「定位 -> 加载 -> 注册 -> 实例化」 中的加载与注册环节。

(定位的作用是获取到配置文件,通常我们spring的配置文件是 application.xml 或自定义的 spring-xxx.xml ,定位过程的细节不少,而对我们的主流程影响不大,所以关于定位过程将放在下篇补充讲解)

本篇继续使用上篇中的调用过程图☞ IOC容器启动调用过程图.jpg

加载与注册

我们再看一眼 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.
        /**
         * obtainFreshBeanFactory方法中会调用loadBeanDefinition方法,用于加载bean的定义
         */
        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.
            /** 初始化所有非lazy-init的bean **/
            finishBeanFactoryInitialization(beanFactory);

            // Last step: publish corresponding event.
            finishRefresh();
        }

        catch (BeansException ex) {
            if (logger.isWarnEnabled()) {
                logger.warn("Exception encountered during context initialization - " +
                        "cancelling refresh attempt: " + ex);
            }

            // Destroy already created singletons to avoid dangling resources.
            destroyBeans();

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

            // Propagate exception to caller.
            throw ex;
        }

        finally {
            // Reset common introspection caches in Spring's core, since we
            // might not ever need metadata for singleton beans anymore...
            resetCommonCaches();
        }
    }
}

在这个 refresh() 方法中,我们首先关注的是 obtainFreshBeanFactory()

protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
    /** 实际是调用子类AbstractRefreshableApplicationContext中的refreshBeanFactory方法 **/
    refreshBeanFactory();
    return getBeanFactory();
}

obtainFreshBeanFactory() 方法中调用了 refreshBeanFactory() ,而这个方法是在 AbstractApplicationContext 的子类 AbstractRefreshableApplicationContext 实现

protected final void refreshBeanFactory() throws BeansException {
    if (hasBeanFactory()) {
        destroyBeans();
        closeBeanFactory();
    }
    try {
        DefaultListableBeanFactory beanFactory = createBeanFactory();
        beanFactory.setSerializationId(getId());
        customizeBeanFactory(beanFactory);
        /** [note-by-leapmie] 调用子类XmlWebApplicationContext的loadBeanDefinitions方法 **/
        loadBeanDefinitions(beanFactory);
        synchronized (this.beanFactoryMonitor) {
            this.beanFactory = beanFactory;
        }
    }
    catch (IOException ex) {
        throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
    }
}

refreshBeanFactory方法中调用了 loadBeanDefinitions 方法,路线又回到了 XmlWebApplicationContext 容器,loadBeanDefinitions 方法是在 XmlWebApplicationContext 类中实现的

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);
    /** [note-by-leapmie] 注意传入的beanDefinitionReader是XmlBeanDefinitionReader **/
    loadBeanDefinitions(beanDefinitionReader);
}

方法最后是调用了重载的 loadBeanDefinitions 方法,传入的参数是 XmlBeanDefinitionReader 的对象,我们先看一看重载的 loadBeanDefinitions 方法

protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws IOException {
    String[] configLocations = getConfigLocations();
    if (configLocations != null) {
        for (String configLocation : configLocations) {
            /** 实际是调用XmlBeanDefinitionReader的loadBeanDefinitions方法 **/
            reader.loadBeanDefinitions(configLocation);
        }
    }
}

可以看到,其实最后是调用 reader 的 loadBeanDefinitions 方法,此处 reader 的类型是 XmlBeanDefinitionReader ,所以我们查看 XmlBeanDefinitionReader 中的 loadBeanDefinitions 方法

public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
    .
    .
    .
    try {
        InputStream inputStream = encodedResource.getResource().getInputStream();
        try {
            InputSource inputSource = new InputSource(inputStream);
            if (encodedResource.getEncoding() != null) {
                inputSource.setEncoding(encodedResource.getEncoding());
            }
            /** do开头的方法表示是正真执行处理操作的方法 **/
            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();
        }
    }
}

此处我们关注的是 doLoadBeanDefinitions 方法,有个技巧,在Spring中,以do开头的方法都是最终实际执行逻辑处理的。

protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
            throws BeanDefinitionStoreException {

    try {
        /** [note-by-leapmie] 读取加载配置文件 **/
        Document doc = doLoadDocument(inputSource, resource);
        /** [note-by-leapmie] 注册Bean **/
        int count = registerBeanDefinitions(doc, resource);
        if (logger.isDebugEnabled()) {
            logger.debug("Loaded " + count + " bean definitions from " + resource);
        }
        return count;
    }
    .
    .
    .
}

在这里有两个方法值得我们关注,一个是 doLoadDocument ,负责把配置文件读取为 Document 对象,这个方法中包含了「定位」过程的处理逻辑,关于定位过程我们在下篇再详细分析;第二个是 registerBeanDefinitions 方法,这个方法包含了「加载」和「注册」逻辑。

public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
    /** [note-by-leapmie] 获取BeanDefinitionDocumentReader,此处获得的对象实际类型为DefaultBeanDefinitionDocumentReader **/
    BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
    int countBefore = getRegistry().getBeanDefinitionCount();
    /** [note-by-leapmie] 调用Reader的registerBeanDefinitions方法 */
    documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
    return getRegistry().getBeanDefinitionCount() - countBefore;
}

这里关键有两步,第一步是获取 documentReader ,第二步是调用 documentReader 的 registerBeanDefinitions 方法。稍微跟踪一下可知 documentReader 的实际类型是 DefaultBeanDefinitionDocumentReader,所以我们进入到 DefaultBeanDefinitionDocumentReader 的 registerBeanDefinitions 方法

public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
    this.readerContext = readerContext;
    doRegisterBeanDefinitions(doc.getDocumentElement());
}

按照惯例,干活的是do开头的方法

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);
            // We cannot use Profiles.of(...) since profile expressions are not supported
            // in XML config. See SPR-12458 for details.
            if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {
                if (logger.isDebugEnabled()) {
                    logger.debug("Skipped XML bean definition file due to specified profiles [" + profileSpec +
                            "] not matching: " + getReaderContext().getResource());
                }
                return;
            }
        }
    }

    preProcessXml(root);
    /** 解析转换为BeanDefinitions **/
    parseBeanDefinitions(root, this.delegate);
    postProcessXml(root);

    this.delegate = parent;
}

然后是 parseBeanDefinitions 方法

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;
                /** [note-by-leapmie] 判断元素是否属于默认的Namespace(当标签为<beans>时判断条件为真) **/
                if (delegate.isDefaultNamespace(ele)) {
                    /** [note-by-leapmie] 处理默认的Element **/
                    parseDefaultElement(ele, delegate);
                }
                else {
                    delegate.parseCustomElement(ele);
                }
            }
        }
    }
    else {
        delegate.parseCustomElement(root);
    }
}

isDefaultNamespaces 方法是判断元素是否属于默认的 Namespace ,通过跟踪可知,这个默认的 Namespace 是指 <beans> 标签, 我们知道在spring的配置文件中,对bean的定义是放在 <beans> 标签里边的,所以接下来的 parseDefaultElement 方法则是用于解析 bean 定义的。

private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
    /** 处理<import>标签 **/
    if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {
        importBeanDefinitionResource(ele);
    }
    /** 处理<alias>标签 **/
    else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {
        processAliasRegistration(ele);
    }
    /** 处理<bean>标签 **/
    else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {
        processBeanDefinition(ele, delegate);
    }
    /** 处理嵌套的<beans>标签 **/
    else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
        // recurse
        doRegisterBeanDefinitions(ele);
    }
}

到这里我们就一目了然了,这很显然就是针对 <beans> 标签中的各种元素进行解析,对于其他标签我们不深究,直接看处理 <bean> 标签的 processBeanDefinition 方法

protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
    /**
     * [note-by-leapmie]
     * 调用BeanDefinitionParserDelegate的parseBeanDefinitionElement方法
     * 返回一个包含BeanDefinition信息的BeanDefinitionHolder实例
     * **/
    BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
    if (bdHolder != null) {
        bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
        try {
            // Register the final decorated instance.
            /**
             * [note-by-leapmie]
             * 注册BeanDefinition
             * 传入的参数是刚刚获取到的BeanDefinitionHolder对象,再加上DefaultListableBeanFactory对象
             * DefaultListableBeanFactory对象的由来需要追溯到AbstractRefreshableApplicationContext的refreshBeanFactory()方法中
             * **/
            BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
        }
        catch (BeanDefinitionStoreException ex) {
            getReaderContext().error("Failed to register bean definition with name '" +
                    bdHolder.getBeanName() + "'", ele, ex);
        }
        // Send registration event.
        getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
    }
}

这里有两个非常关键的方法:

  • 一个是 parseBeanDefinitionElement ,这个方法最终会返回一个持有 BeanDefinition 的 BeanDefinitionHolder 实例,我们在上篇开头的结论中已经说了,加载的过程其实就是把bean的定义转换成一个 BeanDefinition 对象,所以 parseBeanDefinitionElement 对应的便是 「加载」 过程;
  • 另一个则是 registerBeanDefinition ,这个方法对应的便是「注册」过程。

接下来我们将分两部分分别讲解 parseBeanDefinitionElement 和 registerBeanDefinition 的内容

1. 加载 (parseBeanDefinitionElement)

bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder); 中的 delegate 是 BeanDefinitionParserDelegate 的实例,我们查看 BeanDefinitionParserDelegate 中的 decorateBeanDefinitionIfRequired 方法

public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, @Nullable BeanDefinition containingBean) {
    
    .
    .
    .

    /** [note-by-leapmie] 此处调用的parseBeanDefinitionElement方法返回的是BeanDefinition **/
    AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName, containingBean);
    if (beanDefinition != null) {
        .
        .
        .
        /** [note-by-leapmie] 把beanDefinition注入BeanDefinitionHolder中 **/
        return new BeanDefinitionHolder(beanDefinition, beanName, aliasesArray);
    }

    return null;
}

这个方法先调用另一个重载的 parseBeanDefinitionElement 方法,然后把获得 beanDefinition 传入 BeanDefinitionHolder 。我们看一看重载的 parseBeanDefinitionElement 方法

public AbstractBeanDefinition parseBeanDefinitionElement(
            Element ele, String beanName, @Nullable BeanDefinition containingBean) {

    .
    .
    .

    try {
        AbstractBeanDefinition bd = createBeanDefinition(className, parent);

        /** [note-by-leapmie] 解析bean定义的属性 **/
        parseBeanDefinitionAttributes(ele, beanName, containingBean, bd);
        bd.setDescription(DomUtils.getChildElementValueByTagName(ele, DESCRIPTION_ELEMENT));

        parseMetaElements(ele, bd);
        parseLookupOverrideSubElements(ele, bd.getMethodOverrides());
        parseReplacedMethodSubElements(ele, bd.getMethodOverrides());

        parseConstructorArgElements(ele, bd);
        parsePropertyElements(ele, bd);
        parseQualifierElements(ele, bd);

        bd.setResource(this.readerContext.getResource());
        bd.setSource(extractSource(ele));

        return bd;
    }
    .
    .
    .
    return null;
}

这里的parse开头的方法都是对bean定义的属性标签进行解析,例如「name」、「singleton」、「lazy-init」等,大家可以自行深入了解每一个parse方法是如何解析各个属性的,在本文中就不再占用篇幅逐一讲解了。至此我们已经获取到了BeanDefinition的信息,下一步就到「注册」了。

2. 注册 (registerBeanDefinition)

所谓的注册,其实就是把BeanDefintion存储到IOC容器中,我们进入到 registerBeanDefinition 中看一看是如何实现的。

public static void registerBeanDefinition(
            BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)
            throws BeanDefinitionStoreException {

    // Register bean definition under primary name.
    String beanName = definitionHolder.getBeanName();
    /** [note-by-leapmie] 此处实际是调用DefaultListableBeanFactory的registerBeanDefinition方法 **/
    registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());

    // Register aliases for bean name, if any.
    String[] aliases = definitionHolder.getAliases();
    if (aliases != null) {
        for (String alias : aliases) {
            registry.registerAlias(beanName, alias);
        }
    }
}

这里最终调用的是 registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition()) 其中 registry 是 DefaultListableBeanFactory 的实例。

(为什么是 DefaultListableBeanFactory ?感兴趣的可以去追溯一下,给出一点提示,在 AbstractRefreshableApplicationContext 的 refreshBeanFactory() 方法中会创建DefaultListableBeanFactory的实例,并在之后的所有关键方法中都会作为参数传入该实例,保证后续的调用流程中都能获取到该实例)

DefaultListableBeanFactory 的 registerBeanDefinition 方法如下

public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
            throws BeanDefinitionStoreException {

    .
    .
    .
    if (existingDefinition != null) {
        .
        .
        .
    }
    else {
        if (hasBeanCreationStarted()) {
            // Cannot modify startup-time collection elements anymore (for stable iteration)
            synchronized (this.beanDefinitionMap) {
                this.beanDefinitionMap.put(beanName, beanDefinition);
                List<String> updatedDefinitions = new ArrayList<>(this.beanDefinitionNames.size() + 1);
                updatedDefinitions.addAll(this.beanDefinitionNames);
                updatedDefinitions.add(beanName);
                this.beanDefinitionNames = updatedDefinitions;
                if (this.manualSingletonNames.contains(beanName)) {
                    Set<String> updatedSingletons = new LinkedHashSet<>(this.manualSingletonNames);
                    updatedSingletons.remove(beanName);
                    this.manualSingletonNames = updatedSingletons;
                }
            }
        }
        else {
            // Still in startup registration phase
            /**
             * 把BeanDefinitionc添加到beanDefinitionMap中
             * Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>(256);
             * 由此可知,IOC的启动过程是先把Bean的定义解析转换为BeanDefiniton,
             * 最后存储于IOC容器(DefaultListableBeanFactory是一个IOC容器)的一个Map变量中。
             * */
            this.beanDefinitionMap.put(beanName, beanDefinition);
            /**
             * 把所有的Bean名存储于beanDefinitionNames列表中
             */
            this.beanDefinitionNames.add(beanName);
            this.manualSingletonNames.remove(beanName);
        }
        this.frozenBeanDefinitionNames = null;
    }

    if (existingDefinition != null || containsSingleton(beanName)) {
        resetBeanDefinition(beanName);
    }
}

this.beanDefinitionMap.put(beanName, beanDefinition); 这一行是重点,字面意思已经很明显,就是把 beanName 和 beanDefinition 以 key-value 的形式存储于 beanDefinitionMap 中, beanDefinitionMap 的定义如下。

/** Map of bean definition objects, keyed by bean name. */
private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>(256);

跟踪到这一步我们便可以得出结论:「 BeanDefinition 是存储在 DefaultListableBeanFactory 的一个 Map 数据结构中 」


以上所述就是小编给大家介绍的《Spring源码分析专题 —— IOC容器启动过程(中篇)》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

人月神话

人月神话

[美] 弗雷德里克·布鲁克斯 / 汪颖 / 清华大学出版社 / 2002-11 / 29.80元

作者为人们管理复杂项目提供了颇具洞察力的见解,既有很多发人深省的观点,也有大量的软件工程实践。书中的内容来自布鲁克斯在IBM公司System 360家族和OS 360中的项目管理经验。初版的20年后,布鲁克斯重新审视了他原先的观点,增加了一些新的想法和建议。新增加的章节包括:原著中一些核心观点的精华;在经过了一个时代以后,Brooks博士对原先观点新的认识;1986年的经典文章《没有银弹》;对19......一起来看看 《人月神话》 这本书的介绍吧!

图片转BASE64编码
图片转BASE64编码

在线图片转Base64编码工具

Base64 编码/解码
Base64 编码/解码

Base64 编码/解码

HSV CMYK 转换工具
HSV CMYK 转换工具

HSV CMYK互换工具