Spring 的那点事 (三) —— obtainFreshBeanFactory() 创建 beanFactory & 注册 beanDefinition

167 阅读4分钟

这是我参与11月更文挑战的第4天,活动详情查看:2021最后一次更文挑战

refresh流程

上文 Spring IoC:ApplicationContext 刷新前的配置介绍了 refresh 方法前的环境准备操作,接下来正式进入 refresh 方法。prepareRefresh 是 refresh 里的第一个方法,主要是一些准备工作,之前文章已经阐述过了。prepareRefresh 后,马上就调用了 obtainFreshBeanFactory 创建beanFactory以及扫描bean信息(beanDefinition),并通过 BeanDefinitionRegistry 注册到容器中。

obtainFreshBeanFactory 方法概述

该方法会解析所有 Spring 配置文件(通常我们会放在 resources 目录下),将所有 Spring 配置文件中的 bean 定义封装成 BeanDefinition,加载到 BeanFactory 中。常见的,如果解析到 <context:component-scan base-package="" /> 注解时,会扫描 base-package 指定的目录,将该目录下使用指定注解( @Controller、@Service、@Component、@Repository)的 bean 定义也同样封装成 BeanDefinition,加载到 BeanFactory 中。

上面提到的 “加载到 BeanFactory 中” 的内容主要指的是添加到以下3个缓存:

  • beanDefinitionNames缓存:所有被加载到 BeanFactory 中的 bean 的 beanName 集合。\
  • beanDefinitionMap缓存:所有被加载到 BeanFactory 中的 bean 的 beanName 和 BeanDefinition 映射。\
  • aliasMap缓存:所有被加载到 BeanFactory 中的 bean 的 beanName 和别名映射。

obtainFreshBeanFactory 源码

protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
    // 1.刷新 BeanFactory,由AbstractRefreshableApplicationContext实现
    refreshBeanFactory();
    
    // 2.拿到刷新后的 BeanFactory
    ConfigurableListableBeanFactory beanFactory = getBeanFactory();
    if (logger.isDebugEnabled()) {
        logger.debug("Bean factory for " + getDisplayName() + ": " + beanFactory);
    }
    return beanFactory;
}

refreshBeanFactory 方法

  • 刷新 BeanFactory,由 AbstractRefreshableApplicationContext 实现

    @Override
    protected final void refreshBeanFactory() throws BeansException {
        // 1.判断是否已经存在 BeanFactory,如果存在则先销毁、关闭该 BeanFactory
        if (hasBeanFactory()) {
            destroyBeans();
            closeBeanFactory();
        }
        try {
            // 2.创建一个新的BeanFactory
            DefaultListableBeanFactory beanFactory = createBeanFactory();
            beanFactory.setSerializationId(getId());
            customizeBeanFactory(beanFactory);
            // 3.加载 bean 定义。
            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 定义,方法以 do 开头,真正处理的方法

    • 根据 inputSource 和 resource 加载 XML文件,并封装成 Document
    • 根据返回的 Document 注册 bean 信息
    protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
            throws BeanDefinitionStoreException {
        try {
            // 1.根据inputSource和resource加载XML文件,并封装成Document
            Document doc = doLoadDocument(inputSource, resource);
            // 2.根据返回的Document注册Bean信息(对配置文件的解析,核心逻辑)
            return registerBeanDefinitions(doc, resource);
        } catch (BeanDefinitionStoreException ex) {
            throw ex;
        } 
        // ...
    }
    
  • 获取 XML 配置文件的验证模式。XML 文件的验证模式是用来保证 XML 文件的正确性,常见的验证模式有两种:DTD 和 XSD,以下简单展示下这两种验证模式的配置

    
    protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception {
        // 1.getValidationModeForResource(resource): 获取XML配置文件的验证模式
        // 2.documentLoader.loadDocument: 加载XML文件,并得到对应的 Document
        return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler,
                getValidationModeForResource(resource), isNamespaceAware());
    }
    
    protected int getValidationModeForResource(Resource resource) {
        int validationModeToUse = getValidationMode();
        // 1.1 如果手动指定了XML文件的验证模式则使用指定的验证模式
        if (validationModeToUse != VALIDATION_AUTO) {
            return validationModeToUse;
        }
        // 1.2 如果未指定则使用自动检测
        int detectedMode = detectValidationMode(resource);
        // 1.3 如果检测出的验证模式不为 VALIDATION_AUTO, 则返回检测出来的验证模式
        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).
        // 1.4 如果最终没找到验证模式,则使用 XSD
        return VALIDATION_XSD;
    }
    
    protected int detectValidationMode(Resource resource) {
        // 1.2.1 校验resource是否为open stream
        if (resource.isOpen()) {
            throw new BeanDefinitionStoreException(
                    "Passed-in Resource [" + resource + "] contains an open stream: " +
                            "cannot determine validation mode automatically. Either pass in a Resource " +
                            "that is able to create fresh streams, or explicitly specify the validationMode " +
                            "on your XmlBeanDefinitionReader instance.");
        }
    
        InputStream inputStream;
        try {
            // 1.2.2 校验resource是否可以打开InputStream
            inputStream = resource.getInputStream();
        } catch (IOException ex) {
            throw new BeanDefinitionStoreException(
                    "Unable to determine validation mode for [" + resource + "]: cannot open InputStream. " +
                            "Did you attempt to load directly from a SAX InputSource without specifying the " +
                            "validationMode on your XmlBeanDefinitionReader instance?", ex);
        }
    
        try {
            // 1.2.3 根据inputStream检测验证模式
            return this.validationModeDetector.detectValidationMode(inputStream);
        } catch (IOException ex) {
            throw new BeanDefinitionStoreException("Unable to determine validation mode for [" +
                    resource + "]: an error occurred whilst reading from the InputStream.", ex);
        }
    }
    
    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;
            // 1.2.3.1 按行遍历xml配置文件,获取xml文件的验证模式
            while ((content = reader.readLine()) != null) {
                content = consumeCommentTokens(content);
                // 如果读取的行是空或者注释则略过
                if (this.inComment || !StringUtils.hasText(content)) {
                    continue;
                }
                // 内容包含"DOCTYPE"则为DTD,否则为XSD
                if (hasDoctype(content)) {
                    isDtdValidated = true;
                    break;
                }
                // 如果content带有 '<' 开始符号,则结束遍历。因为验证模式一定会在开始符号之前,所以到此可以认为没有验证模式
                if (hasOpeningTag(content)) {
                    // End of meaningful data...
                    break;
                }
            }
            // 1.2.3.2 根据遍历结果返回验证模式是 DTD 还是 XSD
            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();
        }
    }
    
    // DefaultDocumentLoader.java
    @Override
    public Document loadDocument(InputSource inputSource, EntityResolver entityResolver,
            ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception {
        // 2.1 创建DocumentBuilderFactory
        DocumentBuilderFactory factory = createDocumentBuilderFactory(validationMode, namespaceAware);
        if (logger.isDebugEnabled()) {
            logger.debug("Using JAXP provider [" + factory.getClass().getName() + "]");
        }
        // 2.2 通过DocumentBuilderFactory创建DocumentBuilder
        DocumentBuilder builder = createDocumentBuilder(factory, entityResolver, errorHandler);
        // 2.3 使用DocumentBuilder解析inputSource返回Document对象
        return builder.parse(inputSource);
    }
    
  • 通过拿到的节点,注册 bean 定义

    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;
        // 构建BeanDefinitionParserDelegate
        this.delegate = createDelegate(getReaderContext(), root, parent);
    
        // 1.校验root节点的命名空间是否为默认的命名空间(默认命名空间http://www.springframework.org/schema/beans)
        if (this.delegate.isDefaultNamespace(root)) {
            // 2.处理profile属性
            String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
            if (StringUtils.hasText(profileSpec)) {
                String[] specifiedProfiles = StringUtils.tokenizeToStringArray(
                        profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
                // 校验当前节点的 profile 是否符合当前环境定义的, 如果不是则直接跳过, 不解析该节点下的内容
                if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {
                    if (logger.isInfoEnabled()) {
                        logger.info("Skipped XML bean definition file due to specified profiles [" + profileSpec +
                                "] not matching: " + getReaderContext().getResource());
                    }
                    return;
                }
            }
        }
        // 3.解析前处理, 留给子类实现
        preProcessXml(root);
        // 4.解析并注册bean定义
        parseBeanDefinitions(root, this.delegate);
        // 5.解析后处理, 留给子类实现
        postProcessXml(root);
    
        this.delegate = parent;
    }
    

最终,我们来到了解析 bean 定义的核心部分,这边会遍历 root 节点(正常为 节点)下的所有子节点,对子节点进行解析处理。

如果节点的命名空间是 Spring 默认的命名空间,则走 parseDefaultElement(ele, delegate) 方法进行解析,例如最常见的:。

如果节点的命名空间不是 Spring 默认的命名空间,也就是自定义命名空间,则走 delegate.parseCustomElement(ele) 方法进行解析,例如常见的: context:component-scan/、aop:aspectj-autoproxy/。