概述
在上一篇文章中,讲述了定义在classpath 下的文件,spring是如何加载到内存,并转化为流的过程,那么这篇文章就从xml转化为document 对象开始。
在开始之前,有几个类要先介绍一下,这是在上篇文章中涉及到的,对后续的流程也至关重要。
核心类
-
DefaultListableBeanFactory
该类是整个Bean加载的核心,是spring注册和加载bean的默认实现,类图如下:
类图.png
挑几个比较重要的子类说一下:
- AliasRegistry: 定义对alias(别名)的增删该操作
- BeanDefinitionRegistry : 定义对BeanDefinition的各种增删改操作
- SingletornBeanRegistry: 定义了对单例的注册和获取
- AutowireCapableBeanFactory:提供创建bean, 自动注入,初始化以及应用bean的后处理器
- AbstractAutowireCapableBeanFactory: 综合AbstractBeanFactory 并对 Capable BeanFactory进行实现。
- ConfigurableListableBeanFactory : 配置的清单
- DefaultListableBeanFactory: 综合上面所有的功能,主要是对bean 注册后的处理。
-
XmlBeanDefinitionReader
xml配置文件读取的是Spring中重要的内容,Spring的大部分功能都是以配置作为切入点的。就从该类来梳理一下文件读取,解析,注册的大致脉络。- ResourceLoader: 定义资源加载器
- BeanDefinitionReader: 主要定义资源文件读取并转换为BeanDefinition的各个功能
- BeanDefinitionDocumentReader : 定义读取Document并注册BeanDefinition功能
- BeanDefinitionParserDelegate : 定义解析Element的各种方法
上篇文章分析到此处,本篇便从此开始:

进入doLoadBeanDefinitions()方法 :
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)throws BeanDefinitionStoreException {
try {
//转化为Document 对象
Document doc = doLoadDocument(inputSource, resource);
//启动对Bean定义解析的详细过程,会用到Spring Bean的配置规则
return registerBeanDefinitions(doc, resource);
}
//删除了部分catch语句
catch (....) {
throw ex;
}
}
看一下 doLoadDocument()方法:

这里将getEntityResolver()和getValidationModeForResource()展开说一下,说之前先引入两个概念,XML文件的验证模式:
- DTD: 文档类型定义
-
XSD: XMLSchema , 描述XML文档是否符合其要求。
Spring用来检测验证模式的办法就是判断是否包含DOCTYPE,如果包含就是DTD,否则就是XSD .
程序继续往下走,
image.png
此处的加载跳转到了DefaultDocumentLoader 类中的loadDocument()方法,在该方法中有一个EntityResolver参数, 来说下这个参数的作用。
官网中是这样解释的,如果SAX应用程序需要实现自定义处理外部实体,则必须实现此接口并使用setEntityResolver方法向SAX驱动注册一个实例。
看下createDocumentBuilderFactory() 方法:


上面的代码没有啥特别的,我直接罗列了,通过SAX解析XML文档的套路差不多,都是先创建DocumentBuidlerFactory,在创建DocumentBuilder,进而解析inputScource来返回Document对象。
解析XML的方式: Dom | Sax | Dom4j | JDom
这里要说的是,Spring解析Xml采用的是Dom,而这种解析方式,是将文件全部加载到内存,当xml文件较大时,对内存耗费比较大,容易影响解析性能并造成内存溢出。
至此,Spring IOC容器根据定位的Bean定义资源文件,并将其加载读入转换为document对象的过程完成。
下面继续分析:
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
//创建DocumentReader来对XML格式的BeanDefinition进行解析
BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
//获得容器中已经存在的Bean数量
int countBefore = getRegistry().getBeanDefinitionCount();
//具体的解析过程在这个方法中执行
documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
return getRegistry().getBeanDefinitionCount() - countBefore;
}
createBeanDefinitionDocumentReader()方法返回的是DefaultDefinitionDocumentReader对象。进入DefaultDefinitionDocumentReader 后发现这个方法的重要目的就是提取root ,以便于再次将root作为参数,继续BeanDefinition的注册。此处便进入了解析的核心部分。
Bean定义资源的解析分为以下两个过程:
- 通过调用xml解析器,将资源定义文件转换为Document对象,document对应并没有按照spring bean的规则进行解析。
- 在完成通用xml解析之后,按照Spring的Bean规则对Document对象进行解析,这个过程是在documentReader中实现的。具体的操作是由- -DefaultBeanDefinitionDocumentReader完成的。
处理的结果由BeanDefinitionHolder对象持有。解析过程由BeanDefinitionParserDelegate来实现
//根据Spring DTD 对bean定义的规则解析Bean定义Document对象
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
//获取xml描述符
this.readerContext = readerContext;
logger.debug("Loading bean definitions");
//获得Document的根对象
Element root = doc.getDocumentElement();
doRegisterBeanDefinitions(root);
}
protected void doRegisterBeanDefinitions(Element root) {
BeanDefinitionParserDelegate parent = this.delegate;
//BeanDefinitionParserDelegate中定义了Spring Bean定义的XML文件的各种元素
//默认BeanDefinitionParserDelegate会处理”http://www.springframework.org/schema/beans“命名空间下元素及其属性
this.delegate = createDelegate(getReaderContext(), root, parent);
if (this.delegate.isDefaultNamespace(root)) {
//对于默认的命名空间,首先开始的是对profile属性解析
//profile用得最多的是DataSource在不同环境下使用不同的bean
//spring使用StringTokenizer来进行字符串的分割,但是jdk为了兼容性是推荐使用String.split()方法的:
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);
//从Document的根元素开始进行Bean定义的document对象
parseBeanDefinitions(root, this.delegate);
//在解析Bean定义之后,进行自定义的解析,增加解析过程的可扩展性。空实现
postProcessXml(root);
this.delegate = parent;
}
protected BeanDefinitionParserDelegate createDelegate(XmlReaderContext readerContext, Element root, BeanDefinitionParserDelegate parentDelegate) {
BeanDefinitionParserDelegate delegate = new BeanDefinitionParserDelegate(readerContext);
delegate.initDefaults(root, parentDelegate);
return delegate;
}
解析document文件,不同的命名的空间采用不同的方法处理
protected void parseBeanDefinitions(Element root,BeanDefinitionParserDelegate delegate) {
//Bean定义的Document对象使用了Spring默认的XML命名空间
if (delegate.isDefaultNamespace(root)) {
//获取Document对象的所有子节点,NodeList的含义上面进行了介绍
NodeList nl = root.getChildNodes();
for (int i = 0; i < nl.getLength(); i++) {
Node node = nl.item(i); //获取Node节点
//判断Node是否是Element类型
if (node instanceof Element) {
Element ele = (Element) node;
//判断该元素是否属于Spring定义Bean的默认命名空间
if (delegate.isDefaultNamespace(ele)) {
//使用Spring的Bean规则解析元素节点
parseDefaultElement(ele, delegate);
}
else {
//使用用户自定义的规则进行解析
delegate.parseCustomElement(ele);
}
}
}
}
else {
//没有使用spring默认的命名空间,则使用用户自定义的解析规则解析
delegate.parseCustomElement(root);
}
}
使用spring的Bean规则解析Document元素节点,有些元素节点是<import> <bean> <alias> <beans> 等,则分别进行解析
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);
}
}
在4中标签中对bean标签的解析也最为复杂也最为重要,主要看一下对bean标签的解析过程。
protected void processBeanDefinition(Element ele,BeanDefinitionParserDelegate delegate) {
//BeanDefinitionHolder是对BeanDefinition的封装,delegate解析完成后使用holder封装,bdHolder 包含了id ,别名和BeanDefinition的信息
BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
if (bdHolder != null) {
bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
try {
//向容器注册封装后的实例
BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
}
catch (BeanDefinitionStoreException ex) {
....
}
//在Beandefinition向IoC容器注册完成以后,发送消息
getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
}
}
下面看一下parseBeanDefinitionElement()方法的具体实现,对于BeanDefinition的注册时存放在ConcurrentHashMap中的,beanName变为存放的健,过程分为4个部分:
- 委托BeanDefinitionDelegate类的parseBeanDefinitionElement()方法进行解析,返回BeanDefinitionHolder ,经过这个方法后,bdHolder实例已经包含配置中 的各种基本属性,例如 class , name , id 等
- 当返回的Holder不为空,若存在默认标签的情况下在有自定义的属性,还需再次对自定义的标签进行解析
- 解析完成后,需要向DefaultListableBeanFactory进行注册,注册操作委托给了BeanDefinitionReaderUtils类
- 最后发出响应事件,通知相关的监听器,这个bean已经加载完成了。
下面看具体的实现。
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));
}
//将id赋值给beanName
String beanName = id;
if (!StringUtils.hasText(beanName) && !aliases.isEmpty()) {
beanName = aliases.remove(0);
if (logger.isDebugEnabled()) {
logger.debug("....");
}
}
if (containingBean == null) {
checkNameUniqueness(beanName, aliases, ele);
}
//该方法引发对Bean元素的详细解析
AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName, containingBean);
//程序执行到此处,整个<bean>标签的解析就算结束了。一个beanDefinition就创建好了
if (beanDefinition != null) {
if (!StringUtils.hasText(beanName)) {
try {
if (containingBean != null) {
//如果不存在beanName,那么根据Spring中的提供的命名规则为当前bean生成对应的beanName
beanName = BeanDefinitionReaderUtils.generateBeanName(beanDefinition, this.readerContext.getRegistry(), true);
}
else {
beanName = this.readerContext.generateBeanName(beanDefinition);
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("....");
}
}
catch (Exception ex) {
error(ex.getMessage(), ele);
return null;
}
}
String[] aliasesArray = StringUtils.toStringArray(aliases);
//将信息封装到BeanDefinitionHolder中
return new BeanDefinitionHolder(beanDefinition, beanName, aliasesArray);
}
return null;
}
BeanDefinition可以看成是对<bean>定义的抽象,这个数据对象中封装的数据大多都是与<bean>定义相关的,也有很多就是我们在定义Bean时看到的那些Spring标记。这个BeanDefinition数据类型是非常重要的,它封装了很多基本数据,这些都是Ioc容器需要的,上面代码最后我们返回了一个BeanDefinitionHolder实例,这个实例封装了beanDefinition,beanName, aliase三个信息,beanDefinition中也包含了beanName,aliase信息。
public AbstractBeanDefinition parseBeanDefinitionElement(Element ele, String beanName, BeanDefinition containingBean) {
this.parseState.push(new BeanEntry(beanName));
//这里只读取定义的<bean>中设置的class名字,然后载入到BeanDefinition中去,只是做个记录,并不涉及对象的实例化过程,对象的实例化过程实际是在依赖注入时完成的
String className = null;
if (ele.hasAttribute(CLASS_ATTRIBUTE)) {
className = ele.getAttribute(CLASS_ATTRIBUTE).trim();
}
try {
//解析parent属性
String parent = null;
if (ele.hasAttribute(PARENT_ATTRIBUTE)) {
parent = ele.getAttribute(PARENT_ATTRIBUTE);
}
//创建用于承载属性的AbstractBeanDefinition类型的GenereicBeanDefinition
//这里生成需要的BeanDefinition对象,为Bean定义的信息做载入做准备
AbstractBeanDefinition bd = createBeanDefinition(className, parent);
//这里对当前Bean元素进行属性解析,并设置description信息
parseBeanDefinitionAttributes(ele, beanName, containingBean, bd);
bd.setDescription(DomUtils.getChildElementValueByTagName(ele, DESCRIPTION_ELEMENT));
//解析元数据
parseMetaElements(ele, bd);
//解析lookup-method属性
parseLookupOverrideSubElements(ele, bd.getMethodOverrides());
//解析replaced-method属性
parseReplacedMethodSubElements(ele, bd.getMethodOverrides());
//解析构造函数参数
parseConstructorArgElements(ele, bd);
//解析property子元素
parsePropertyElements(ele, bd);
//解析qualifier子元素
parseQualifierElements(ele, bd);
bd.setResource(this.readerContext.getResource());
bd.setSource(extractSource(ele));
//返回BeanDefinition对象
return bd;
} catch (Throwable ex) {
...
}
finally {
this.parseState.pop();
}
return null;
}
从上面的代码可以看出,要解析属性首先要创建用于承载属性的实例,也就是创建GenericBeanDefinition类型的实例,而代码createBeanDefinition(className,parent)的作用就是实现此功能。创建完承接的实例后,便可以进行各种属性的解析了,首先进行解析的是在<bean></bean>标签中定义的各种属性,如scope, singleton,abstract,lazy-init等,然后再解析子标签中的属性,如:lookup-method ,constructor-arg等。解析完成之后关联到实例上,之所以能进行关联,是因为xml中所有的配置都能在GenericBeanDefinition的实力类中找到对应的配置。 此时容器还没有起作用,要想起作用,需要向容器进行注册。
分析到这,已经完成了xml文件向BeanDefinition的转化,每个一个<bean>标签都会转化成一个BeanDefinition对应的实体。实体中包含了<bean>中定义的所有属性。
上面都是默认标签的解析,spring其实还支持自定义标签的解析,这里就不再赘述了,感兴趣的可以查看一下dubbo是如何解析的。大致包含如下几个步骤:
- 创建一个需要扩展的组件
- 定义一个xsd文件描述组件的内容
- 创建一个文件,实现BeanDefinitionParser接口,用来解析XSD文件中的定义和组件定义
- 创建一个Handler文件,扩展自NamespaceHandlerSupport,目的是将组件注册到Spring容器
- 编写Spring.handlers 和 Spring.schemas文件。
最后以一张图来结束本篇文章

下一篇文章, bean的载入
网友评论