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进行补充说明
  1. registerBeanDefinition名字一听,这叫注册bean的定义信息,registerBeanDefinition的上面注释中解释了是来实现BeanDefinitionRegistry接口的
  2. beanDefinition(bean的定义)一直是造东西的图纸就叫bean的定义,然后这些bean的定义要存在这个定义的馆里面,而这个馆就叫BeanDefinitionRegistry (Bean的定义中心)
  3. 档案馆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定义信息的呢?

  1. loadBeanDefinitions(beanDefinitionReader); 加载bean的定义信息,它是利用beanDefinitionReader来加载的
  2. beanDefinitionReader这个从XmlBeanDefinitionReader里面来, 它在这里准备了一个beanDefinitionReader(bean定义信息的读取器)
  3. 而beanDefinitionReader叫bean定义信息的读取器,也就是读取那些xml内容的读取器,甚至可以给loadBeanDefinitions下面的XmlBeanDefinitionReader这里打一个断点,来看看这个loadBeanDefinitions读取器是咋读方法的—这一块断点我已取消,因为先放在这
  4. 然后接下来有个方法叫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);
  1. 这个beanDefinitionReader(读取器)里面又组合了一个叫资源加载器:beanDefinitionReader.setResourceLoader(this)
  2. 再回到bean工厂,因为AnnotationConfigApplicationContext这个东西功能很强,它既是BeanFactory又是ResourceLoader(资源加载器),所以这个XmlBeanDefinitionReader就把ResourceLoader(资源加载器)拿到,拿到以后接下来进行loadBeanDefinitions
  • ⑦. loadBeanDefinitions(beanDefinitionReader); 加载bean的配置信息,是如何加载的?
  1. 先getConfigLocations(); 获取所有的配置文件的位置,可以一次传入很多配置文件
	AbstractXmlApplicationContext源码里面:
	//获取配置文件的位置,可以一次传入很多配置文件
	String[] configLocations = getConfigLocations(); 
	    if (configLocations != null) {
	      //读取器来加载所有的bean定义信息,也就是相当于读取器从这一行开始去读取配置文件了
	      reader.loadBeanDefinitions(configLocations);  
	    }
  1. 接下来读取器来加载所有的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的定义信息,(参数一:配置文件传过来即可)
	}
  1. 然后如何读取,继续进入debug的堆栈-如下图所示,count += loadBeanDefinitions(location); 如果传了多个xml配置文件,然后for循环挨个加载每一个配置文件里面所有的组件,最后做一个统一的数值,就知道了加载了多少组件,这是一个递归调用方法–loadBeanDefinitions
  2. 继续debug堆栈跟踪–如下图,这个loadBeanDefinitions方法: 加载指定的配置文件的所有内容
  3. 再跟踪堆栈下图所示:ResourcePatternResolver:它是一个策略模式,策略模式呢只需要调用(getResources)获取它资源的策略,就会得到这些资源(Resource)
  4. 然后把这些资源(Resource) 传入到loadBeanDefinitions这里面才进行加载东西
  5. 资源就是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这个方法进行注册,那它如何注册的?

  1. 在DefaultListableBeanFactory对信息进行校验,验证成功以后,再来看这个beanDefinitionMap.get (beanName);里面有没有这个bean的名字
  2. Spring的底层大量使用到了这样的写法,想要给容器里面注册一个定义信息,但是先上来检查beanDefinitionMap里面有没有,没有才进行注册
  3. 也就是说现在是逐行解析,解析到每一个bean,解析成了以后,就要把它注册到档案馆(DefaultListableBeanFactory)里面,然后先检查档案馆(beanDefinitionMap)里面有没有
  4. 如果没有就注册进去,如果有就不用注册了,至此就把整个注册流程就了解清楚了

③. 源码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创建流程流程图总结的主要内容,如果未能解决你的问题,请参考以下文章

SpringBoot 核心源码解读

flume-ng源码分析-核心组件分析

SpringMVC核心组件之HandlerMapping接口详解

Netty核心组件之NioEventLoop(一)

Netty之4Channel

Spring Web源码之核心组件(拦截器与异常处理)