概念

在较早的spring版本中,xml是配置spring唯一的方式。在如今的spring5.x版本已经spring boot2.x版本中,xml已经不再是唯一的配置手段了,甚至已经不再是推荐的手段。

但是,作为spring元老级的功能,xml配置的方式在可预见的时间内还是不会被淘汰的。所以学习一下spring中读取xml配置文件的方法也还是不错的。

用法

演示一个十分基础的用法,作为讲解原理的起点。

public class XmlBeanDefinitionTest {
  @Test
  public void test(){
    //创建一个实现了BeanDefinitionRegistry的BeanFactory实现
    //DefaultListableBeanFactory也是绝大多数场景下,BeanFactory的具体实现
    DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
    //XmlBeanDefinitionReader创建,从名字可以看出来 这个类是用来从xml文件中读取配置的
    XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(beanFactory);
    //具体解析xml文件中的配置,并注册到BeanDefinitionRegistry中去
    reader.loadBeanDefinitions(new ClassPathResource("xmlBeanDefinition.xml"));
  }
}

实现

进入reader.loadBeanDefinitions(new ClassPathResource(“xmlBeanDefinition.xml”))

可以发现主要的逻辑在XmlBeanDefinitionReader.doLoadBeanDefinitions()方法中

//省略异常捕获代码
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource) throws BeanDefinitionStoreException {
  //将xml文件转化为Document对象
  Document doc = doLoadDocument(inputSource, resource);//@1
  //解析配置
  int count = registerBeanDefinitions(doc, resource);//@2
  if (logger.isDebugEnabled()) {
    logger.debug("Loaded " + count + " bean definitions from " + resource);
  }
  return count;
}

1.将xml文件转换为Document对象

对应上面@1处的方法

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

EntityResolver是啥

可以当做是xml文件转换为对象的一个验证模板,在spring 中有如下实现:

  • BeansDtdResolver:spring中以dtd验证模式来加载xml中的中的配置。会从当前路径下找spring-beans.dtd文件
  • PluggableSchemaResolver:spring中以scheme模式(xsd)加载配置。这个是可插入式的,spring中用的最多的是标签。这个EntityResolver应用了SPI的手法,在使用时,会先到META-INF/spring.schemas路径下读取所有的配置项,每个配置项就是类似这种:
http\://www.springframework.org/schema/beans/spring-beans.xsd=org/springframework/beans/factory/xml/spring-beans.xsd

将一个xsd的url映射到本地的类路径下

  • DelegatingEntityResolver:内部聚合了一个dtdResolver和一个schemaResolver,分别用来处理dtd模式和xsd模式的xml文件
  • ResourceEntityResolver:继承自DelegatingEntityResolver,首先用DelegatingEntityResolver去读取验证文件,如果没有读取到,则在当前路径下找

validationMode又是啥

validationMode就是上述的dtd或者xsd,是xml验证的不同模式。在spring中,使用XmlValidationModeDetector来获取验证模式,检测方法就是判断验证文件是否包含DOCTYPE,如果包含就是dtd,否则就是xsd

DocumentLoader.loadDocument()方法

public Document loadDocument(InputSource inputSource, EntityResolver entityResolver,
                             ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception {

  DocumentBuilderFactory factory = createDocumentBuilderFactory(validationMode, namespaceAware);
  if (logger.isTraceEnabled()) {
    logger.trace("Using JAXP provider [" + factory.getClass().getName() + "]");
  }
  DocumentBuilder builder = createDocumentBuilder(factory, entityResolver, errorHandler);
  return builder.parse(inputSource);
}

spring中也是通过SAX解析XML文档,项创建一个DocumentBuilderFactory,在创建出DocumentBuilder,然后去解析InputSource的文件,返回一个Document对象。具体知识都是xml的,跟spring无关,这里就不再继续介绍了

2.关键:将Document文档转化为BeanDefinition,并注册到BeanDefinitionRegistry中去

对应上面的@2方法

public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
  //BeanDefinitionDocumentReader用于具体解析Document
  BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
  int countBefore = getRegistry().getBeanDefinitionCount();
  //解析并注册BeanDefinition
  documentReader.registerBeanDefinitions(doc, createReaderContext(resource));//@2.1
  return getRegistry().getBeanDefinitionCount() - countBefore;
}

先看看这个createReaderContext()都干了啥

public XmlReaderContext createReaderContext(Resource resource) {
   return new XmlReaderContext(resource, this.problemReporter, this.eventListener,
         this.sourceExtractor, this, getNamespaceHandlerResolver());
}
这个NamespaceHandlerResolver又是啥
public interface NamespaceHandlerResolver {
  //将一个标签的解析映射到一个NamespaceHandler
	NamespaceHandler resolve(String namespaceUri);
}

作用就是根据一个标签找到一个具体的NamespaceHandler,由这个NamespaceHandler去具体解析标签。其默认实现DefaultNamespaceHandlerResolver也使用了SPI机制,会去classpath低下找META-INF/spring.handlers文件。比如大dubbo的jar中就有这个文件:

http\://dubbo.apache.org/schema/dubbo=com.alibaba.dubbo.config.spring.schema.DubboNamespaceHandler
http\://code.alibabatech.com/schema/dubbo=com.alibaba.dubbo.config.spring.schema.DubboNamespaceHandler

就是把标签交给DubboNamespaceHandler去解析

2.1.DocumentReader.registerBeanDefinitions()方法

进入@2.1处的方法,然后兜兜转转,找到具体的逻辑处理方法:DocumentReader.doRegisterBeanDefinition()方法

然后再找到更具体的处理方法😂,DocumentReader.parseBeanDefinitions()

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)) {
            //@2.1.1解析默认标签
						parseDefaultElement(ele, delegate);
					}
					else {
            //@2.1.2解析自定义标签
						delegate.parseCustomElement(ele);
					}
				}
			}
		}
		else {
			delegate.parseCustomElement(root);
		}
	}

转来转去,重要找到了,真的去处理xml里面各个标签的方法了。

解析默认标签

@4的方法用于解析默认标签。

主要的逻辑在DefaultBeanDefinitionDocumentReader.parseDefaultElement()方法中

private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
  //解析import标签
  if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {
    importBeanDefinitionResource(ele);
  }
  //解析alias标签
  else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {
    processAliasRegistration(ele);
  }
  //解析bean标签
  else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {
    processBeanDefinition(ele, delegate);
  }
  //解析嵌套的beans标签
  else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
    // recurse
    doRegisterBeanDefinitions(ele);
  }
}
解析import标签

一般使用如:

用于导入别的配置文件。其解析非常简单:

  • 找到对应的文件
  • 调用前面的reader的loadBeanDefinitions方法
解析alias标签

类似这样

<bean id="userInfo" class="com.pk.study.spring.UserInfo"/>
<alias name="userInfo" alias="hahaha,aaa"/>

解析也很简单,就是调用AliasRegistry接口的registerAlias方法注册一下

解析bean标签

这个是最复杂的标签,有茫茫多的属性配置。所以我单独用一篇文章来分析😂

请见:xml文件的解析之bean标签的解析

2.1.2.解析自定义标签

如果还不清楚如果在spring中使用自定义的标签,可以看下这篇:

结语

在spring中,加载xml文件,着实是非常复杂了。。。

(水平有限,最近在看spring源码,分享学习过程,希望对各位有点微小的帮助。如有错误,请指正~)