美文网首页IT@程序员猿媛
【Spring 笔记】BeanDefinition 装载与注册相

【Spring 笔记】BeanDefinition 装载与注册相

作者: 58bc06151329 | 来源:发表于2019-09-16 17:23 被阅读0次

    文前说明

    作为码农中的一员,需要不断的学习,我工作之余将一些分析总结和学习笔记写成博客与大家一起交流,也希望采用这种方式记录自己的学习之旅。

    本文仅供学习交流使用,侵权必删。
    不用于商业目的,转载请注明出处。

    1. 概述

    • IoC 容器的 初始化过程 可以分为三步骤:Resource 定位BeanDefinition 装载和解析BeanDefinition 注册
      • Resource 定位。用外部资源描述 Bean 对象,初始化 IoC 容器定位外部资源,具体实现在 【Spring 笔记】资源加载策略相关整理 中说明。
      • BeanDefinition 的装载和解析。载入 BeanDefinition,通过 BeanDefinitionReader 读取、解析 Resource 资源,将用户定义的 Bean 表示成 IoC 容器的内部数据结构 BeanDefinition
        • 在 IoC 容器内部维护着一个 BeanDefinition Map 的数据结构。
        • 配置文件中每一个 <bean> 都对应着一个 BeanDefinition 对象。
      • BeanDefinition 注册。向 IoC 容器注册解析好的 BeanDefinition,这个过程通过 BeanDefinitionRegistry 接口实现。
        • 将解析得到的 BeanDefinition 注入到一个 HashMap 容器中,IoC 容器通过这个 HashMap 来维护 BeanDefinition
          • 这个过程并没有完成依赖注入(Bean 创建),Bean 创建是发生在应用第一次调用 getBean() 方法,向容器索要 Bean 时。
          • 也可以通过设置预处理,即对某个 Bean 设置 lazyinit = false 属性,那么这个 Bean 的依赖注入就会在容器初始化时完成。
    ClassPathResource resource = new ClassPathResource("bean.xml");
    DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
    XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);
    reader.loadBeanDefinitions(resource);
    
    • 如上例所示,根据 XML 配置文件(bean.xml)创建 Resource 资源对象,bean.xml 文件中的内容是定义的 Bean 信息。
    • 创建 XmlBeanDefinitionReader 读取器,可用于装载 BeanDefinition
    • 通过 loadBeanDefinitions 方法,开始 BeanDefinition 的装载和注册进程,完成后的 BeanDefinition 放置在 IoC 容器中。
    IoC 初始化的流程

    2. 原理

    2.1 创建读取器

    • 通过 ResourceResourceLoader 两个接口的互相配合,已经定位了资源。
    • 创建 XmlBeanDefinitionReader 读取器,后续可用于读取配置文件的内容,并将其转换成 Ioc 容器内部的数据结构 BeanDefinition
    // XmlBeanDefinitionReader.java
    public XmlBeanDefinitionReader(BeanDefinitionRegistry registry) {
        super(registry);
    }
    
    // AbstractBeanDefinitionReader.java
    protected AbstractBeanDefinitionReader(BeanDefinitionRegistry registry) {
        Assert.notNull(registry, "BeanDefinitionRegistry must not be null");
        this.registry = registry;
        // Determine ResourceLoader to use.
        if (this.registry instanceof ResourceLoader) {
            this.resourceLoader = (ResourceLoader) this.registry;
        }   else {
            this.resourceLoader = new PathMatchingResourcePatternResolver();
        }
    
        // Inherit Environment if possible
        if (this.registry instanceof EnvironmentCapable) {
            this.environment = ((EnvironmentCapable) this.registry).getEnvironment();
        } else {
            this.environment = new StandardEnvironment();
        }
    }
    
    • 如果设置了 ResourceLoader 则用设置的,否则使用 PathMatchingResourcePatternResolver
    BeanDefinitionReader 体系
    • 以上接口或类归属于 spring-beans 项目。

    2.2 BeanDefinition 的装载和解析

    loadBeanDefinitions(加载过程)

    // XmlBeanDefinitionReader.java
    @Override
    public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
        return loadBeanDefinitions(new EncodedResource(resource));
    }
    
    • Resource 资源封装成 org.springframework.core.io.support.EncodedResource 对象,对 Resource 进行编码,保证内容读取的正确性。
    /**
     * 当前线程,正在加载的 EncodedResource 集合。
     */
    private final ThreadLocal<Set<EncodedResource>> resourcesCurrentlyBeingLoaded = new NamedThreadLocal<>("XML bean definition resources currently being loaded");
    
    public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
        Assert.notNull(encodedResource, "EncodedResource must not be null");
        if (logger.isTraceEnabled()) {
            logger.trace("Loading XML bean definitions from " + encodedResource);
        }
    
        // 1. 获取已经加载过的资源
        Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();
        if (currentResources == null) {
            currentResources = new HashSet<>(4);
            this.resourcesCurrentlyBeingLoaded.set(currentResources);
        }
        if (!currentResources.add(encodedResource)) { // 将当前资源加入记录中。如果已存在,抛出异常
            throw new BeanDefinitionStoreException("Detected cyclic loading of " + encodedResource + " - check your import definitions!");
        }
        try {
            // 2. 从 EncodedResource 获取封装的 Resource ,并从 Resource 中获取其中的 InputStream
            InputStream inputStream = encodedResource.getResource().getInputStream();
            try {
                InputSource inputSource = new InputSource(inputStream);
                if (encodedResource.getEncoding() != null) { // 设置编码
                    inputSource.setEncoding(encodedResource.getEncoding());
                }
                // 核心逻辑部分,执行加载 BeanDefinition
                return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
            } finally {
                inputStream.close();
            }
        } catch (IOException ex) {
            throw new BeanDefinitionStoreException("IOException parsing XML document from " + encodedResource.getResource(), ex);
        } finally {
            // 3. 从缓存中剔除该资源
            currentResources.remove(encodedResource);
            if (currentResources.isEmpty()) {
                this.resourcesCurrentlyBeingLoaded.remove();
            }
        }
    }
    
    • 方法的调用流程。
      • 步骤 1,获取已经加载过的资源,将 encodedResource 加入其中,如果 resourcesCurrentlyBeingLoaded 中已经存在该资源,则抛出 BeanDefinitionStoreException 异常。
        • 这样做可以避免一个 EncodedResource 正在加载还没加载完成时,又加载自身,从而导致 死循环
      • 步骤 2,从 encodedResource 获取封装的 Resource 资源,并从中获取相应的 InputStream,将其封装为 InputSource ,调用 doLoadBeanDefinitions() 方法,执行加载逻辑。
      • 步骤 3EncodedResource 加载完成后,从缓存中剔除。

    doLoadBeanDefinitions

    protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
            throws BeanDefinitionStoreException {
        try {
            // 1. 获取 XML Document 实例
            Document doc = doLoadDocument(inputSource, resource);
            // 2. 根据 Document 实例,注册 Bean 信息
            int count = registerBeanDefinitions(doc, resource);
            if (logger.isDebugEnabled()) {
                logger.debug("Loaded " + count + " bean definitions from " + resource);
            }
            return count;
        } 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);
        }
    }
    
    • 方法的调用流程。
      • 步骤 1,根据 XML 文件,获取 Document 实例。
      • 步骤 2,根据获取的 Document 实例,注册 Bean 信息。

    doLoadDocument

    protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception {
        return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler,
                getValidationModeForResource(resource), isNamespaceAware());
    }
    
    • 方法的调用流程。
      • 步骤 1, 调用 getValidationModeForResource() 方法,获取指定资源(XML)的验证模型
      • 步骤 2,调用 DocumentLoader#loadDocument() 方法,获取 XML Document 实例

    2.2.1 获取 XML 的验证模型

    • 分析获取 XML 文件的验证模式,以保证 XML 文件的正确性。

    DTD

    • DTD(Document Type Definition)即文档类型定义,为 XML 文件的验证机制,属于 XML 文件中组成的一部分。
      • DTD 是一种保证 XML 文档格式正确的有效验证方式,它定义了相关 XML 文档的元素、属性、排列方式、元素的内容类型以及元素的层次结构。
      • DTD 相当于 XML 中的词汇和语法,通过比较 XML 文件和 DTD 文件查看文档是否符合规范,元素和标签使用是否正确。
    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE beans PUBLIC  "-//SPRING//DTD BEAN//EN"  "http://www.springframework.org/dtd/spring-beans.dtd">
    
    • 在 Spring 中使用 DTD,在 Spring XML 文件头部声明。

    XSD

    • XSD(XML Schemas Definition)即 XML Schema 语言。
      • XML Schema 本身是一个 XML文档,使用 XML 语法,可以很方便的解析 XSD 文档。
      • 相对于 DTD,XSD 具有如下优势。
        • XML Schema 基于 XML ,没有专门的语法。
        • XML Schema 可以象其他 XML 文件一样解析和处理。
        • XML Schema 比 DTD 提供了更丰富的数据类型。
        • XML Schema 提供可扩充的数据模型。
        • XML Schema 支持综合命名空间。
        • XML Schema 支持属性组。

    getValidationModeForResource(获取验证模式)

    // XmlBeanDefinitionReader.java
    // 禁用验证模式
    public static final int VALIDATION_NONE = XmlValidationModeDetector.VALIDATION_NONE;
    // 自动获取验证模式
    public static final int VALIDATION_AUTO = XmlValidationModeDetector.VALIDATION_AUTO;
    // DTD 验证模式
    public static final int VALIDATION_DTD = XmlValidationModeDetector.VALIDATION_DTD;
    // XSD 验证模式
    public static final int VALIDATION_XSD = XmlValidationModeDetector.VALIDATION_XSD;
        
    /**
     * 验证模式。默认为自动模式。
     */
    private int validationMode = VALIDATION_AUTO;
        
    protected int getValidationModeForResource(Resource resource) {
        // 1. 获取指定的验证模式
        int validationModeToUse = getValidationMode();
        // 手动指定则直接返回
        if (validationModeToUse != VALIDATION_AUTO) {
            return validationModeToUse;
        }
        // 2. 自动获取验证模式
        int detectedMode = detectValidationMode(resource);
        if (detectedMode != VALIDATION_AUTO) {
            return detectedMode;
        }
        // 3. 使用 VALIDATION_XSD 做为默认
        // 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;
    }
    
    • 方法的调用流程。
      • 步骤 1,获取指定的验证模式(validationMode)。手动指定,则直接返回。
      • 步骤 2,自动获取验证模式。
    /**
    * XML 验证模式探测器
    */
    private final XmlValidationModeDetector validationModeDetector = new XmlValidationModeDetector();
    
    protected int detectValidationMode(Resource resource) {
        // 不可读,抛出 BeanDefinitionStoreException 异常
        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 inputStream;
        try {
            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);
        }
        // 2.1. 获取相应的验证模式
        try {
            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 void setValidationMode(int validationMode) {
        this.validationMode = validationMode;
    }
    
    public int getValidationMode() {
        return this.validationMode;
    }
    
    • 步骤 2.1,获取相应的验证模式。
    • 步骤 3,使用 VALIDATION_XSD 做为默认。

    2.2.1.1 XmlValidationModeDetector

    • org.springframework.util.xml.XmlValidationModeDetector 是 XML 验证模式探测器。

    detectValidationMode(自动获取验证模式)

    detectValidationMode 自动获取验证模式流程
    public int detectValidationMode(InputStream inputStream) throws IOException {
        // Peek into the file to look for DOCTYPE.
        BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
        try {
            // 是否为 DTD 校验模式。默认为,非 DTD 模式,即 XSD 模式
            boolean isDtdValidated = false;
            String content;
            // 1. 循环,逐行读取 XML 文件的内容
            while ((content = reader.readLine()) != null) {
                content = consumeCommentTokens(content);
                // 跳过注释
                if (this.inComment || !StringUtils.hasText(content)) {
                    continue;
                }
                // 2. 包含 DOCTYPE 为 DTD 模式
                if (hasDoctype(content)) {
                    isDtdValidated = true;
                    break;
                }
                // 3.  hasOpeningTag 方法会校验,如果这一行有 < ,并且 < 后面跟着的是字母,则返回 true 。
                if (hasOpeningTag(content)) {
                    // End of meaningful data...
                    break;
                }
            }
            // 返回 VALIDATION_DTD or VALIDATION_XSD 模式
            return (isDtdValidated ? VALIDATION_DTD : VALIDATION_XSD);
        } catch (CharConversionException ex) {
               
            // 4. 返回 VALIDATION_AUTO 模式
            // Choked on some character encoding...
            // Leave the decision up to the caller.
            return VALIDATION_AUTO;
        } finally {
            reader.close();
        }
    }
    
    private static final String DOCTYPE = "DOCTYPE";
    
    private boolean hasDoctype(String content) {
        return content.contains(DOCTYPE);
    }
    
    private boolean hasOpeningTag(String content) {
        if (this.inComment) {
            return false;
        }
        int openTagIndex = content.indexOf('<');
        return (openTagIndex > -1 // < 存在
                && (content.length() > openTagIndex + 1) // < 后面还有内容
                && Character.isLetter(content.charAt(openTagIndex + 1))); // < 后面的内容是字幕
    }
    
    private String consumeCommentTokens(String line) {
        if(!line.contains("<!--") && !line.contains("-->")) {//如果这一行不包含注释标记,则直接把这一行返回
            return line;
        } else {
            do {//包含标记的话,进入consume方法,进一步判断
                if((line = this.consume(line)) == null) {
                    return line;
                }
            } while(this.inComment || line.trim().startsWith("<!--"));
    
            return line;
        }
    }
    
    //如果包含头标记,则返回头标记以后的内容;如果包含结束标记,则返回标记后面的内容。
    private String consume(String line) {
        int index = this.inComment?this.endComment(line):this.startComment(line);
        return index == -1?null:line.substring(index);
    }
    
    • 方法的调用流程。
      • 步骤 1,通过读取 XML 文件的内容,进行自动判断。
      • 步骤 2,判断内容中如果包含有 " DOCTYPE ",则为 DTD 验证模式
      • 步骤 3,判断行包含 <,并且紧跟着字母,则为 XSD 验证模式
      • 步骤 4,发生 CharConversionException 异常,则为 VALIDATION_AUTO 模式

    2.2.2 获取 XML Document 实例

    DocumentLoader

    • 获取 Document 的策略,由接口 org.springframework.beans.factory.xml.DocumentLoader 定义。
    public interface DocumentLoader {
        Document loadDocument(
                InputSource inputSource, EntityResolver entityResolver,
                ErrorHandler errorHandler, int validationMode, boolean namespaceAware)
                throws Exception;
    }
    
    参数 说明
    inputSource 加载 Document 的 Resource 资源。
    entityResolver 解析文件的解析器。
    errorHandler 处理加载 Document 对象的过程的错误。
    validationMode 验证模式。
    namespaceAware 命名空间支持。如果提供对 XML 名称空间的支持,则值为 true 。

    DefaultDocumentLoader

    • DocumentLoader 的默认实现类 org.springframework.beans.factory.xml.DefaultDocumentLoader
    /**
     * Load the {@link Document} at the supplied {@link InputSource} using the standard JAXP-configured
     * XML parser.
     */
    @Override
    public Document loadDocument(InputSource inputSource, EntityResolver entityResolver,
            ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception {
        // 1. 创建 DocumentBuilderFactory
        DocumentBuilderFactory factory = createDocumentBuilderFactory(validationMode, namespaceAware);
        if (logger.isTraceEnabled()) {
            logger.trace("Using JAXP provider [" + factory.getClass().getName() + "]");
        }
        // 2. 创建 DocumentBuilder
        DocumentBuilder builder = createDocumentBuilder(factory, entityResolver, errorHandler);
        // 3. 解析 XML InputSource 返回 Document 对象
        return builder.parse(inputSource);
    }
    
    • 方法的调用流程。
      • 步骤 1,创建 javax.xml.parsers.DocumentBuilderFactory 对象。
    private static final String SCHEMA_LANGUAGE_ATTRIBUTE = "http://java.sun.com/xml/jaxp/properties/schemaLanguage";
    /**
     * JAXP attribute value indicating the XSD schema language.
     */
    private static final String XSD_SCHEMA_LANGUAGE = "http://www.w3.org/2001/XMLSchema";
    protected DocumentBuilderFactory createDocumentBuilderFactory(int validationMode, boolean namespaceAware)
            throws ParserConfigurationException {
        // 创建 DocumentBuilderFactory
        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
        factory.setNamespaceAware(namespaceAware); // 设置命名空间支持
        if (validationMode != XmlValidationModeDetector.VALIDATION_NONE) {
            factory.setValidating(true); // 开启校验
            // XSD 模式下,设置 factory 的属性
            if (validationMode == XmlValidationModeDetector.VALIDATION_XSD) {
                // Enforce namespace aware for XSD...
                factory.setNamespaceAware(true); // XSD 模式下,强制设置命名空间支持
                // 设置 SCHEMA_LANGUAGE_ATTRIBUTE
                try {
                    factory.setAttribute(SCHEMA_LANGUAGE_ATTRIBUTE, XSD_SCHEMA_LANGUAGE);
                } catch (IllegalArgumentException ex) {
                    ParserConfigurationException pcex = new ParserConfigurationException(
                            "Unable to validate using XSD: Your JAXP provider [" + factory +
                            "] does not support XML Schema. Are you running on Java 1.4 with Apache Crimson? " +
                            "Upgrade to Apache Xerces (or Java 1.5) for full XSD support.");
                    pcex.initCause(ex);
                    throw pcex;
                }
            }
        }
        return factory;
    }
    
    • 步骤 2,创建 javax.xml.parsers.DocumentBuilder 对象。
    protected DocumentBuilder createDocumentBuilder(DocumentBuilderFactory factory,
            @Nullable EntityResolver entityResolver, @Nullable ErrorHandler errorHandler)
            throws ParserConfigurationException {
        // 创建 DocumentBuilder 对象
        DocumentBuilder docBuilder = factory.newDocumentBuilder();
        // 2.1. 设置 EntityResolver 属性
        if (entityResolver != null) {
            docBuilder.setEntityResolver(entityResolver);
        }
        // 设置 ErrorHandler 属性
        if (errorHandler != null) {
            docBuilder.setErrorHandler(errorHandler);
        }
        return docBuilder;
    }
    
    • 步骤 2.1,设置 DocumentBuilderEntityResolver 属性。
    • 步骤 3,通过 javax.xml 库进行解析,从 XML 中获取 Document 对象。

    2.2.3 EntityResolver(解析器)

    • 返回指定的解析器,如果没有指定,则构造一个未指定的默认解析器。
    // XmlBeanDefinitionReader.java
    /**
     * EntityResolver 解析器
     */
    @Nullable
    private EntityResolver entityResolver;
    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;
    }
    
    • 如果 ResourceLoader 不为 null,则根据指定的 ResourceLoader 创建一个 ResourceEntityResolver 对象。
    • 如果 ResourceLoader 为 null ,则创建 一个 DelegatingEntityResolver 对象。
      • Resolver 委托给默认的 BeansDtdResolverPluggableSchemaResolver
    • EntityResolver 的作用在于,通过实现它,应用可以自定义如何寻找 验证文件 的逻辑。
      • 解析一个 XML,首先会读取该 XML 文档上的声明,根据声明去寻找相应的 DTD 定义,以便对文档进行一个验证。默认的寻找规则,即通过网络下载相应的 DTD 声明,并进行认证。下载过程很慢,而且当网络中断或不可用时会报错。
      • EntityResolver 可以提供一个如何寻找 DTD 声明的方法由程序实现(比如将 DTD 文件放到项目中),实现时直接读取并返回即可,这样避免了通过网络寻找相应的声明。
    EntityResolver 体系
    • org.springframework.beans.factory.xm.BeansDtdResolverSpring Bean dtd 解码器,用来从 classpath 或者 jar 文件中加载 DTD。
    • org.springframework.beans.factory.xml.PluggableSchemaResolver,读取 classpath 下的所有 " META-INF/spring.schemas " 形成一个 namespaceURI 与 Schema 文件地址的 map 。
    • org.springframework.beans.factory.xml.DelegatingEntityResolver,分别代理 DTD 的 BeansDtdResolver 和 xml schemas 的 PluggableSchemaResolver
    • org.springframework.beans.factory.xml.ResourceEntityResolver,通过 ResourceLoader 解析实体的引用。
    public interface EntityResolver {
        public abstract InputSource resolveEntity (String publicId, String systemId)
            throws SAXException, IOException;
    }
    
    参数 说明
    publicId 被引用的外部实体的公共标识符,如果没有提供,则返回 null 。
    systemId 被引用的外部实体的系统标识符。

    XSD 验证模式

    DTD 验证模式

    2.2.3.1 BeansDtdResolver

    • Spring Bean dtd 解码器。对 systemId 进行了简单的校验(从最后一个 / 开始,内容中是否包含 spring-beans),构造一个 InputSource 对象,并设置 publicId、systemId 属性,然后返回。
    /**
     * DTD 文件的后缀
     */
    private static final String DTD_EXTENSION = ".dtd";
    /**
     * Spring Bean DTD 的文件名
     */
    private static final String DTD_NAME = "spring-beans";
    
    @Override
    @Nullable
    public InputSource resolveEntity(String publicId, @Nullable String systemId) throws IOException {
        if (logger.isTraceEnabled()) {
            logger.trace("Trying to resolve XML entity with public ID [" + publicId +
                    "] and system ID [" + systemId + "]");
        }
        // 必须以 .dtd 结尾
        if (systemId != null && systemId.endsWith(DTD_EXTENSION)) {
            // 获取最后一个 / 的位置
            int lastPathSeparator = systemId.lastIndexOf('/');
            // 获取 spring-beans 的位置
            int dtdNameStart = systemId.indexOf(DTD_NAME, lastPathSeparator);
            if (dtdNameStart != -1) { // 找到
                String dtdFile = DTD_NAME + DTD_EXTENSION;
                if (logger.isTraceEnabled()) {
                    logger.trace("Trying to locate [" + dtdFile + "] in Spring jar on classpath");
                }
                try {
                    // 创建 ClassPathResource 对象
                    Resource resource = new ClassPathResource(dtdFile, getClass());
                    // 创建 InputSource 对象,并设置 publicId、systemId 属性
                    InputSource source = new InputSource(resource.getInputStream());
                    source.setPublicId(publicId);
                    source.setSystemId(systemId);
                    if (logger.isTraceEnabled()) {
                        logger.trace("Found beans DTD [" + systemId + "] in classpath: " + dtdFile);
                    }
                    return source;
                }
                catch (IOException ex) {
                    if (logger.isDebugEnabled()) {
                        logger.debug("Could not resolve beans DTD [" + systemId + "]: not found in classpath", ex);
                    }
                }
            }
        }
    
        // 使用默认行为,从网络上下载
        // Use the default behavior -> download from website or wherever.
        return null;
    }
    

    2.2.3.2 PluggableSchemaResolver

    @Nullable
    private final ClassLoader classLoader;
    
    /**
     * Schema 文件地址
     */
    private final String schemaMappingsLocation;
    
    /** Stores the mapping of schema URL -> local schema path. */
    @Nullable
    private volatile Map<String, String> schemaMappings; // namespaceURI 与 Schema 文件地址的映射集合
    
    @Override
    @Nullable
    public InputSource resolveEntity(String publicId, @Nullable String systemId) throws IOException {
        if (logger.isTraceEnabled()) {
            logger.trace("Trying to resolve XML entity with public id [" + publicId +
                    "] and system id [" + systemId + "]");
        }
    
        if (systemId != null) {
            // 获得 Resource 所在位置
            String resourceLocation = getSchemaMappings().get(systemId);
            if (resourceLocation != null) {
                // 创建 ClassPathResource
                Resource resource = new ClassPathResource(resourceLocation, this.classLoader);
                try {
                    // 创建 InputSource 对象,并设置 publicId、systemId 属性
                    InputSource source = new InputSource(resource.getInputStream());
                    source.setPublicId(publicId);
                    source.setSystemId(systemId);
                    if (logger.isTraceEnabled()) {
                        logger.trace("Found XML schema [" + systemId + "] in classpath: " + resourceLocation);
                    }
                    return source;
                }
                catch (FileNotFoundException ex) {
                    if (logger.isDebugEnabled()) {
                        logger.debug("Could not find XML schema [" + systemId + "]: " + resource, ex);
                    }
                }
            }
        }
        return null;
    }
    
    • 获取一个映射表(systemId 与其在本地的对照关系)。
    private Map<String, String> getSchemaMappings() {
        Map<String, String> schemaMappings = this.schemaMappings;
        // 双重检查锁,实现 schemaMappings 单例
        if (schemaMappings == null) {
            synchronized (this) {
                schemaMappings = this.schemaMappings;
                if (schemaMappings == null) {
                    if (logger.isTraceEnabled()) {
                        logger.trace("Loading schema mappings from [" + this.schemaMappingsLocation + "]");
                    }
                    try {
                        // 以 Properties 的方式,读取 schemaMappingsLocation
                        Properties mappings = PropertiesLoaderUtils.loadAllProperties(this.schemaMappingsLocation, this.classLoader);
                        if (logger.isTraceEnabled()) {
                            logger.trace("Loaded schema mappings: " + mappings);
                        }
                        // 将 mappings 初始化到 schemaMappings 中
                        schemaMappings = new ConcurrentHashMap<>(mappings.size());
                        CollectionUtils.mergePropertiesIntoMap(mappings, schemaMappings);
                        this.schemaMappings = schemaMappings;
                    } catch (IOException ex) {
                        throw new IllegalStateException(
                                "Unable to load schema mappings from location [" + this.schemaMappingsLocation + "]", ex);
                    }
                }
            }
        }
        return schemaMappings;
    }
    
    • 根据传入的 systemId 获取该 systemId 在本地的路径 resourceLocation 。
    • 根据 resourceLocation ,构造 InputSource 对象。

    2.2.3.3 DelegatingEntityResolver

    @Override
    @Nullable
    public InputSource resolveEntity(String publicId, @Nullable String systemId) throws SAXException, IOException {
        if (systemId != null) {
            // DTD 模式
            if (systemId.endsWith(DTD_SUFFIX)) {
                return this.dtdResolver.resolveEntity(publicId, systemId);
            // XSD 模式
            } else if (systemId.endsWith(XSD_SUFFIX)) {
                return this.schemaResolver.resolveEntity(publicId, systemId);
            }
        }
        return null;
    }
    
    • 如果是 DTD 验证模式,则使用 BeansDtdResolver 进行解析
    • 如果是 XSD 验证模式,则使用 PluggableSchemaResolver 进行解析。

    2.2.3.4 ResourceEntityResolver

    private final ResourceLoader resourceLoader;
    
    @Override
    @Nullable
    public InputSource resolveEntity(String publicId, @Nullable String systemId) throws SAXException, IOException {
        // 1. 调用父类的方法,进行解析
        InputSource source = super.resolveEntity(publicId, systemId);
        // 解析失败,resourceLoader 进行解析
        if (source == null && systemId != null) {
            // 获得 resourcePath ,即 Resource 资源地址
            String resourcePath = null;
            try {
                String decodedSystemId = URLDecoder.decode(systemId, "UTF-8"); // 使用 UTF-8 ,解码 systemId
                String givenUrl = new URL(decodedSystemId).toString(); // 转换成 URL 字符串
                // 解析文件资源的相对路径(相对于系统根路径)
                String systemRootUrl = new File("").toURI().toURL().toString();
                // Try relative to resource base if currently in system root.
                if (givenUrl.startsWith(systemRootUrl)) {
                    resourcePath = givenUrl.substring(systemRootUrl.length());
                }
            } catch (Exception ex) {
                // Typically a MalformedURLException or AccessControlException.
                if (logger.isDebugEnabled()) {
                    logger.debug("Could not resolve XML entity [" + systemId + "] against system root URL", ex);
                }
                // No URL (or no resolvable URL) -> try relative to resource base.
                resourcePath = systemId;
            }
            if (resourcePath != null) {
                if (logger.isTraceEnabled()) {
                    logger.trace("Trying to locate XML entity [" + systemId + "] as resource [" + resourcePath + "]");
                }
                // 获得 Resource 资源
                Resource resource = this.resourceLoader.getResource(resourcePath);
                // 创建 InputSource 对象
                source = new InputSource(resource.getInputStream());
                // 设置 publicId 和 systemId 属性
                source.setPublicId(publicId);
                source.setSystemId(systemId);
                if (logger.isDebugEnabled()) {
                    logger.debug("Found XML entity [" + systemId + "]: " + resource);
                }
            }
        }
        return source;
    }
    
    • 方法的调用流程。
      • 步骤 1,调用父类的方法,进行解析。
      • 步骤 2,如果失败,使用 resourceLoader ,尝试读取 systemId 对应的 Resource 资源。

    2.2.3.5 自定义 EntityResolver

    • 实现 EntityResolver 接口,并使用 setEntityResolver() 方法,向 SAX 驱动器注册一个 EntityResolver 实例。
    public class MyResolver implements EntityResolver {
        @Override
        public InputSource resolveEntity(String publicId, String systemId) {
            if (systemId.equals("http://www.myhost.com/today")) {
                MyReader reader = new MyReader();
                return new InputSource(reader);
            } else {
                // use the default behaviour
                return null;
            }
        }
    }
    
    // XmlBeanDefinitionReader.java
    public void setEntityResolver(EntityResolver entityResolver) {
       this.entityResolver = entityResolver;
    }
    protected EntityResolver getEntityResolver() {
        if (this.entityResolver == null) {
            ResourceLoader resourceLoader = this.getResourceLoader();
            if (resourceLoader != null) {
                this.entityResolver = new ResourceEntityResolver(resourceLoader);
            } else {
                this.entityResolver = new DelegatingEntityResolver(this.getBeanClassLoader());
            }
        }
    
        return this.entityResolver;
    }
    

    2.3 BeanDefinition 的注册

    • 获取 XML Document 对象后,会根据该对象和 Resource 资源对象调用 XmlBeanDefinitionReader#registerBeanDefinitions() 方法,开始注册 BeanDefinition
    // AbstractBeanDefinitionReader.java
    private final BeanDefinitionRegistry registry;
    
    // XmlBeanDefinitionReader.java
    public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
        // 1. 创建 BeanDefinitionDocumentReader 对象
        BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
        // 2. 获取已注册的 BeanDefinition 数量
        int countBefore = getRegistry().getBeanDefinitionCount();
        // 3. 创建 XmlReaderContext 对象
        // 4. 注册 BeanDefinition
        documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
        // 计算新注册的 BeanDefinition 数量
        return getRegistry().getBeanDefinitionCount() - countBefore;
    }
    
    • 方法的调用流程。
      • 步骤 1,实例化 BeanDefinitionDocumentReader 对象。
      • 步骤 2,获取已注册的 BeanDefinition 数量。
      • 步骤 3,创建 XmlReaderContext 对象。
      • 步骤 4,读取 XML 元素,注册 BeanDefinition

    2.3.1 createBeanDefinitionDocumentReader

    /**
     * documentReader 的类
     *
     * @see #createBeanDefinitionDocumentReader() 
     */
    private Class<? extends BeanDefinitionDocumentReader> documentReaderClass = DefaultBeanDefinitionDocumentReader.class;
    
    protected BeanDefinitionDocumentReader createBeanDefinitionDocumentReader() {
        return BeanUtils.instantiateClass(this.documentReaderClass);
    }
    

    2.3.1.1 BeanDefinitionDocumentReader

    • 从给定的 Document 对象中解析定义的 BeanDefinition 并注册到注册表中。
    public interface BeanDefinitionDocumentReader {
    
        /**
         * Read bean definitions from the given DOM document and
         * register them with the registry in the given reader context.
         * @param doc the DOM document
         * @param readerContext the current context of the reader
         * (includes the target registry and the resource being parsed)
         * @throws BeanDefinitionStoreException in case of parsing errors
         */
        void registerBeanDefinitions(Document doc, XmlReaderContext readerContext)
                throws BeanDefinitionStoreException;
    }
    
    参数 说明
    doc 方法参数 待解析的 Document 对象。
    readerContext 解析器的当前上下文,包括目标注册表和被解析的资源。根据 Resource 创建。

    2.3.1.2 DefaultBeanDefinitionDocumentReader

    • BeanDefinitionDocumentReader 接口的默认实现。
    @Nullable
    private XmlReaderContext readerContext;
    
    @Nullable
    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;
        // 获得 XML Document Root Element
        // 执行注册 BeanDefinition
        doRegisterBeanDefinitions(doc.getDocumentElement());
    }
    
    /**
     * Register each bean definition within the given root {@code <beans/>} element.
     */
    @SuppressWarnings("deprecation")  // for Environment.acceptsProfiles(String...)
    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 对象
        BeanDefinitionParserDelegate parent = this.delegate;
        // 1. 创建 BeanDefinitionParserDelegate 对象,并进行设置到 delegate
        this.delegate = createDelegate(getReaderContext(), root, parent);
        // 2. 检查 <beans /> 根标签的命名空间是否为空,或者是 http://www.springframework.org/schema/beans
        if (this.delegate.isDefaultNamespace(root)) {
            // 2.1. 处理 profile 属性。可参见《Spring3自定义环境配置 <beans profile="">》http://nassir.iteye.com/blog/1535799
            String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
            if (StringUtils.hasText(profileSpec)) {
                // 2.2. 使用分隔符切分,可能有多个 profile 。
                String[] specifiedProfiles = StringUtils.tokenizeToStringArray(
                        profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
                // 2.3. 如果所有 profile 都无效,则不进行注册
                // 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;
                }
            }
        }
    
        // 3. 解析前处理
        preProcessXml(root);
        // 4. 解析
        parseBeanDefinitions(root, this.delegate);
        // 5. 解析后处理
        postProcessXml(root);
    
        // 设置 delegate 回老的 BeanDefinitionParserDelegate 对象
        this.delegate = parent;
    }
    
    • 方法的调用流程。
      • 步骤 1,创建 BeanDefinitionParserDelegate 对象,它负责解析 BeanDefinition
      • 步骤 2,检查 <beans/> 标签的命名空间是否为空,或者是 http://www.springframework.org/schema/beans
        • 步骤 2.1,判断 <beans/> 上是否配置了 profile 属性。
        • 步骤 2.2,使用分隔符切分,可能存在多个 profile。
        • 步骤 2.3,如果所有 profile 都无效,则 return 不进行注册。
      • 步骤 3,解析前处理。
      • 步骤 4,解析逻辑处理。
      • 步骤 5,解析后处理。

    parseBeanDefinitions(解析逻辑处理)

    protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
        // 1. 如果根节点使用默认命名空间,执行默认解析
        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);
                    }
                }
            }
        // 2. 如果根节点非默认命名空间,执行自定义解析
        } else {
            delegate.parseCustomElement(root);
        }
    }
    
    • 方法的调用流程。
      • 步骤 1,如果根节点或子节点使用默认命名空间,执行默认解析(例如:<bean id="studentService" class="org.springframework.core.StudentService" />)。
    private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
        if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) { // import
            importBeanDefinitionResource(ele);
        } else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) { // alias
            processAliasRegistration(ele);
        } else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) { // bean
            processBeanDefinition(ele, delegate);
        } else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) { // beans
            // recurse
            doRegisterBeanDefinitions(ele);
        }
    }
    
    • 步骤 2,如果根节点或子节点不使用默认命名空间,执行自定义解析(例如:<tx:annotation-driven>)。

    • 步骤 1 和 步骤 2 中的标签解析可以查看 【Spring 笔记】Bean 解析相关整理

    processBeanDefinition

    • 完成 Bean 标签解析的核心工作。
    protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
        // 进行 bean 元素解析。
        // 如果解析成功,则返回 BeanDefinitionHolder 对象。而 BeanDefinitionHolder 为 name 和 alias 的 BeanDefinition 对象
        // 如果解析失败,则返回 null 。
        BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
        if (bdHolder != null) {
            // 进行自定义标签处理
            bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
            try {
                // 进行 BeanDefinition 的注册
                // Register the final decorated instance.
                BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
            } catch (BeanDefinitionStoreException ex) {
                getReaderContext().error("Failed to register bean definition with name '" +
                        bdHolder.getBeanName() + "'", ele, ex);
            }
            // 发出响应事件,通知相关的监听器,已完成该 Bean 标签的解析。
            // Send registration event.
            getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
        }
    }
    
    • 然后调用 BeanDefinitionReaderUtils#registerBeanDefinition() 方法进行注册。

    2.3.2 BeanDefinitionReaderUtils

    registerBeanDefinition

    // BeanDefinitionReaderUtils.java
    public static void registerBeanDefinition(
            BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)
            throws BeanDefinitionStoreException {
    
        // 1. 注册 beanName
        // Register bean definition under primary name.
        String beanName = definitionHolder.getBeanName();
        registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());
    
        // 2. 注册 alias
        // Register aliases for bean name, if any.
        String[] aliases = definitionHolder.getAliases();
        if (aliases != null) {
            for (String alias : aliases) {
                registry.registerAlias(beanName, alias);
            }
        }
    }
    
    • 方法的调用流程。
      • 步骤 1,通过 beanName 注册 BeanDefinition
      • 步骤 2,注册别名 aliasbeanName 的映射。

    2.3.2.1 BeanDefinitionRegistry

    • BeanDefinition 的注册,由接口 org.springframework.beans.factory.support.BeanDefinitionRegistry 定义。

    • 通过 beanName 注册 BeanDefinition
    // DefaultListableBeanFactory.java
    /** Whether to allow re-registration of a different definition with the same name. */
    private boolean allowBeanDefinitionOverriding = true;
    
    /** Map of bean definition objects, keyed by bean name. */
    private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>(256);
    /** List of bean definition names, in registration order. */
    private volatile List<String> beanDefinitionNames = new ArrayList<>(256);
    /** List of names of manually registered singletons, in registration order. */
    private volatile Set<String> manualSingletonNames = new LinkedHashSet<>(16);
    /** Cached array of bean definition names in case of frozen configuration. */
    @Nullable
    private volatile String[] frozenBeanDefinitionNames;
    
    @Override
    public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
            throws BeanDefinitionStoreException {
    
        // 校验 beanName 与 beanDefinition 非空
        Assert.hasText(beanName, "Bean name must not be empty");
        Assert.notNull(beanDefinition, "BeanDefinition must not be null");
    
        // 1. 校验 BeanDefinition 。
        // 这是注册前的最后一次校验了,主要是对属性 methodOverrides 进行校验。
        if (beanDefinition instanceof AbstractBeanDefinition) {
            try {
                ((AbstractBeanDefinition) beanDefinition).validate();
            } catch (BeanDefinitionValidationException ex) {
                throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,
                        "Validation of bean definition failed", ex);
            }
        }
    
        // 2. 从缓存中获取指定 beanName 的 BeanDefinition
        BeanDefinition existingDefinition = this.beanDefinitionMap.get(beanName);
        // 3. 如果已经存在
        if (existingDefinition != null) {
            // 如果存在但是不允许覆盖,抛出异常
            if (!isAllowBeanDefinitionOverriding()) {
                throw new BeanDefinitionOverrideException(beanName, beanDefinition, existingDefinition);
            // 覆盖 beanDefinition 大于 被覆盖的 beanDefinition 的 ROLE ,打印 info 日志
            } else if (existingDefinition.getRole() < beanDefinition.getRole()) {
                // e.g. was ROLE_APPLICATION, now overriding with ROLE_SUPPORT or ROLE_INFRASTRUCTURE
                if (logger.isInfoEnabled()) {
                    logger.info("Overriding user-defined bean definition for bean '" + beanName +
                            "' with a framework-generated bean definition: replacing [" +
                            existingDefinition + "] with [" + beanDefinition + "]");
                }
            // 覆盖 beanDefinition 与 被覆盖的 beanDefinition 不相同,打印 debug 日志
            } else if (!beanDefinition.equals(existingDefinition)) {
                if (logger.isDebugEnabled()) {
                    logger.debug("Overriding bean definition for bean '" + beanName +
                            "' with a different definition: replacing [" + existingDefinition +
                            "] with [" + beanDefinition + "]");
                }
            // 其它,打印 debug 日志
            } else {
                if (logger.isTraceEnabled()) {
                    logger.trace("Overriding bean definition for bean '" + beanName +
                            "' with an equivalent definition: replacing [" + existingDefinition +
                            "] with [" + beanDefinition + "]");
                }
            }
            // 允许覆盖,直接覆盖原有的 BeanDefinition 到 beanDefinitionMap 中。
            this.beanDefinitionMap.put(beanName, beanDefinition);
        // 4. 如果未存在
        } else {
            // 检测创建 Bean 阶段是否已经开启,如果开启了则需要对 beanDefinitionMap 进行并发控制
            if (hasBeanCreationStarted()) {
                // beanDefinitionMap 为全局变量,避免并发情况
                // Cannot modify startup-time collection elements anymore (for stable iteration)
                synchronized (this.beanDefinitionMap) {
                    // 添加到 BeanDefinition 到 beanDefinitionMap 中。
                    this.beanDefinitionMap.put(beanName, beanDefinition);
                    // 添加 beanName 到 beanDefinitionNames 中
                    List<String> updatedDefinitions = new ArrayList<>(this.beanDefinitionNames.size() + 1);
                    updatedDefinitions.addAll(this.beanDefinitionNames);
                    updatedDefinitions.add(beanName);
                    this.beanDefinitionNames = updatedDefinitions;
                    // 从 manualSingletonNames 移除 beanName
                    if (this.manualSingletonNames.contains(beanName)) {
                        Set<String> updatedSingletons = new LinkedHashSet<>(this.manualSingletonNames);
                        updatedSingletons.remove(beanName);
                        this.manualSingletonNames = updatedSingletons;
                    }
                }
            } else {
                // Still in startup registration phase
                // 添加到 BeanDefinition 到 beanDefinitionMap 中。
                this.beanDefinitionMap.put(beanName, beanDefinition);
                // 添加 beanName 到 beanDefinitionNames 中
                this.beanDefinitionNames.add(beanName);
                // 从 manualSingletonNames 移除 beanName
                this.manualSingletonNames.remove(beanName);
            }
            
            this.frozenBeanDefinitionNames = null;
        }
    
        // 5. 重新设置 beanName 对应的缓存
        if (existingDefinition != null || containsSingleton(beanName)) {
            resetBeanDefinition(beanName);
        }
    }
    
    • 方法的调用流程。
      • 步骤 1,对 BeanDefinition 进行校验,主要对 AbstractBeanDefinitionmethodOverrides 属性进行校验。
      • 步骤 2,根据 beanName 从缓存中获取 BeanDefinition 对象。
      • 步骤 3, 如果缓存中存在,则根据 allowBeanDefinitionOverriding 标志来判断是否允许覆盖。如果允许则直接覆盖。否则,抛出 BeanDefinitionStoreException 异常。
      • 步骤 4,若缓存中没有指定 beanName 的 BeanDefinition,则判断当前阶段是否已经开始了 Bean 的创建阶段,如果是,则需要对 beanDefinitionMap 进行加锁控制并发问题,否则直接设置即可。
      • 步骤 5,若缓存中存在该 beanName 或者单例 bean 集合中存在该 beanName ,则调用 resetBeanDefinition() 方法,重置 BeanDefinition 缓存。

    • registerAlias() 方法,注册 alias 和 beanName 的映射关系。
    // SimpleAliasRegistry.java
    private final Map<String, String> aliasMap = new ConcurrentHashMap<>(16);
    
    @Override
    public void registerAlias(String name, String alias) {
        // 校验 name 、 alias
        Assert.hasText(name, "'name' must not be empty");
        Assert.hasText(alias, "'alias' must not be empty");
        synchronized (this.aliasMap) {
            // name == alias 则去掉alias
            if (alias.equals(name)) {
                this.aliasMap.remove(alias);
                if (logger.isDebugEnabled()) {
                    logger.debug("Alias definition '" + alias + "' ignored since it points to same name");
                }
            } else {
                // 获取 alias 已注册的 beanName
                String registeredName = this.aliasMap.get(alias);
                // 已存在
                if (registeredName != null) {
                    // 相同,则 return ,无需重复注册
                    if (registeredName.equals(name)) {
                        // An existing alias - no need to re-register
                        return;
                    }
                    // 不允许覆盖,则抛出 IllegalStateException 异常
                    if (!allowAliasOverriding()) {
                        throw new IllegalStateException("Cannot define alias '" + alias + "' for name '" +
                                name + "': It is already registered for name '" + registeredName + "'.");
                    }
                    if (logger.isDebugEnabled()) {
                        logger.debug("Overriding alias '" + alias + "' definition for registered name '" +
                                registeredName + "' with new target name '" + name + "'");
                    }
                }
                // 校验,是否存在循环指向
                checkForAliasCircle(name, alias);
                // 注册 alias
                this.aliasMap.put(alias, name);
                if (logger.isTraceEnabled()) {
                    logger.trace("Alias definition '" + alias + "' registered for name '" + name + "'");
                }
            }
        }
    }
    
    • checkForAliasCircle() 对别名进行循环检测。
      • 假设 name、alias 分别为 X 和 Y,则构成 (X,Y) 的映射,如果集合中存在(A,X)、(Y,A)的映射,则意味着出现循环指向的情况,抛出 IllegalStateException 异常。
    protected void checkForAliasCircle(String name, String alias) {
        if (hasAlias(alias, name)) {
            throw new IllegalStateException("Cannot register alias '" + alias +
                    "' for name '" + name + "': Circular reference - '" +
                    name + "' is a direct or indirect alias for '" + alias + "' already");
        }
    }
    public boolean hasAlias(String name, String alias) {
        for (Map.Entry<String, String> entry : this.aliasMap.entrySet()) {
            String registeredName = entry.getValue();
            if (registeredName.equals(name)) {
                String registeredAlias = entry.getKey();
                if (registeredAlias.equals(alias) || hasAlias(registeredAlias, alias)) {
                    return true;
                }
            }
        }
        return false;
    }
    

    3. 总结

    • 至此 BeanDefinition 基于 beanName 和 alias 的维度,都已经注入到缓存中。
      • 整个 IoC 的初始化过程就已经完成,从 Bean 资源的定位,转换为 Document 对象,接着对其进行解析,最后注册到 IoC 容器中。
      • IoC 容器中已经建立了整个 Bean 的配置信息,这些 Bean 可以被检索、使用、维护,他们是控制反转的基础,是后面注入 Bean 的依赖。

    相关文章

      网友评论

        本文标题:【Spring 笔记】BeanDefinition 装载与注册相

        本文链接:https://www.haomeiwen.com/subject/wfugyctx.html