SPRIN06_源码之核心组件接口BeanDefinitionDebug创建流程流程图总结
Posted 所得皆惊喜
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了SPRIN06_源码之核心组件接口BeanDefinitionDebug创建流程流程图总结相关的知识,希望对你有一定的参考价值。
①. 环境准备
- ①. 以xml的方式,因为注解的方式还稍微有一点点的不一样,所以在resources下面的beans.xml这里面准备一个cat,然后看看档案馆是如何工作的,而且在工作的过程中最好记录一下整体流程
<?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 class="com.xiaozhi.bean.Person" id="person">
<property name="name" value="tangzhi"></property>
</bean>
<bean class="com.xiaozhi.bean.Cat" id="cat">
<property name="name" value="TOMCAT"></property>
</bean>
</beans>
- ②. 然后再来到测试类MainTest,还是加载这个beans.xml配置文件
- ③. 给档案馆的registerBeanDefinition方法下面的registerBeanDefinition代码中打了一个断点
(可以研究一下,打断点来验证一下这个事情:在beanDefinitionMap里面来搜一下(双击shift进行搜索),既然你是一个map,就先看什么时候调用put方法,那既然找到了put方法就给这个put的上面代码打一个断点,也既在DefaultListableBeanFactory这个类下面的这个registerBeanDefinition方法下面的Assert.hasText()这代码这里打一个断点,来看一下档案馆是如何工作的)
- ④. 对上图的registerBeanDefinition进行补充说明
- registerBeanDefinition名字一听,这叫注册bean的定义信息,registerBeanDefinition的上面注释中解释了是来实现BeanDefinitionRegistry接口的
- beanDefinition(bean的定义)一直是造东西的图纸就叫bean的定义,然后这些bean的定义要存在这个定义的馆里面,而这个馆就叫BeanDefinitionRegistry (Bean的定义中心)
- 档案馆DefaultListableBeanFactory正好实现了BeanDefinitionRegistry (Bean的定义中心)
- ⑤. 看上图就是档案馆DefaultListableBeanFactory实现了BeanDefinitionRegistry (Bean的定义中心),那么只要实现了BeanDefinitionRegistry (Bean定义注册中心),那么自然而然要做的事情要编写人家的方法
双击shift搜索找到BeanDefinitionRegistry(Bean定义的注册中心),然后Ctrl+f12找到这个类的所有方法,这个类里面的方法有很多:其中有一个就叫注册bean的定义信息的方法==registerBeanDefinitio
- ⑥. debug启动后,先来到了标注好的registerBeanDefinition这个方法下面的Assert.hasText这方法里面,这叫注册bean的定义信息,它是从在创建ioc容器(new ApplicationContext)的时候就要进行注册–debug栈往下翻找到MainTest,跟踪Debugger堆栈就可以了解到整个运行流程
②. Debug创建流程
-
①. 在创建ioc容器(new ApplicationContext)的时候,传了配置文件的位置ApplicationContext(“beans.xml”)
-
②. ioc容器创建–ClassPathXmlApplicationContext,这个ClassPathXmlApplicationContext构造器调用里面的this 也既上图所示,也既下图的流程图所示
-
③. 调用refresh(),刷新容器refresh(); 所谓的刷新容器是什么呢?都干了什么活?
内容详解
(1). 在刷新容器的refresh();方法下面有一个叫ConfigurableListableBeanFactory这个工厂,这个工厂就是定义了一些方法,并必须实现它,定义了哪些方法就是到底有多少个bean,bean的定义信息名字等 (2). 在创建BeanFactory的时候,这上面代码注释就是告诉子类去刷新自己内部的工厂,所以这是BeanFactory第一次开始创建的时候调用obtainFreshBeanFactory这个方法来获取刷新好的bean工厂,然后obtainFreshBeanFactory这里面有一个叫refreshBeanFactory刷新整个bean工厂,也就是说整个spring工厂的整个启动,底层叫刷新整个bean工厂(造厂叫刷新厂)
-
④. 先来创建(createBeanFactory)保存所有bean定义信息的档案馆(DefaultListableBean Factory),也就是说先上来创建一个档案馆,并且给这个档案馆里面给一个唯一的id–beanFactory.setSerializationId(getId());,然后开始做一些自定义操作–customizeBeanFactory(beanFactory);但这些都不重要,重要的是在段代码里面有一个createBeanFactory()创建一个保存所有bean定义信息的档案馆,这个DefaultListableBeanFactory档案馆之前都研究过,而这个DefaultListableBeanFactory档案馆里面核心有那些map: beanDefinitionMap,为什么把DefaultListableBeanFactory叫为档案馆?它是个map,保存了造各种东西的图纸
-
⑤. 档案馆(DefaultListableBeanFactory)创建好,初始化好以后有一个方法叫loadBeanDefinitions(beanFactory); 也就是加载所有的bean定义信息,bean的定义信息就是xml配置的各种组件,就叫bean的定义信息
-
⑥. 也就是xml配置的那些组件(bean定义信息),被加载进去了如何加载bean定义信息的呢?
- loadBeanDefinitions(beanDefinitionReader); 加载bean的定义信息,它是利用beanDefinitionReader来加载的
- beanDefinitionReader这个从XmlBeanDefinitionReader里面来, 它在这里准备了一个beanDefinitionReader(bean定义信息的读取器)
- 而beanDefinitionReader叫bean定义信息的读取器,也就是读取那些xml内容的读取器,甚至可以给loadBeanDefinitions下面的XmlBeanDefinitionReader这里打一个断点,来看看这个loadBeanDefinitions读取器是咋读方法的—这一块断点我已取消,因为先放在这
- 然后接下来有个方法叫loadBeanDefinitions加载bean的定义信息,它是把读取器beanDefinitionReader传入进去了,而这个beanDefinitionReader这个读取器里面也有资源加载器
AbstractXmlApplicationContext里面的:
// Configure the bean definition reader with this context's
// resource loading environment.
beanDefinitionReader.setEnvironment(this.getEnvironment());
beanDefinitionReader.setResourceLoader(this);//持有ioc容器的环境类
beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));
// Allow a subclass to provide custom initialization of the reader,
// then proceed with actually loading the bean definitions.
initBeanDefinitionReader(beanDefinitionReader);
loadBeanDefinitions(beanDefinitionReader);
- 这个beanDefinitionReader(读取器)里面又组合了一个叫资源加载器:beanDefinitionReader.setResourceLoader(this)
- 再回到bean工厂,因为AnnotationConfigApplicationContext这个东西功能很强,它既是BeanFactory又是ResourceLoader(资源加载器),所以这个XmlBeanDefinitionReader就把ResourceLoader(资源加载器)拿到,拿到以后接下来进行loadBeanDefinitions
- ⑦. loadBeanDefinitions(beanDefinitionReader); 加载bean的配置信息,是如何加载的?
- 先getConfigLocations(); 获取所有的配置文件的位置,可以一次传入很多配置文件
AbstractXmlApplicationContext源码里面:
//获取配置文件的位置,可以一次传入很多配置文件
String[] configLocations = getConfigLocations();
if (configLocations != null) {
//读取器来加载所有的bean定义信息,也就是相当于读取器从这一行开始去读取配置文件了
reader.loadBeanDefinitions(configLocations);
}
- 接下来读取器来加载所有的bean定义信息–reader.loadBeanDefinitions(configLocations); ,也就是相当于读取器从这一行开始去读取xml配置文件了
AbstractBeanDefinitionReader:
@Override
public int loadBeanDefinitions(String... locations) throws BeanDefinitionStoreException {
Assert.notNull(locations, "Location array must not be null");
int count = 0;
for (String location : locations) { //逐个加载每一个配置文件的内容
count += loadBeanDefinitions(location);
}
return count;
}
@Override //加载指定配置文件的所有内容
public int loadBeanDefinitions(String location) throws BeanDefinitionStoreException {
return loadBeanDefinitions(location, null); //加载bean的定义信息,(参数一:配置文件传过来即可)
}
- 然后如何读取,继续进入debug的堆栈-如下图所示,count += loadBeanDefinitions(location); 如果传了多个xml配置文件,然后for循环挨个加载每一个配置文件里面所有的组件,最后做一个统一的数值,就知道了加载了多少组件,这是一个递归调用方法–loadBeanDefinitions
- 继续debug堆栈跟踪–如下图,这个loadBeanDefinitions方法: 加载指定的配置文件的所有内容
- 再跟踪堆栈下图所示:ResourcePatternResolver:它是一个策略模式,策略模式呢只需要调用(getResources)获取它资源的策略,就会得到这些资源(Resource)
- 然后把这些资源(Resource) 传入到loadBeanDefinitions这里面才进行加载东西
- 资源就是Spring抽取的Resource,在第三篇就详细介绍了Resource资源接口,这个Resource跟ResourcePatternResolver还是一个策略模式,这个ResourcePatternResolver被IOC容器持有,但ioc容器又被BeanDefinitionReader持有,因为BeanDefinitionReader直接把ioc容器给传进去了-如下图
-
⑧. 反正现在就是一句话:ioc容器里面有的东西,直接ioc容器传给BeanDefinitionReader慢慢用去
-
⑨. 然后加载指定配置文件的bean的定义信息,就是利用ResourcePatternResolver资源解析器去加载指定配置文件的所有内容
-
⑩. 然后上来加载所有bean的配置,然后它在这递归调用–loadBeanDefinitions,然后这个loadBeanDefinitions方法里面创建EncodedResource–new EncodedResource(resource) 把resource包装了一下传给了EncodedResource,这就是一个装饰模式
/**
* Load bean definitions from the specified XML file.
* @param resource the resource descriptor for the XML file
* @return the number of bean definitions found
* @throws BeanDefinitionStoreException in case of loading or parsing errors
*/
@Override
public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
return loadBeanDefinitions(new EncodedResource(resource));
}
- ⑩①. EncodedResource是一个InputStreamSource,它把原生的Resource一包装,返回了一个InputStreamSource,这是一个适配器模式
- ⑩②. 连接了两个不同的接口,它把Spring底层规定的resource接口最终能跟InputStreamSource来进行对接,因为在底层能够看到所有真正调用方法的时候,它在这会调用resource的方法–如上图,而因为这个原生的这个流InputStream是spring定义的流,而这个InputStream流又包装成了InputStreamReader流,而这个InputStreamReader就方便多了,读取文件的内容肯定要一次读取一行,而不是读一个一个字节,所以这个InputStreamReader会有相应的编码器StreamDecoder,而这个编码器来进行帮忙读取,还要进行边解码StreamDecoder(如下图所示)
- ⑩③. 接下来继续跟踪debug堆栈–如上图,继续来进行加载,InputSource它把所有的资源拿过来,准备好以后有一个方法叫doLoadBeanDefinitions,就是层层加载,然后继续下边跟踪堆栈-入下图,有一个叫registerBeanDefinitions,这个registerBeanDefinitions方法里面传入了一个doc,事实上它最后引入了第三方的dom解析工具来把整个xml配置变成一个Document文档 也就是org.w3c.dom,这一块Document文档的流程大概了解下就行了
-
⑩④. BeanDefinitionReader利用dom解析把xml成Document文档对象,然后再把Document文档对象再交给BeanDefinitionDocumentReader
-
⑩⑤. 继续跟踪堆栈–下图所示,这是以下步骤的堆栈,就不一一带着来研究堆栈的细节了:然后BeanDefinitionDocumentReader再负责把Document文档对象交给BeanDefinitionParserDelegate(Bean定义解析器委托)
-
⑩⑥. 最终BeanDefinitionParserDelegate(Bean定义解析器委托)把Document文档对象解析成BeanDefinition,这里面也就利用了解释器模式
xml的所有信息解析完的信息并封装在了BeanDefinitionHolder(bean定义信息的持有)中
public class BeanDefinitionHolder implements BeanMetadataElement {
private final BeanDefinition beanDefinition;
private final String beanName;
}
-
⑩⑦. beanDefinition和beanname这两个玩意要注册到档案馆里面,它就为了方便,就先封装到了这个BeanDefinitionHolder(bean定义信息的持有)
-
⑩⑧. 然后就再注册这个玩意,直接拿到BeanDefinitionRegistry(bean定义信息的注册中心),给这个注册中心里面利用registerBeanDefinition这个方法进行注册,那它如何注册的?
- 在DefaultListableBeanFactory对信息进行校验,验证成功以后,再来看这个beanDefinitionMap.get (beanName);里面有没有这个bean的名字
- Spring的底层大量使用到了这样的写法,想要给容器里面注册一个定义信息,但是先上来检查beanDefinitionMap里面有没有,没有才进行注册
- 也就是说现在是逐行解析,解析到每一个bean,解析成了以后,就要把它注册到档案馆(DefaultListableBeanFactory)里面,然后先检查档案馆(beanDefinitionMap)里面有没有
- 如果没有就注册进去,如果有就不用注册了,至此就把整个注册流程就了解清楚了
③. 源码Debug总结
- ①. 源码步骤:
源码步骤:
//1.先传入一个bean的xml文件
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
//2.这个xml配置文件,根据它来创建ioc容器
public ClassPathXmlApplicationContext(String configLocation) throws BeansException {
this(new String[] {configLocation}, true, null);
}
//3.ioc容器底层还要刷新容器
if (refresh) {
refresh();
}
//4.刷新容器的时候。它得先创建bean的工厂
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
//5.创建bean工厂的时候(也叫刷新工厂)
protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
refreshBeanFactory();
return getBeanFactory();
}
//6.//加载所有的bean定义信息
DefaultListableBeanFactory beanFactory = createBeanFactory(); //创建保存所有bean定义信息的档案馆
beanFactory.setSerializationId(getId());
customizeBeanFactory(beanFactory);
loadBeanDefinitions(beanFactory);
this.beanFactory = beanFactory;
//7.beanDefinitionReader去读取传入的xml配置文件的内容()
loadBeanDefinitions(beanDefinitionReader);
//8.读取指定的配置文件
if (configLocations != null) {
//读取器来加载所有的bean定义信息,也就是相当于读取器从这一行开始去读取配置文件了
reader.loadBeanDefinitions(configLocations);
}
//9.读取器挨个遍历每一个指定的配置文件去来解析
count += loadBeanDefinitions(location);
//10.加载指定配置文件的所有内容
@Override
public int loadBeanDefinitions(String location) throws BeanDefinitionStoreException {
return loadBeanDefinitions(location, null); //加载bean的定义信息,(参数一:配置文件传过来即可)
}
//11.利用DocumentReader开始读取整篇文档
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;
}
//12.处理bean的定义信息
parseBeanDefinitions(root, this.delegate);
//13.dom解析当前标签生成BeanDefinitionHolder
@Nullable
public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, @Nullable BeanDefinition containingBean) {
String id = ele.getAttribute(ID_ATTRIBUTE);
String nameAttr = ele.getAttribute(NAME_ATTRIBUTE);
//14.准备创建一个抽象的bean定义信息
AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName, containingBean);
//15.bean定义信息的类名就是xml配置文件指定写的类名
GenericBeanDefinition bd = new GenericBeanDefinition();
bd.setParentName(parentName);
if (className != null) {
if (classLoader != null) {
bd.setBeanClass(ClassUtils.forName(className, classLoader));
}
- ②. 图像展示
以上是关于SPRIN06_源码之核心组件接口BeanDefinitionDebug创建流程流程图总结的主要内容,如果未能解决你的问题,请参考以下文章