深入理解Spring源码(二)- 配置文件的解析流程

149 阅读4分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第2天,点击查看活动详情

现如今大部分项目都已经采用了Spring Boot作为项目的基地,对于Spring xml的配置文件用的不在那么频繁,但是对于学习Spring源码来说还是比较重要的,本文将介绍Spring在启动时是如何将xml配置文件进行解析。

一、准备测试类及xml文件

测试案例仍以加载一个bean作为例子

text.xml

<?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: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 https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">

    <bean class="com.spring.Bean" id="bean">
        <property name="name" value="张三"></property>
        <property name="age" value="12"></property>
    </bean>
</beans>

Text.class

package com.spring;

import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Text {
    public static void main(String[] args) {

        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("text.xml");
        Bean bean = context.getBean(Bean.class);
        System.out.println(bean.getName());
        System.out.println(bean.getAge());

    }
}

Bean.class

package com.spring;

public class Bean {

    private String name;

    private Integer age;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }
}

二、解析流程

前面了解Spring中的核心方法后,直接把断电打在ObtainFreshBeanFactory上,进入该方法,能够看到有两个方法

这两个方法一个是刷新BeanFactory,一个是获取BeanFactory

跟随断点能够进入到refreshBeanFactory()

这个方法中做了这么几件事:

1、判断是否存在BeanFactory,如果存在进行销毁

2、创建一个新的类型为DefaultListableBeanFactory的bean工厂

3、设置beanFactory的一些属性

4、执行loadBeanDefinition,加载beanDefinition

在这个方法中最重要也是最值得关注的方法就是loadBeanDefinition,后续会有很个名字相同的重载方法,到执行loadBeanDefinition之前 ,我们可以看到此时的bean工厂已经创建完成,接下来就是要进行beanDefinition的填充,可以看一下当前bean工厂的一个状态

进入loadBeanDefinition xml文件的解析可以说正式的开始了

进入AbstractXmlApplicationContext#loadBeanDefinition(DefaultListableBeanFactory beanFactory)方法中,主要做了这么几件事

1、创建一个Xml的阅读器 XmlBeanDefinitionReader

2、给beanDefinitionReader设置一些属性

3、初始化BeanDefinitionReader

4、继续执行重载方法loadBeanDefinition

接着进入重载方法AbstractXmlApplicationContext#loadBeanDefinitions(XmlBeanDefinitionReader reader)

可以看到这个方法中有两个获取配置的地方 ,getConfigResources这个方法我们一般用不到,所以一定为空,主要看的是下面的getConfigLocations,该方法用来获取我们的配置文件名,即ClassPathXmlApplicationContext中传入的text.xml

可以进去getConfigLocations中看一下是怎么获取到的

这里的configLocations就是一个数组,这里是直接进行获取,想象一下这个值是什么时候设置进来的。

向前找我们可以找到在ClassPathXmlApplicationContext中找到一个setConfigLocations(configLocations)方法,这里的入参就是我们上面ClassPathXmlApplicationContext中出入的参数,由于可能是多个配置文件,所以在这个方法中会解析为一个数组存储,当Spring需要加载的时候直接取值用就可以了。

继续解析xml,拿到configLocations后,调用AbstractBeanDefinitionReader#loadBeanDefinitions(String... locations)

这个方法没什么特殊的,就是将配置文件进行循环解析,直接看后面的解析方法

最终会走到

XmlBeanDefinitionReader#loadBeanDefinitions(EncodedResource encodedResource)

该方法中需要注意的点在于

1、将resource转换为了一个inputStream的输入流

2、设置了输入流的字符格式

接着调用doLoadBeanDefinitions,在Spring中有个奇怪的现象,就是以do开头的方法大部分都是真正做事的方法。

XmlBeanDefinitionReader#doLoadBeanDefinitions(InputSource inputSource, Resource resource)

该方法做了:

1、将inputStream输入流转换成一个Document,在这个过程中会去选择dtd还是xsd

2、调用registerBeanDefinitions方法

XmlBeanDefinitionReader#registerBeanDefinitions(Document doc, Resource resource)

1、创建了一个documentReader的读取

2、统计当前注册前的注册数量

3、调用registerBeanDefinitions进行注册

直接跳过中间的过程到

DefaultBeanDefinitionDocumentReader#parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate)

开始正式的解析xml中bean的过程

可以看到方法中比较关键的方法

1、parseDefaultElement(ele, delegate); 用来解析spring默认的标签类

2、delegate.parseCustomElement(ele); 用来解析spring扩展的标签类,例如aop的标签

先看默认标签的解析

DefaultBeanDefinitionDocumentReader#parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate)

可以看到一个有四种类型,分别对应着、、、标签,结合本次测试类的标签,将会processBeanDefinition(ele, delegate); 其余方法不在赘述。

DefaultBeanDefinitionDocumentReader#processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate)

这个方法中会获取到一个BeanDefinitionHolder,然后会把这个holder注册到beanFactory中去,先跳过这个方法,看parseBeanDefinitionElement方法是怎么获得holder

DefaultBeanDefinitionDocumentReader#parseBeanDefinitionElement(Element ele, @Nullable BeanDefinition containingBean)

public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, @Nullable BeanDefinition containingBean) {
        //获取到标签上的id值
        String id = ele.getAttribute(ID_ATTRIBUTE);
        //获取到标签上name的值
        String nameAttr = ele.getAttribute(NAME_ATTRIBUTE);

        //创建一个别名的list
        List<String> aliases = new ArrayList<>();
        if (StringUtils.hasLength(nameAttr)) {
            String[] nameArr = StringUtils.tokenizeToStringArray(nameAttr, MULTI_VALUE_ATTRIBUTE_DELIMITERS);
            aliases.addAll(Arrays.asList(nameArr));
        }

        //beanName 等于id
        String beanName = id;
        //如果别名不为空的话设置beanName等于list中的第一个元素的值,病删除别名中的第一个元素
        if (!StringUtils.hasText(beanName) && !aliases.isEmpty()) {
            beanName = aliases.remove(0);
            if (logger.isTraceEnabled()) {
                logger.trace("No XML 'id' specified - using '" + beanName +
                        "' as bean name and " + aliases + " as aliases");
            }
        }
        //containingBean从processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate)方法中进来的为null
        if (containingBean == null) {
            //检查beanName和别名是否唯一
            checkNameUniqueness(beanName, aliases, ele);
        }
        //将bean中剩下的属性解析出来,并返回一个AbstractBeanDefinition对象回来
        AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName, containingBean);
        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.
                        //如果生成器返回类名加上后缀,则为普通 bean 类名注册一个别名(如果仍然可能
                        String beanClassName = beanDefinition.getBeanClassName();
                        if (beanClassName != null &&
                                beanName.startsWith(beanClassName) && beanName.length() > beanClassName.length() &&
                                !this.readerContext.getRegistry().isBeanNameInUse(beanClassName)) {
                            aliases.add(beanClassName);
                        }
                    }
                    if (logger.isTraceEnabled()) {
                        logger.trace("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);
            return new BeanDefinitionHolder(beanDefinition, beanName, aliasesArray);
        }

        return null;
    }

现在在回过头来看DefaultBeanDefinitionDocumentReader#processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) ,返回了BeanDefinitionHolder已经,到目前为止 xml中的内容都已经被封装进BeanDefinitionHolder中。

beanDefinition的注册

调用BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());完成beanDefinition的注册

三、总结

用一张图来展示来展示整个流程