概要

Spring 框架使用了BeanFactory 进行加载 xml 和生成 bean 实例。下面我们分析下Spring加载xml文件的过程。
spring 版本是最新的 4.3.9 release 版本

示例

XmlBeanFactory xbf = new XmlBeanFactory(new ClassPathResource("bean.xml"));
User user = User.class.cast(xbf.getBean("user"));
System.out.println(user);

我们通过XmlBeanFactory分析下xml的加载过程。通常我们开发的时候一般都是使用ClassPathXmlApplicationContext进行加载配置文件的。原理都一样,只不过ClassPathXmlApplicationContext宽展了好多功能。但加载xml的原理都一样。

ClassPathResource 封装了xml文件信息,可以调用getInputStream() 方法获取文件。

源码解析

XmlBeanFactory.java


从代码中发现XmlBeanFactory委托给XmlBeanDefintionReader进行处理

XmlBeanDefintionReader.java


1. 使用EncodeResource封装资源文件。如果指定编码则使用指定编码进行读取资源文件。

2. 判断该资源是否已经加载过

3. 构造InputStream实例,然后调用 doLoadBeanDefinitions() 方法

InputSource 类结构
public class InputSource {
    private String publicId;
    private String systemId;
    private InputStream byteStream;
    private String encoding;
    private Reader characterStream;
    ....
}

使用SAX解析、验证xml的时候需要使用到 publicId和systemId

doLoadBeanDefinitions() 方法


1. 使用SAX解析xml获取Document对象

2. 根据返回的Document 注册 Bean 信息

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


判断xml的文档验证机制是DTD还是XSD

1.如果指定验证模式则使用指定的。

2.如果没有指定则调用 detectValidationMode 自动检查

读取xml文件中的是否保护“DOCTYPE”,如果包含则是DTD,否则则是XSD

getEntityResolver() 方法

EntityResovle作用:SAX解析xml的时候首先读取xml文档上的声明,根据声明找相应的DTD定义。默认寻找规则:首先通过网络下载相应的DTD,并认证。网络下载是一个不确定的过程(网速问题、网络中断等),就会出现DTD找不到的情况。而EntityResovle提供了一个寻找DTD的自定义方法,一般我们回吧DTD放到项目中某文件夹下,直接读取本地的DTD交给SAX解析即可。避免了网络交换过程。

loadDocument() 方法


通过SAX解析xml。构造DocumentBuilderFactory解析xml。

registerBeanDefinitions() 方法
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;
    }

protected BeanDefinitionDocumentReader createBeanDefinitionDocumentReader() {
    return BeanDefinitionDocumentReader.class.cast(BeanUtils.instantiateClass(this.documentReaderClass));
}
  1. 使用DefaultBeanDefinitionDocumentReader.class 构造BeanDefinitionDocumentReader 。
  2. 记录已经加载的Bean的个数
  3. 加载及注册Bean
  4. 返回这次加载的Bean的个数

从当前代码中可以看出注册加载Bean委托给 BeanDefinitionDocumentReader .registerBeanDefinitions() 方法处理

registerBeanDefinitions() 方法

public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
    this.readerContext = readerContext;
    Element root = doc.getDocumentElement();
    doRegisterBeanDefinitions(root);
}
protected void doRegisterBeanDefinitions(Element root) {
    BeanDefinitionParserDelegate parent = this.delegate;
    this.delegate = createDelegate(getReaderContext(), root, parent);
    if (this.delegate.isDefaultNamespace(root)) {
        //判断xml的beans标签属性中是否有profile属性,并验证跟web.xml中配置的信息是否匹配
        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)) {
                return;
            }
        }
    }
    preProcessXml(root);
    parseBeanDefinitions(root, this.delegate);
    postProcessXml(root);
    this.delegate = parent;
}
profile 用法
<!-- spring的applicationContext.xml中配置 -->
<beans profile="development">
    ......
</beans>
<beans profile="produce">
    ......
</beans>

<!-- web项目的web.xml中配置 -->
<context-param>
    <param-name>spring.profiles.default</param-name>
    <param-value>production</param-value>
</context-param>

可以使用profile来进行切换线上配置和开发环境配置,方便开发使用

parseBeanDefinitions() 方法

判断是自定义便签还是系统默认标签。
1.系统默认的标签调用parseDefaultElement方法解析
2.用户自定义标签使用parseCustomElement方法解析

parseDefaultElement() 方法

  1. 解析 import 标签
  2. 解析 alias 标签
  3. 解析 bean 标签
  4. 解析 beans 标签

parseCustomElement() 方法

主要解析自定义的标签内容
比如:

<!-- 用户自定义标签 -->
<bean id="xxx" class="test.XXX">
    <mybean:user username="zhangsan"/>
</bean>

<!-- 系统默认实现的自定义标签 -->
<tx:annotation-driven />