spring在注入bean的时候,可以通过bean.xml来配置,在xml文件中配置bean的属性,然后spring在refresh的时候,会去解析xml配置文件,这篇笔记,主要来记录。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"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd "> <bean id="userBean" class="com.luban.springsource.xml.UserBean"></bean>
</beans> public class XmlBeanTest {
public static void main(String[] args) {
ClassPathXmlApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
System.out.println(ac.getBean(UserBean.class));
}
}

UserBean就是一个普通的类,没有加任何注解

下面我们来一步一步解析源码

对于Xml格式的,是通过ClassPathXmlApplicationContext来实现的,首先会进入到对应的构造函数中

 public ClassPathXmlApplicationContext(String[] configLocations, boolean refresh, @Nullable ApplicationContext parent) throws BeansException {
super(parent);
this.setConfigLocations(configLocations);
if (refresh) {
this.refresh();
}
}

在这段代码中,第三行这里,主要是把当前要解析的xml文件存放到了一个String数组中,在后面解析的时候,会遍历这个数据,挨个解析xml文件

this.refresh()方法完成了解析和初始化,主要来看这个方法

 @Override
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// Prepare this context for refreshing.
//准备工作包括设置启动时间、是否激活标志位、初始化属性源配置
prepareRefresh(); // Tell the subclass to refresh the internal bean factory.
/**
* 返回一个factory
* xml格式的配置文件,是在这个方法中扫描到beanDefinitionMap中的
* 在org.springframework.web.context.support.XmlWebApplicationContext#loadBeanDefinitions(org.springframework.beans.factory.support.DefaultListableBeanFactory)中会创建一个XmlBeanDefinitionReader来解析xml文件
* 会把bean.xml解析成一个InputStream,然后再解析成document格式
* 按照document格式解析,从root节点进行解析,判断root节点是bean?还是beans?还是import等,如果是bean
* 就把解析到的信息包装成beanDefinitionHolder,然后调用DefaultListablebeanFactory的注册方法将bean放到beanDefinitionMap中
*/
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory(); // Prepare the bean factory for use in this context.
//准备工厂
prepareBeanFactory(beanFactory); try {
// Allows post-processing of the bean factory in context subclasses.
postProcessBeanFactory(beanFactory); // Invoke factory processors registered as beans in the context.
invokeBeanFactoryPostProcessors(beanFactory); // Register bean processors that intercept bean creation.
registerBeanPostProcessors(beanFactory); // Initialize message source for this context.
//初始化MessageSource组件(该组件在spring中用来做国际化、消息绑定、消息解析)
initMessageSource(); // Initialize event multicaster for this context.
/**
* 注册一个多事件派发器
* 先从beanFactory获取,如果没有,就创建一个,并将创建的派发器放到beanFactory中
*/
initApplicationEventMulticaster(); // Initialize other special beans in specific context subclasses.
/**
* 这是一个空方法,在springboot中,如果集成了Tomcat,会在这里new Tomcat()
*/
onRefresh(); // Check for listener beans and register them.
/**
* 注册所有的事件监听器
* 将容器中的时间监听器添加到 applicationEventMulticaster 中
*
*/
registerListeners(); // Instantiate all remaining (non-lazy-init) singletons.
/**
* TODO
* 完成对bean的实例化
*
* 主要的功能都在这里面
*/
finishBeanFactoryInitialization(beanFactory); // Last step: publish corresponding event.
/**
* 当容器刷新完成之后,发送容器刷新完成事件
* publishEvent(new ContextRefreshedEvent(this));
*/
finishRefresh();
} catch (BeansException ex) {
if (logger.isWarnEnabled()) {
logger.warn("Exception encountered during context initialization - " +
"cancelling refresh attempt: " + ex);
} // Destroy already created singletons to avoid dangling resources.
destroyBeans(); // Reset 'active' flag.
cancelRefresh(ex); // Propagate exception to caller.
throw ex;
} finally {
// Reset common introspection caches in Spring's core, since we
// might not ever need metadata for singleton beans anymore...
resetCommonCaches();
}
}
}

在前面的spring源码博客中有说到过,spring初始化的一个大致流程是这样的:

1.把所有的要注入的class解析,存到beanDefinitionMap中

2.循环遍历beanDefinitionMap,把每个beanDefinition经过初始化,实例化等生命周期方法的处理,转变为spring容器中存储的bean

3.把bean存到bean的单实例池中

今天要说的,主要是第一步这里,把class解析到beanDefinition中,对于注解版的配置,是在invokeBeanFactoryPostProcessors(beanFactory);中完成,区分了@ComponentScan、ImportSeletor、ImportBeanDefinitionRegistrar、@Bean这么几种情况

那么,xml是另外一种方式,主要是在ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();中,完成了对bean.xml的解析

这个方法,进来之后,首先是刷新beanFactory(),其中,有一个loadBeanDefinitions(beanFactory);方法

这个方法里面的逻辑还是比较简单的,大致说一下:

1.new一个xmlBeanDefinitionReader

2.遍历前面存放的配置文件的数组,依次解析

3.将xml配置文件转换成inputSource,然后生成一个Document对象; Document doc = this.doLoadDocument(inputSource, resource);  这里的这个方法,就是把当前xml配置文件转解析成一个document对象,对于这个方法,

我没有怎么深入研究,姑且我们就认为这是一个黑盒方法

真正开始解析xml,是从这个方法中开始的

public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
BeanDefinitionDocumentReader documentReader = this.createBeanDefinitionDocumentReader();
int countBefore = this.getRegistry().getBeanDefinitionCount();
documentReader.registerBeanDefinitions(doc, this.createReaderContext(resource));
return this.getRegistry().getBeanDefinitionCount() - countBefore;
}

从这里的registerBeanDefinitions方法开始来解析的


这里是解析root根节点(beans)之后,会获取到当前root下面的子节点,然后依次循环处理子节点的代码;

 private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
if (delegate.nodeNameEquals(ele, "import")) {
this.importBeanDefinitionResource(ele);
} else if (delegate.nodeNameEquals(ele, "alias")) {
this.processAliasRegistration(ele);
} else if (delegate.nodeNameEquals(ele, "bean")) {
this.processBeanDefinition(ele, delegate);
} else if (delegate.nodeNameEquals(ele, "beans")) {
this.doRegisterBeanDefinitions(ele);
} }

从上面说的registerBeanDefinitions到这里的解析方法中间没有几步,不细说了,直接把调用链给出来,可以自己debug再细看一下

我们来说解析的这个方法:

会依次判断beans子节点是哪几个,由于我们只配置了一个<bean,所以,来看处理bean的方法

protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
if (bdHolder != null) {
bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder); try {
BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, this.getReaderContext().getRegistry());
} catch (BeanDefinitionStoreException var5) {
this.getReaderContext().error("Failed to register bean definition with name '" + bdHolder.getBeanName() + "'", ele, var5);
} this.getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
} }
在处理<bean>的时候,会返回一个BeanDefinitionHolder对象,在解析注解版的class的时候,也有好多地方是这样用的,把class解析成BeanDefinitionHolder,然后再把beanDefinitionHolder对应的BeanName,作为BeanDefinitionMap的key,
holder里面的beanDefinition作为value;这里来着重看parseBeanDefinitionElement()方法,因为经过这个方法之后,已经解析成beanDefinition了
@Nullable
public AbstractBeanDefinition parseBeanDefinitionElement(
Element ele, String beanName, @Nullable BeanDefinition containingBean) { this.parseState.push(new BeanEntry(beanName)); String className = null;
if (ele.hasAttribute(CLASS_ATTRIBUTE)) {
//获取xml中bean节点配置的class(就是类的全类名)
className = ele.getAttribute(CLASS_ATTRIBUTE).trim();
}
String parent = null;
if (ele.hasAttribute(PARENT_ATTRIBUTE)) {
parent = ele.getAttribute(PARENT_ATTRIBUTE);
} try {
//根据全类名,通过反射
AbstractBeanDefinition bd = createBeanDefinition(className, parent); //解析<bean>节点的属性信息,比如:lazy/autowire/scope/destroy-method/init-method等,然后把解析到的属性set到beanDefinition中
parseBeanDefinitionAttributes(ele, beanName, containingBean, bd);
//解析bean的子节点 <description>的信息
bd.setDescription(DomUtils.getChildElementValueByTagName(ele, DESCRIPTION_ELEMENT)); parseMetaElements(ele, bd);
//下面两个方法分别解析<bean>的子节点 lookup-method和replaced-method
parseLookupOverrideSubElements(ele, bd.getMethodOverrides());
parseReplacedMethodSubElements(ele, bd.getMethodOverrides()); //解析bean的构造函数,最后调用的是bd.getConstructorArgumentValues().addGenericArgumentValue(valueHolder);
parseConstructorArgElements(ele, bd);
parsePropertyElements(ele, bd);
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;
}
这里面,加了一些注释,不细看每个方法了,其实就是解析每个配置的信息,然后把属性信息set到beanDefinition中

总结
1.首先把入参中待解析的xml文件存到了一个string数组中
2.依次遍历数组,来解析
3.解析的时候,会把xml文件解析成document对象,然后首先从根节点<beans>开始解析
4.解析到<bean>节点的时候,会依次解析bean的属性信息以及bean的子节点信息,把解析到的属性set到beanDefinition中
5.把beanDefinition再put到beanDefinitionMap中

最新文章

  1. jqury 右击事件插件
  2. win7、win8上SaveFileDialog窗口跳不出的问题
  3. MySQL数据库最大连接数
  4. javascript进击(四)HTML DOM
  5. Enabling Active Directory Authentication for VMWare Server running on Linux《转载》
  6. No DEFAULT or UI configuration directive found!
  7. 《天书夜读:从汇编语言到windows内核编程》五 WDM驱动开发环境搭建
  8. 跨域问题实践总结! 上(JSONP/document.domain/window.name)
  9. (一)Git时间--初识版本控制工具
  10. python 字符串格式化转换类型
  11. linux操作系统4 软件包管理
  12. [HTML5] Canvas绘制简单形状
  13. [TJOI2014]Alice and Bob[拓扑排序+贪心]
  14. fatal error C1010: unexpected end of file while looking for precompiled header. Did you forget to add &#39;#include &quot;stdafx.h&quot;&#39; to your source?
  15. acm专题---动态规划
  16. 大杀器TheFatRat
  17. VUE插件整理
  18. js 多张爆炸效果轮播图
  19. 3 不用IDE开发groovy
  20. 在苹果iOS平台中获取当前程序进程的进程名等信息

热门文章

  1. python基础-数字类型及内置方法
  2. kettle计划任务
  3. [考试反思]0919csp-s模拟测试47:苦难
  4. MongoDB自学------(1)MongoDB4.0安装
  5. 最新JetBrainsPyCharm自动部署Python(Django,tornado等)项目至远程服务器
  6. Nginx做缓存服务器
  7. (C#)WPF:关于INotifyPropertyChanged接口的介绍
  8. cn_windows虚拟机配置
  9. 《JAVA 程序员面试宝典(第四版)》之传递与引用篇
  10. Win32窗口消息机制 x Android消息机制 x 异步执行