美文网首页spring源码解读系列
spring源码系列-容器之XmlBeanFactory

spring源码系列-容器之XmlBeanFactory

作者: 默写流年 | 来源:发表于2019-07-26 16:13 被阅读39次

    本文开始,将开始spring 源码解读系列。该系列将会分析下spring的核心功能 IOC/DI,AOP,Transaction,MVC 的实现。通过分析系列源码,理清楚spring的运行流程,不至于在工作中遇到相关问题无从下手,同时学习spring中优秀的设计方式,对自己也是颇有裨益的。而本文作为spring源码的入门篇,着重介绍spring的基础容器的实现。本文大致分为以下几个板块:

    1. 容器
      1.1 bean 容器的基本用法
      1.2 bean容器的种类及区别
    2. XmlBeanFactory 源码解读
      2.1 加载配置文件
      2.2 验证配置文件
      2.2.1 DTD
      2.2.2 XSD
      2.3 将InputSource解析为标准xml Document
      2.4 解析配置文件
    3. XmlBeanFactory 过期替代方法
      总结

    1. 容器

    容器是指用以容纳物料并以壳体为主的基本装置。生活中杯子,收纳箱都是容器。在java的世界里就是用来存放各种数据的装置-集合,而在spring里,则是存放各种bean的装置-BeanFactory。

    1.1 bean 容器的基本用法

    我们先来回顾一下spring容器的基本用法,尽管大家都知道。

    1. 定义一个bean
    2. 在配置文件配置该bean
    3. 初始化bean容器并获取bean

    public class Person {
    
        private String name;
    
        public Person(String name) {
            this.name = name;
        }
    
        public String getName() {
            return name;
        }
    
    
        @Override
        public String toString() {
            return "Person{" +
                    "name='" + name + '\'' +
                    '}';
        }
    }
    
        <bean id="user" class="example.Person">
            <constructor-arg name="name" value="hello person"/>
        </bean>
    
        public static void main(String[] args) {
            BeanFactory beanFactory=new XmlBeanFactory(new ClassPathResource("spring-config.xml"));
            System.out.println(beanFactory.getBean("user"));
        }
    

    1.2 bean容器的种类及区别

    spring中最常用的bean容器为BeanFactory及ApplicationContext。同样都是bean的容器,两者有什么区别呢?

    • ApplicationContext和BeanFactory都提供基础的Bean容器的功能
    • ApplicationContext除了包含BeanFactory所有功能外,还包含了更多的扩展功能。如Bean后置处理器,消息国际化,事件广播等。我们熟悉的Aop,事物也是基于ApplicationContentext实现的。

    2. XmlBeanFactory 源码解读

    BeanFactory作为容器的基础,定义了容器的基础行为。而XmlBeanFactory作为BeanFactory的常用实现之一,除了实现了BeanFactory的方法之外,还提供了从Xml解析配置文件,装载Bean的方法。试想如果让我们自己实现一个Bean的容器,我们会怎么去实现呢。编写一个指定规则的配置文件,加载这个配置文件,解析配置文件并存储。其实spring也做了类似的功能,只不过要比我们设想的要复杂。下面我们按照spring XmlBeanFacotry的初始化步骤来读下源码,大致分为以下步骤:

    1. 加载配置文件得到输入流
    2. 验证配置文件
    3. 读取配置文件得到Dom
    4. 解析Dom得到Bean抽象定义
    5. 存储解析后的数据
      大致时序图如下(图丑凑合看吧( ╯□╰ )):
      image.png

    2.1 加载配置文件

    我们是通过new XmlBeanFactory的方式构造XmlBeanFactory,跟踪其构造方法

        /**
         * 根据Resource实例化XmlBeanFactory
         * @param resource
         * @throws BeansException
         */
        public XmlBeanFactory(Resource resource) throws BeansException {
            this(resource, null);
        }
    
        /**
         * 根据Resource和BeanFactory实例化XmlBeanFactory
         * @param resource
         * @param parentBeanFactory
         * @throws BeansException
         */
        public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException {
            super(parentBeanFactory);
            this.reader.loadBeanDefinitions(resource);
        }
    

    主要做了两件事

    • 1.调用父类的构造方法,设置parentBeanFactory。一般情况下parentBeanFactory为空
    • 2.用XmlBeanDefinitionReader加载bean配置。其中XmlBeanDefinitionReader是XmlBeanFactory类是私有变量
    private final XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(this);
    

    我们继续跟踪步骤2
    2.1 根据Resource 构造 EncodedResource

        @Override
        public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
                //根据Resource 构造 EncodedResource
                //默认情况下encoding和charset都为空
                return loadBeanDefinitions(new EncodedResource(resource));
        }
    

    2.2 将EncodedResource放入线程副本,防止循环加载

            //将EncodedResource放入线程副本,防止循环加载
            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!");
            }
    

    2.3 从resource里获取输入流

            try {
                //从resource里获取输入流
                InputStream inputStream = encodedResource.getResource().getInputStream();
                try {
                    //包装流
                    InputSource inputSource = new InputSource(inputStream);
                    if (encodedResource.getEncoding() != null) {
                        inputSource.setEncoding(encodedResource.getEncoding());
                    }
                    //根据inputSource和Resource 来读取配置文件并解析
                    //inputSource用来解析xml得到Dom(用jaxp解析xml的标准输入)
                    //encodedResource.getResource()用来获取xml的验证模式
                    return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
                }
                finally {
                    inputStream.close();
                }
            }
    

    再来看下如何根据resource获取流的,此处以上面基础用法部分的ClassPathResource为例

        @Override
        public InputStream getInputStream() throws IOException {
            InputStream is;
            if (this.clazz != null) {
                is = this.clazz.getResourceAsStream(this.path);
            }
            else if (this.classLoader != null) {
                is = this.classLoader.getResourceAsStream(this.path);
            }
            else {
                is = ClassLoader.getSystemResourceAsStream(this.path);
            }
            if (is == null) {
                throw new FileNotFoundException(getDescription() + " cannot be opened because it does not exist");
            }
            return is;
        }
    

    所以从上面可以看出,ClassPathResource获取InputStream是调用的Class.getResourceAsStream或者ClassLoader.getResourceAsStream或者ClassLoader.getSystemResourceAsStream方法获得输入流。

    2.2 验证配置文件

    spring的基础是基于配置文件。所有的定义又都是从配置文件来的,那么加载配置文件的时候就有必要验证配置文件的正确性。那么spring是如何验证xml的呢?xml验证一般有两种模式DTD和XSD

    2.2.1 DTD

    DTD即 Documnet Type Definition,文档定义语言。使用非XML文件编写,不可扩展,不支持命名空间,仅支持有限的数据类型。使用DTD,要在spring的配置文件中做如下配置:

    <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN 2.0//EN"
    "http://www.springframework.org/dtd/spring-beans-2.0.dtd">
    

    2.2.2 XSD

    XSD即XML Schemas Definition。XSD中描述了xml文档的结构,可以用某个xsd验证xml是否符合规范。XSD相比DTD而言,基于XML编写,支持扩展,支持命名空间,支持更多的数据类型。要在spring中使用xsd验证,需要做如下配置:

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns:tx="http://www.springframework.org/schema/tx"
           xmlns:context="http://www.springframework.org/schema/context"
           xmlns:aop="http://www.springframework.org/schema/aop"
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
    http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
    http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
    http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
    

    其中要配置xmlns和xsi:schemaLocation。且schemaLocation中xmlns要和xsd文件的地址成对出现。另外 ,不配置xsd文件的版本号的原因是,spring会加载jar包中缓存的默认版本号,防止网络闪断造成的下载xsd失败。

    2.2.3 spring验证配置文件

    验证配置文件发生在利用InputSource得到Document的过程中,在这个过程中,先获取配置文件的验证模式,然后再加载配置文件得到Document。如果配置文件不通过,这个过程就会抛异常。

        protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
                throws BeanDefinitionStoreException {
            try {
                //加载配置文件得到Document
                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);
            }
    

    1. 获取配置文件的验证模式,基于XSD验证还是基于DTD验证。源码如下

        protected int getValidationModeForResource(Resource resource) {
            int validationModeToUse = getValidationMode();
            if (validationModeToUse != VALIDATION_AUTO) {
                return validationModeToUse;
            }
            int detectedMode = detectValidationMode(resource);
            if (detectedMode != VALIDATION_AUTO) {
                return detectedMode;
            }
            // Hmm, we didn't get a clear indication... Let's assume XSD,
            // since apparently no DTD declaration has been found up until
            // detection stopped (before finding the document's root tag).
            return VALIDATION_XSD;
        }
    
        public int detectValidationMode(InputStream inputStream) throws IOException {
            // Peek into the file to look for DOCTYPE.
            BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
            try {
                boolean isDtdValidated = false;
                String content;
                while ((content = reader.readLine()) != null) {
                    content = consumeCommentTokens(content);
                    if (this.inComment || !StringUtils.hasText(content)) {
                        continue;
                    }
                    if (hasDoctype(content)) {
                        isDtdValidated = true;
                        break;
                    }
                    if (hasOpeningTag(content)) {
                        // End of meaningful data...
                        break;
                    }
                }
                return (isDtdValidated ? VALIDATION_DTD : VALIDATION_XSD);
            }
            catch (CharConversionException ex) {
                // Choked on some character encoding...
                // Leave the decision up to the caller.
                return VALIDATION_AUTO;
            }
            finally {
                reader.close();
            }
        }
    

    获取验证模式的依据是标签中是否有"DOCTYPE"出现

        private boolean hasDoctype(String content) {
            return content.contains(DOCTYPE);
        }
    
        protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception {
            return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler,
                    getValidationModeForResource(resource), isNamespaceAware());
        }
    

    2. 将InputSource 解析为 Document对象

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

    设置验证模式的关键在于,构建DocumentBuilderFactory的属性设置

        protected DocumentBuilderFactory createDocumentBuilderFactory(int validationMode, boolean namespaceAware)
                throws ParserConfigurationException {
    
            DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
            factory.setNamespaceAware(namespaceAware);
    
            if (validationMode != XmlValidationModeDetector.VALIDATION_NONE) {
                factory.setValidating(true);
                if (validationMode == XmlValidationModeDetector.VALIDATION_XSD) {
                    // 启用命名空间.
                    factory.setNamespaceAware(true);
                    try {
                        //设置xsd验证所需属性
                        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;
        }
    

    同时还要设置DocumentBuilder的ErrorHandler,方便异常抛出

        protected DocumentBuilder createDocumentBuilder(
                DocumentBuilderFactory factory, EntityResolver entityResolver, ErrorHandler errorHandler)
                throws ParserConfigurationException {
    
            DocumentBuilder docBuilder = factory.newDocumentBuilder();
            if (entityResolver != null) {
                docBuilder.setEntityResolver(entityResolver);
            }
            if (errorHandler != null) {
                docBuilder.setErrorHandler(errorHandler);
            }
            return docBuilder;
        }
    

    其中ErrorHandler为类内部变量

    private ErrorHandler errorHandler = new SimpleSaxErrorHandler(logger);
    

    至此已完成了xsd校验的设置

    2.3 将InputSource解析为标准xml Document

    其实上面我们已经贴出了将InputSource解析为xml的源码。值得注意的是,spring解析xml 使用的是 jaxp,并未使用dom4j来解析xml,或许是spring>本文开始,将开始spring 源码解读系列。该系列将会分析下spring的核心功能 IC/DI,AOP,Transaction,MVC 的实现。通过分析系列源码,理清楚spring的运行流程,不至于在工作中遇到相关问题无从下手,同时学习spring中优秀的设计方式,对自己也是颇有裨益的。而本文作为spring源码的入门篇,着重介绍spring的基础容器的实现。本文大致分为以下几个板块:

    1. 容器
      1.1 bean 容器的基本用法
      1.2 bean容器的种类及区别
    2. XmlBeanFactory 源码解读
      2.1 加载配置文件
      2.2 验证配置文件
      2.2.1 DTD
      2.2.2 XSD
      2.3 读取配置文件得到Document
      2.4 解析配置文件
      2.5 存储解析后的数据
    3. XmlBeanFactory 过期替代方法

    1. 容器

    容器,顾名思义容纳物体的器皿。生活中杯子,收纳箱都是容器。在java的世界里就是用来存放各种数据的器皿也就是集合,而在spring里,则是存放各种bean的器皿-BeanFactory。

    1.1 bean 容器的基本用法

    我们先来回顾一下spring容器的基本用法,尽管大家都知道。
    step1:定义一个bean
    step2:在配置文件配置该bean
    step3:初始化bean容器并获取bean


    public class Person {
    
        private String name;
    
        public Person(String name) {
            this.name = name;
        }
    
        public String getName() {
            return name;
        }
    
    
        @Override
        public String toString() {
            return "Person{" +
                    "name='" + name + '\'' +
                    '}';
        }
    }
    
        <bean id="user" class="example.Person">
            <constructor-arg name="name" value="hello person"/>
        </bean>
    
        public static void main(String[] args) {
            BeanFactory beanFactory=new XmlBeanFactory(new ClassPathResource("spring-config.xml"));
            System.out.println(beanFactory.getBean("user"));
        }
    

    1.2 bean容器的种类及区别

    spring中最常用的bean容器为BeanFactory及ApplicationContext。同样都是bean的容器,两者有什么区别呢?

    • ApplicationContext和BeanFactory都提供基础的Bean容器的功能
    • ApplicationContext除了包含BeanFactory所有功能外,还包含了更多的扩展功能。如Bean后置处理器,消息国际化,事件广播等。我们熟悉的Aop,事物也是基于ApplicationContentext实现的。

    2. XmlBeanFactory 源码解读

    BeanFactory作为容器的基础,定义了容器的基础行为。而XmlBeanFactory作为BeanFactory的常用实现之一,除了实现了BeanFactory的方法之外,还提供了从Xml解析配置文件,装载Bean的方法。试想如果让我们自己实现一个Bean的容器,我们会怎么去实现呢。编写一个指定规则的配置文件,加载这个配置文件,解析配置文件并存储。其实spring也做了类似的功能,只不过要比我们设想的要复杂。下面我们按照spring XmlBeanFacotry的初始化步骤来读下源码,大致分为以下步骤:

    • 加载配置文件得到输入流
    • 验证配置文件
    • 读取配置文件得到Dom
    • 解析Dom得到Bean抽象定义
    • 存储解析后的数据
      大致时序图如下:
      image.png

    2.1 加载配置文件

    我们是通过new XmlBeanFactory的方式构造XmlBeanFactory,跟踪其构造方法

        /**
         * 根据Resource实例化XmlBeanFactory
         * @param resource
         * @throws BeansException
         */
        public XmlBeanFactory(Resource resource) throws BeansException {
            this(resource, null);
        }
    
        /**
         * 根据Resource和BeanFactory实例化XmlBeanFactory
         * @param resource
         * @param parentBeanFactory
         * @throws BeansException
         */
        public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException {
            super(parentBeanFactory);
            this.reader.loadBeanDefinitions(resource);
        }
    

    主要做了两件事

    • 1.调用父类的构造方法,设置parentBeanFactory。一般情况下parentBeanFactory为空
    • 2.用XmlBeanDefinitionReader加载bean配置。其中XmlBeanDefinitionReader是XmlBeanFactory类是私有变量
    private final XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(this);
    

    我们继续跟踪步骤2
    2.1 根据Resource 构造 EncodedResource

        @Override
        public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
                //根据Resource 构造 EncodedResource
                //默认情况下encoding和charset都为空
                return loadBeanDefinitions(new EncodedResource(resource));
        }
    

    2.2 将EncodedResource放入线程副本,防止循环加载

            //将EncodedResource放入线程副本,防止循环加载
            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!");
            }
    

    2.3 从resource里获取输入流

            try {
                //从resource里获取输入流
                InputStream inputStream = encodedResource.getResource().getInputStream();
                try {
                    //包装流
                    InputSource inputSource = new InputSource(inputStream);
                    if (encodedResource.getEncoding() != null) {
                        inputSource.setEncoding(encodedResource.getEncoding());
                    }
                    //根据inputSource和Resource 来读取配置文件并解析
                    //inputSource用来解析xml得到Dom(用jaxp解析xml的标准输入)
                    //encodedResource.getResource()用来获取xml的验证模式
                    return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
                }
                finally {
                    inputStream.close();
                }
            }
    

    再来看下如何根据resource获取流的,此处以上面基础用法部分的ClassPathResource为例

        @Override
        public InputStream getInputStream() throws IOException {
            InputStream is;
            if (this.clazz != null) {
                is = this.clazz.getResourceAsStream(this.path);
            }
            else if (this.classLoader != null) {
                is = this.classLoader.getResourceAsStream(this.path);
            }
            else {
                is = ClassLoader.getSystemResourceAsStream(this.path);
            }
            if (is == null) {
                throw new FileNotFoundException(getDescription() + " cannot be opened because it does not exist");
            }
            return is;
        }
    

    所以从上面可以看出,ClassPathResource获取InputStream是调用的Class.getResourceAsStream或者ClassLoader.getResourceAsStream或者ClassLoader.getSystemResourceAsStream方法获得输入流。

    2.2 验证配置文件

    spring的基础是基于配置文件。所有的定义又都是从配置文件来的,那么加载配置文件的时候就有必要验证配置文件的正确性。那么spring是如何验证xml的呢?xml验证一般有两种模式DTD和XSD

    2.2.1 DTD

    DTD即 Documnet Type Definition,文档定义语言。使用非XML文件编写,不可扩展,不支持命名空间,仅支持有限的数据类型。使用DTD,要在spring的配置文件中做如下配置:

    <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN 2.0//EN"
    "http://www.springframework.org/dtd/spring-beans-2.0.dtd">
    

    2.2.2 XSD

    XSD即XML Schemas Definition。XSD中描述了xml文档的结构,可以用某个xsd验证xml是否符合规范。XSD相比DTD而言,基于XML编写,支持扩展,支持命名空间,支持更多的数据类型。要在spring中使用xsd验证,需要做如下配置:

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns:tx="http://www.springframework.org/schema/tx"
           xmlns:context="http://www.springframework.org/schema/context"
           xmlns:aop="http://www.springframework.org/schema/aop"
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
    http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
    http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
    http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
    

    其中要配置xmlns和xsi:schemaLocation。且schemaLocation中xmlns要和xsd文件的地址成对出现。另外 ,不配置xsd文件的版本号的原因是,spring会加载jar包中缓存的默认版本号,防止网络闪断造成的下载xsd失败。

    2.2.3 spring验证配置文件

    验证配置文件发生在利用InputSource得到Document的过程中,在这个过程中,先获取配置文件的验证模式,然后再加载配置文件得到Document。如果配置文件不通过,这个过程就会抛异常。

        protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
                throws BeanDefinitionStoreException {
            try {
                //加载配置文件得到Document
                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);
            }
    

    1. 获取配置文件的验证模式,基于XSD验证还是基于DTD验证。源码如下

        protected int getValidationModeForResource(Resource resource) {
            int validationModeToUse = getValidationMode();
            if (validationModeToUse != VALIDATION_AUTO) {
                return validationModeToUse;
            }
            int detectedMode = detectValidationMode(resource);
            if (detectedMode != VALIDATION_AUTO) {
                return detectedMode;
            }
            // Hmm, we didn't get a clear indication... Let's assume XSD,
            // since apparently no DTD declaration has been found up until
            // detection stopped (before finding the document's root tag).
            return VALIDATION_XSD;
        }
    
        public int detectValidationMode(InputStream inputStream) throws IOException {
            // Peek into the file to look for DOCTYPE.
            BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
            try {
                boolean isDtdValidated = false;
                String content;
                while ((content = reader.readLine()) != null) {
                    content = consumeCommentTokens(content);
                    if (this.inComment || !StringUtils.hasText(content)) {
                        continue;
                    }
                    if (hasDoctype(content)) {
                        isDtdValidated = true;
                        break;
                    }
                    if (hasOpeningTag(content)) {
                        // End of meaningful data...
                        break;
                    }
                }
                return (isDtdValidated ? VALIDATION_DTD : VALIDATION_XSD);
            }
            catch (CharConversionException ex) {
                // Choked on some character encoding...
                // Leave the decision up to the caller.
                return VALIDATION_AUTO;
            }
            finally {
                reader.close();
            }
        }
    

    获取验证模式的依据是标签中是否有"DOCTYPE"出现

        private boolean hasDoctype(String content) {
            return content.contains(DOCTYPE);
        }
    
        protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception {
            return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler,
                    getValidationModeForResource(resource), isNamespaceAware());
        }
    

    2. 将InputSource 解析为 Document对象

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

    设置验证模式的关键在于,构建DocumentBuilderFactory的属性设置

        protected DocumentBuilderFactory createDocumentBuilderFactory(int validationMode, boolean namespaceAware)
                throws ParserConfigurationException {
    
            DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
            factory.setNamespaceAware(namespaceAware);
    
            if (validationMode != XmlValidationModeDetector.VALIDATION_NONE) {
                factory.setValidating(true);
                if (validationMode == XmlValidationModeDetector.VALIDATION_XSD) {
                    // 启用命名空间.
                    factory.setNamespaceAware(true);
                    try {
                        //设置xsd验证所需属性
                        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;
        }
    

    同时还要设置DocumentBuilder的ErrorHandler,方便异常抛出

        protected DocumentBuilder createDocumentBuilder(
                DocumentBuilderFactory factory, EntityResolver entityResolver, ErrorHandler errorHandler)
                throws ParserConfigurationException {
    
            DocumentBuilder docBuilder = factory.newDocumentBuilder();
            if (entityResolver != null) {
                docBuilder.setEntityResolver(entityResolver);
            }
            if (errorHandler != null) {
                docBuilder.setErrorHandler(errorHandler);
            }
            return docBuilder;
        }
    

    其中ErrorHandler为类内部变量

    private ErrorHandler errorHandler = new SimpleSaxErrorHandler(logger);
    

    至此已完成了xsd校验的设置

    2.3 将InputSource解析为标准xml Document

    其实上面我们已经贴出了将InputSource解析为xml的源码。值得注意的是,spring解析xml 使用的是 jaxp,并未使用dom4j来解析xml,之所以不和Hibernate一样采用dom4j加载xml文件,可能是觉得加载少量的静态配置文件,jaxp性能足矣。大致经历了以下步骤

    • 构建DocumentBuilderFactory-设置验证模式和命名空间
    • 构建DocumentBuilder-设置错误处理器
    • 解析配置文件得到 Document
        @Override
        public Document loadDocument(InputSource inputSource, EntityResolver entityResolver,
                ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception {
            //构建DocumentBuilderFactory-设置验证模式和命名空间
            DocumentBuilderFactory factory = createDocumentBuilderFactory(validationMode, namespaceAware);
            if (logger.isDebugEnabled()) {
                logger.debug("Using JAXP provider [" + factory.getClass().getName() + "]");
            }
            //构建DocumentBuilder-设置错误处理器
            DocumentBuilder builder = createDocumentBuilder(factory, entityResolver, errorHandler);
            //解析配置文件
            return builder.parse(inputSource);
        }
    

    2.4 配置文件解析

    得到Document 之后,就可以开始配置文件的解析了。对配置文件的解析的过程,实际上是对配置文件各个标签进行解析转换为BeanDefinitionHolder的过程。解析的过程又分为对默认标签(namespace 为 http://www.springframework.org/schema/beans)和自定义标签(除了默认标签之外的标签)的解析。在分析两种解析的规则之前,我们先看下解析标签之前的执行逻辑
    1.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);
            }
        }
    

    2.创建BeanDefinitionDocumentReader,并调用其registerBeanDefinitions方法解析注册BeanDefinition

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

    3.得到Document的根节点,并循环解析

    public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
            this.readerContext = readerContext;
            logger.debug("Loading bean definitions");
            Element root = doc.getDocumentElement();
            doRegisterBeanDefinitions(root);
        }
    

    4.解析前后的预处理(部分方法空实现,留给子类覆盖)

    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)) {
                        if (logger.isInfoEnabled()) {
                            logger.info("Skipped XML bean definition file due to specified profiles [" + profileSpec +
                                    "] not matching: " + getReaderContext().getResource());
                        }
                        return;
                    }
                }
            }
    
            preProcessXml(root);
            parseBeanDefinitions(root, this.delegate);
            postProcessXml(root);
    
            this.delegate = parent;
        }
    

    5.进入解析方法,根据不同的标签类型执行不同的解析逻辑

    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 {
                            //解析自定义标签 如mvc tx 等
                            delegate.parseCustomElement(ele);
                        }
                    }
                }
            }
            else {
                delegate.parseCustomElement(root);
            }
        }
    

    2.4.1 默认标签的解析

    spring 会把 namespace 为 http://www.springframework.org/schema/beans的都作为默认标签来解析。判断依据的代码如下:

    public boolean isDefaultNamespace(String namespaceUri) {
            return (!StringUtils.hasLength(namespaceUri) || BEANS_NAMESPACE_URI.equals(namespaceUri));
        }
    

    其中BEANS_NAMESPACE_URI=http://www.springframework.org/schema/beans
    默认标签又分为import,alias,bean,beans四类标签的解析(根据nodeName 判断)。

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

    我们以bean标签的解析为例,分析下解析的流程。

    2.4.1.1 bean 标签的解析

    bean标签的解析分为如下两个主要步骤
    1. 将bean标签解析为BeanDefinitionHolder
    2. 注册BeanDefinitionHolder

    2.4.1.1.1 将bean标签解析为BeanDefinitionHolder
    protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
            //解析bean 标签的各种属性,转换为BeanDefinitionHolder
            BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
            if (bdHolder != null) {
                bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
                try {
                    // 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);
                }
                // Send registration event.
                getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
            }
        }
    

    可以看到核心解析代码是在BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
    跟进该方法,如下

    public BeanDefinitionHolder parseBeanDefinitionElement(Element ele) {
            return parseBeanDefinitionElement(ele, null);
        }
    
    • 获取id
    • 获取name
    • 处理多个name
    • 当不存在id时且name存在时,将首个name作为beanName
    • 检查name和id的唯一性
    • 解析其它attribute
    • beanName为空时(id,name都为空),生成默认beanName
    • 包装成BeanDefinitionHolder并返回
    public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, BeanDefinition containingBean) {
            //获取id
            String id = ele.getAttribute(ID_ATTRIBUTE);
            //获取name
            String nameAttr = ele.getAttribute(NAME_ATTRIBUTE);
            //处理多个name
            List<String> aliases = new ArrayList<String>();
            if (StringUtils.hasLength(nameAttr)) {
                String[] nameArr = StringUtils.tokenizeToStringArray(nameAttr, MULTI_VALUE_ATTRIBUTE_DELIMITERS);
                aliases.addAll(Arrays.asList(nameArr));
            }
    
            String beanName = id;
            //当不存在id时且name存在时,将首个name作为beanName
            if (!StringUtils.hasText(beanName) && !aliases.isEmpty()) {
                beanName = aliases.remove(0);
                if (logger.isDebugEnabled()) {
                    logger.debug("No XML 'id' specified - using '" + beanName +
                            "' as bean name and " + aliases + " as aliases");
                }
            }
            //检查name和id的唯一性
            if (containingBean == null) {
                checkNameUniqueness(beanName, aliases, ele);
            }
            //解析其它attribute
            AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName, containingBean);
            //自动生成beanName
            if (beanDefinition != null) {
                if (!StringUtils.hasText(beanName)) {
                    try {
                        if (containingBean != null) {
                            beanName = BeanDefinitionReaderUtils.generateBeanName(
                                    beanDefinition, this.readerContext.getRegistry(), true);
                        }
                        else {
                            beanName = this.readerContext.generateBeanName(beanDefinition);
                            // Register an alias for the plain bean class name, if still possible,
                            // if the generator returned the class name plus a suffix.
                            // This is expected for Spring 1.2/2.0 backwards compatibility.
                            String beanClassName = beanDefinition.getBeanClassName();
                            if (beanClassName != null &&
                                    beanName.startsWith(beanClassName) && beanName.length() > beanClassName.length() &&
                                    !this.readerContext.getRegistry().isBeanNameInUse(beanClassName)) {
                                aliases.add(beanClassName);
                            }
                        }
                        if (logger.isDebugEnabled()) {
                            logger.debug("Neither XML 'id' nor 'name' specified - " +
                                    "using generated bean name [" + beanName + "]");
                        }
                    }
                    catch (Exception ex) {
                        error(ex.getMessage(), ele);
                        return null;
                    }
                }
                String[] aliasesArray = StringUtils.toStringArray(aliases);
                //包装成BeanDefinitionHolder并返回
                return new BeanDefinitionHolder(beanDefinition, beanName, aliasesArray);
            }
    
            return null;
        }
    

    其中beanName的生成规则为className+"#"+num,其中num从-1开始自增。
    到此为止,我们只看到了name 和id属性的解析,那么其它属性是如何解析的呢?
    从AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName, containingBean);大致流程如下

    • 解析class属性
    • 解析parent属性
    • 创建用来记录属性的AbstractBeanDefinition
    • 硬编码解析bean的各种属性
    • 解析description
    • 解析meta标签
    • 解析lookup-method
    • 解析replaced-method
    • 解析constructor-arg
    • 解析property标签
    • 解析qualifier
    public AbstractBeanDefinition parseBeanDefinitionElement(
                Element ele, String beanName, BeanDefinition containingBean) {
    
            this.parseState.push(new BeanEntry(beanName));
    
            String className = null;
            //解析class属性
            if (ele.hasAttribute(CLASS_ATTRIBUTE)) {
                className = ele.getAttribute(CLASS_ATTRIBUTE).trim();
            }
    
            try {
                String parent = null;
                //解析parent属性
                if (ele.hasAttribute(PARENT_ATTRIBUTE)) {
                    parent = ele.getAttribute(PARENT_ATTRIBUTE);
                }
                //创建用来记录属性的AbstractBeanDefinition
                AbstractBeanDefinition bd = createBeanDefinition(className, parent);
                //硬编码解析bean的各种属性
                parseBeanDefinitionAttributes(ele, beanName, containingBean, bd);
                //解析description
                bd.setDescription(DomUtils.getChildElementValueByTagName(ele, DESCRIPTION_ELEMENT));
                //解析meta标签
                parseMetaElements(ele, bd);
                //解析lookup-method
                parseLookupOverrideSubElements(ele, bd.getMethodOverrides());
                //解析replaced-method
                parseReplacedMethodSubElements(ele, bd.getMethodOverrides());
                //解析constructor-arg
                parseConstructorArgElements(ele, bd);
                //解析property标签
                parsePropertyElements(ele, bd);
                //解析qualifier
                parseQualifierElements(ele, bd);
    
                bd.setResource(this.readerContext.getResource());
                bd.setSource(extractSource(ele));
    
                return bd;
            }
            catch (ClassNotFoundException ex) {
                error("Bean class [" + className + "] not found", ele, ex);
            }
            catch (NoClassDefFoundError err) {
                error("Class that bean class [" + className + "] depends on not found", ele, err);
            }
            catch (Throwable ex) {
                error("Unexpected failure during bean definition parsing", ele, ex);
            }
            finally {
                this.parseState.pop();
            }
    
            return null;
        }
    

    大致的思路都是提取node的属性,然后做封装,我们以parseBeanDefinitionAttributes(ele, beanName, containingBean, bd);为例来看下提取并封装的过程

    public AbstractBeanDefinition parseBeanDefinitionAttributes(Element ele, String beanName,
                BeanDefinition containingBean, AbstractBeanDefinition bd) {
    
            if (ele.hasAttribute(SINGLETON_ATTRIBUTE)) {
                error("Old 1.x 'singleton' attribute in use - upgrade to 'scope' declaration", ele);
            }
            else if (ele.hasAttribute(SCOPE_ATTRIBUTE)) {
                bd.setScope(ele.getAttribute(SCOPE_ATTRIBUTE));
            }
            else if (containingBean != null) {
                // Take default from containing bean in case of an inner bean definition.
                bd.setScope(containingBean.getScope());
            }
    
            if (ele.hasAttribute(ABSTRACT_ATTRIBUTE)) {
                bd.setAbstract(TRUE_VALUE.equals(ele.getAttribute(ABSTRACT_ATTRIBUTE)));
            }
    
            String lazyInit = ele.getAttribute(LAZY_INIT_ATTRIBUTE);
            if (DEFAULT_VALUE.equals(lazyInit)) {
                lazyInit = this.defaults.getLazyInit();
            }
            bd.setLazyInit(TRUE_VALUE.equals(lazyInit));
    
            String autowire = ele.getAttribute(AUTOWIRE_ATTRIBUTE);
            bd.setAutowireMode(getAutowireMode(autowire));
    
            String dependencyCheck = ele.getAttribute(DEPENDENCY_CHECK_ATTRIBUTE);
            bd.setDependencyCheck(getDependencyCheck(dependencyCheck));
    
            if (ele.hasAttribute(DEPENDS_ON_ATTRIBUTE)) {
                String dependsOn = ele.getAttribute(DEPENDS_ON_ATTRIBUTE);
                bd.setDependsOn(StringUtils.tokenizeToStringArray(dependsOn, MULTI_VALUE_ATTRIBUTE_DELIMITERS));
            }
    
            String autowireCandidate = ele.getAttribute(AUTOWIRE_CANDIDATE_ATTRIBUTE);
            if ("".equals(autowireCandidate) || DEFAULT_VALUE.equals(autowireCandidate)) {
                String candidatePattern = this.defaults.getAutowireCandidates();
                if (candidatePattern != null) {
                    String[] patterns = StringUtils.commaDelimitedListToStringArray(candidatePattern);
                    bd.setAutowireCandidate(PatternMatchUtils.simpleMatch(patterns, beanName));
                }
            }
            else {
                bd.setAutowireCandidate(TRUE_VALUE.equals(autowireCandidate));
            }
    
            if (ele.hasAttribute(PRIMARY_ATTRIBUTE)) {
                bd.setPrimary(TRUE_VALUE.equals(ele.getAttribute(PRIMARY_ATTRIBUTE)));
            }
    
            if (ele.hasAttribute(INIT_METHOD_ATTRIBUTE)) {
                String initMethodName = ele.getAttribute(INIT_METHOD_ATTRIBUTE);
                if (!"".equals(initMethodName)) {
                    bd.setInitMethodName(initMethodName);
                }
            }
            else {
                if (this.defaults.getInitMethod() != null) {
                    bd.setInitMethodName(this.defaults.getInitMethod());
                    bd.setEnforceInitMethod(false);
                }
            }
    
            if (ele.hasAttribute(DESTROY_METHOD_ATTRIBUTE)) {
                String destroyMethodName = ele.getAttribute(DESTROY_METHOD_ATTRIBUTE);
                bd.setDestroyMethodName(destroyMethodName);
            }
            else {
                if (this.defaults.getDestroyMethod() != null) {
                    bd.setDestroyMethodName(this.defaults.getDestroyMethod());
                    bd.setEnforceDestroyMethod(false);
                }
            }
    
            if (ele.hasAttribute(FACTORY_METHOD_ATTRIBUTE)) {
                bd.setFactoryMethodName(ele.getAttribute(FACTORY_METHOD_ATTRIBUTE));
            }
            if (ele.hasAttribute(FACTORY_BEAN_ATTRIBUTE)) {
                bd.setFactoryBeanName(ele.getAttribute(FACTORY_BEAN_ATTRIBUTE));
            }
    
            return bd;
        }
    

    可以看到,基本上都是提取attribute,然后设置到AbstractBeanDefinition的各个对应属性中,也就是说AbstractBeanDefinition的属性和我们在xml中配置的属性是一一对应的
    下图为AbstractBeanDefinition的属性截图,详情请自行查看

    image.png
    2.4.1.1.2 注册BeanDefinitionHolder

    解析完标签并包装后,剩下的工作就是beanDefinition的保存,即注册BeanDefinitionHolder。

    protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
            BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
            if (bdHolder != null) {
                bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
                try {
                    // 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);
                }
                // Send registration event.
                getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
            }
        }
    
    public static void registerBeanDefinition(
                BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)
                throws BeanDefinitionStoreException {
    
            // Register bean definition under primary name.
            String beanName = definitionHolder.getBeanName();
            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);
                }
            }
        }
    
    public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
                throws BeanDefinitionStoreException {
    
            Assert.hasText(beanName, "Bean name must not be empty");
            Assert.notNull(beanDefinition, "BeanDefinition must not be null");
    
            if (beanDefinition instanceof AbstractBeanDefinition) {
                try {
                    ((AbstractBeanDefinition) beanDefinition).validate();
                }
                catch (BeanDefinitionValidationException ex) {
                    throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,
                            "Validation of bean definition failed", ex);
                }
            }
    
            BeanDefinition oldBeanDefinition;
    
            oldBeanDefinition = this.beanDefinitionMap.get(beanName);
            if (oldBeanDefinition != null) {
                if (!isAllowBeanDefinitionOverriding()) {
                    throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,
                            "Cannot register bean definition [" + beanDefinition + "] for bean '" + beanName +
                            "': There is already [" + oldBeanDefinition + "] bound.");
                }
                else if (oldBeanDefinition.getRole() < beanDefinition.getRole()) {
                    // e.g. was ROLE_APPLICATION, now overriding with ROLE_SUPPORT or ROLE_INFRASTRUCTURE
                    if (this.logger.isWarnEnabled()) {
                        this.logger.warn("Overriding user-defined bean definition for bean '" + beanName +
                                "' with a framework-generated bean definition: replacing [" +
                                oldBeanDefinition + "] with [" + beanDefinition + "]");
                    }
                }
                else if (!beanDefinition.equals(oldBeanDefinition)) {
                    if (this.logger.isInfoEnabled()) {
                        this.logger.info("Overriding bean definition for bean '" + beanName +
                                "' with a different definition: replacing [" + oldBeanDefinition +
                                "] with [" + beanDefinition + "]");
                    }
                }
                else {
                    if (this.logger.isDebugEnabled()) {
                        this.logger.debug("Overriding bean definition for bean '" + beanName +
                                "' with an equivalent definition: replacing [" + oldBeanDefinition +
                                "] with [" + beanDefinition + "]");
                    }
                }
                this.beanDefinitionMap.put(beanName, beanDefinition);
            }
            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<String>(this.beanDefinitionNames.size() + 1);
                        updatedDefinitions.addAll(this.beanDefinitionNames);
                        updatedDefinitions.add(beanName);
                        this.beanDefinitionNames = updatedDefinitions;
                        if (this.manualSingletonNames.contains(beanName)) {
                            Set<String> updatedSingletons = new LinkedHashSet<String>(this.manualSingletonNames);
                            updatedSingletons.remove(beanName);
                            this.manualSingletonNames = updatedSingletons;
                        }
                    }
                }
                else {
                    // Still in startup registration phase
                    this.beanDefinitionMap.put(beanName, beanDefinition);
                    this.beanDefinitionNames.add(beanName);
                    this.manualSingletonNames.remove(beanName);
                }
                this.frozenBeanDefinitionNames = null;
            }
    
            if (oldBeanDefinition != null || containsSingleton(beanName)) {
                resetBeanDefinition(beanName);
            }
        }
    

    注册的关键在于将beanDefinition 放入map中,其中key为beanName,value为beanDefinition。

    2.4.2 自定义标签的解析

    自定义标签的解析关键有如下三个步骤:
    1. 获取nameSpaceUri
    2.根据nameSpaceUri获得相应的NamespaceHandler
    3.调用NamespaceHandler.parse方法得到相应的BeanDefinition

    2.4.2.1 获取namespaceUri

    这步就是获取Element的NameSpaceUrI属性

    2.4.2.2 获取NamespaceHandler

    获取NamespaceHandler又分为如下步骤

    1. 获取namespaceHandlerResolver
    2. 通过namespaceHandlerResolver获取NamespaceHandler
    2.4.2.2.1 获取namespaceHandlerResolver

    获取XmlReaderContext的namespaceHandlerResolver属性,该属性是在解析配置文件创建XmlReaderContext时设置的默认值,代码如下

    public XmlReaderContext createReaderContext(Resource resource) {
            return new XmlReaderContext(resource, this.problemReporter, this.eventListener,
                    this.sourceExtractor, this, getNamespaceHandlerResolver());
        }
    public NamespaceHandlerResolver getNamespaceHandlerResolver() {
            if (this.namespaceHandlerResolver == null) {
                this.namespaceHandlerResolver = createDefaultNamespaceHandlerResolver();
            }
            return this.namespaceHandlerResolver;
        }
    protected NamespaceHandlerResolver createDefaultNamespaceHandlerResolver() {
            return new DefaultNamespaceHandlerResolver(getResourceLoader().getClassLoader());
        }
    
    2.4.2.2.2 获取NamespaceHandler

    获取NamespaceHandler时会做如下操作:
    1. 首次加载的时候,读取"META-INF/spring.handlers"下的文件,初始化handlerMappings 其中key 为NameSpaceUrI,value为NamespaceHandler className
    2. 取NameSpaceUrI相应的NamespaceHandler className,并通过反射的方式实例化NamespaceHandler
    3. 调用NamespaceHandler的init方法,初始化NamespaceHandler(此时会注册BeanDefinitionParser)
    4. 初始化NamespaceHandler完成后覆盖缓存,并返回(最后handlerMappings key为namespaceUri,value为初始化后的NamespaceHandler)
    以spring-aop-4.3.18.RELEASE.jar 看下spring.handlers

    http\://www.springframework.org/schema/aop=org.springframework.aop.config.AopNamespaceHandler
    

    初始化NameSpaceHandler的部分代码如下:

    public NamespaceHandler resolve(String namespaceUri) {
            Map<String, Object> handlerMappings = getHandlerMappings();
            Object handlerOrClassName = handlerMappings.get(namespaceUri);
            if (handlerOrClassName == null) {
                return null;
            }
            else if (handlerOrClassName instanceof NamespaceHandler) {
                return (NamespaceHandler) handlerOrClassName;
            }
            else {
                String className = (String) handlerOrClassName;
                try {
                    Class<?> handlerClass = ClassUtils.forName(className, this.classLoader);
                    if (!NamespaceHandler.class.isAssignableFrom(handlerClass)) {
                        throw new FatalBeanException("Class [" + className + "] for namespace [" + namespaceUri +
                                "] does not implement the [" + NamespaceHandler.class.getName() + "] interface");
                    }
                    NamespaceHandler namespaceHandler = (NamespaceHandler) BeanUtils.instantiateClass(handlerClass);
                    namespaceHandler.init();
                    handlerMappings.put(namespaceUri, namespaceHandler);
                    return namespaceHandler;
                }
                catch (ClassNotFoundException ex) {
                    throw new FatalBeanException("NamespaceHandler class [" + className + "] for namespace [" +
                            namespaceUri + "] not found", ex);
                }
                catch (LinkageError err) {
                    throw new FatalBeanException("Invalid NamespaceHandler class [" + className + "] for namespace [" +
                            namespaceUri + "]: problem with handler class file or dependent class", err);
                }
            }
        }
    

    这样就完成了自定义标签解析器的自定义逻辑和通用逻辑的解耦,标签解析器只需要维护自身相关的业务逻辑即可,这也是spring框架中常用的优雅设计。

    2.4.2.2.3 调用NamespaceHandler.parse获取BeanDefinition

    调用NamespaceHandler.parse获取BeanDefinition时,又经过如下步骤
    1. 获取Element的localName(如<aop:aspectj-autoproxy expose-proxy="true"/> 中的aspectj-autoproxy)
    2. 根据localName获得BeanDefinitionParser(其中BeanDefinitionParser是在获取NamespaceHandler时,在init 方法内部调用registerBeanDefinitionParser 方法赋值的,放入map中,key为localName,value 为BeanDefinitionParser)
    3. 调用BeanDefinitionParser的parse 方法获得BeanDefinition(这个返回的BeanDefinition,spring 并未像解析自定义标签那样去注册,因为一般在parse时已经完成了注册)
    部分代码如下

    public BeanDefinition parse(Element element, ParserContext parserContext) {
            return findParserForElement(element, parserContext).parse(element, parserContext);
        }
    
        /**
         * Locates the {@link BeanDefinitionParser} from the register implementations using
         * the local name of the supplied {@link Element}.
         */
        private BeanDefinitionParser findParserForElement(Element element, ParserContext parserContext) {
            String localName = parserContext.getDelegate().getLocalName(element);
            BeanDefinitionParser parser = this.parsers.get(localName);
            if (parser == null) {
                parserContext.getReaderContext().fatal(
                        "Cannot locate BeanDefinitionParser for element [" + localName + "]", element);
            }
            return parser;
        }
    

    自此,我们已经完成了整个bean的解析流程。最终的结果是Spring把BeanDefinition放到缓存map中,方便以后getBean时根据BeanDefinition的信息创建Bean。

    3. XmlBeanFactory 过期替代方法

    XmlBeanFactory 从spring 3.1开始被标记为过期方法。那么我们可以用什么方法来替代它呢?答案是DefaultListableBeanFactory。DefaultListableBeanFactory是spring注册加载bean的默认实现,XmlBeanFactory继承了DefaultListableBeanFactory,并定义了XmlBeanDefinitionReader实现了从xml文件读取。
    old

    BeanFactory beanFactory=new XmlBeanFactory(new ClassPathResource("spring-config.xml"));
            System.out.println(beanFactory.getBean("um"));
    }
    

    replace

    DefaultListableBeanFactory defaultListableBeanFactory=new DefaultListableBeanFactory();
            XmlBeanDefinitionReader xmlBeanDefinitionReader=new XmlBeanDefinitionReader(defaultListableBeanFactory);
            xmlBeanDefinitionReader.loadBeanDefinitions(new ClassPathResource("spring-config.xml"));
            System.out.println(defaultListableBeanFactory.getBean("um"));
    }
    

    总结

    通过分析XmlBeanFactory解析注册bean的过程中,我们发现spring的代码在执行的时候,并不是一步或者一个类就到位的,而是如抽丝剥茧一样,一层层调用,才得以见到其核心逻辑。把一个复杂的逻辑,简化成一个个的逻辑单元,由这些个逻辑单元,搭建起整个方法的执行脉络。同时其可扩展的设计,充分体现了程序设计的开闭原则及单一职责原则。在阅读源码的过程中,我们在深入原理的同时,更可以学习其优秀的设计模式。内外兼修,立于不败。

    解析bean完成后,就可以创建并使用bean了。稍后,我们将继续解读bean创建的源码,看下bean创建的过程到底发生了什么。下一篇-spring源码系列-Bean的创建流程

    参考文章
    Spring 源码深度解析-郝佳

    相关文章

      网友评论

        本文标题:spring源码系列-容器之XmlBeanFactory

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