spring源码系列-容器之XmlBeanFactory

本文开始,将开始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 + '\'' +
                '}';
    }
}
    
        
    
    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. 存储解析后的数据
    大致时序图如下(图丑凑合看吧( ╯□╰ )):
    spring源码系列-容器之XmlBeanFactory_第1张图片
    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 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!");
        }

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的配置文件中做如下配置:


2.2.2 XSD

XSD即XML Schemas Definition。XSD中描述了xml文档的结构,可以用某个xsd验证xml是否符合规范。XSD相比DTD而言,基于XML编写,支持扩展,支持命名空间,支持更多的数据类型。要在spring中使用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 + '\'' +
                '}';
    }
}
    
        
    
    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抽象定义
  • 存储解析后的数据
    大致时序图如下:
    spring源码系列-容器之XmlBeanFactory_第2张图片
    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 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!");
        }

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的配置文件中做如下配置:


2.2.2 XSD

XSD即XML Schemas Definition。XSD中描述了xml文档的结构,可以用某个xsd验证xml是否符合规范。XSD相比DTD而言,基于XML编写,支持扩展,支持命名空间,支持更多的数据类型。要在spring中使用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  elements will cause recursion in this method. In
        // order to propagate and preserve  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 aliases = new ArrayList();
        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的属性截图,详情请自行查看

spring源码系列-容器之XmlBeanFactory_第3张图片
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 updatedDefinitions = new ArrayList(this.beanDefinitionNames.size() + 1);
                    updatedDefinitions.addAll(this.beanDefinitionNames);
                    updatedDefinitions.add(beanName);
                    this.beanDefinitionNames = updatedDefinitions;
                    if (this.manualSingletonNames.contains(beanName)) {
                        Set updatedSingletons = new LinkedHashSet(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 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(如 中的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)