关于IOC容器的初始化,结合之前SpringMVC的demo,对其过程进行一个相对详细的梳理,主要分为几个部分:

一、IOC的初始化过程,结合代码和debug过程重点说明

1、 为什么要debug?

答:直接自己从源码看第一遍,会有一个初步的认识;但是看完之后,会因为没有实际走一遍执行而无法验证自己认知的过程;另外就是:因为基于接口本身会有多个实现,因此在很多情况下,通过Ctrl+B直接会进入到接口中,但是方法的实现体为空,通过Ctrl+Alt+B查看具体实现,会出现多个类都实现了这个方法,具体是哪一个,无法准确确定,容易导致理解失误

2、 初始化的阶段及对应调用到的方法

从SpringMVC的demo中的web.xml配置项可以看到:

<listener>
    <listener-class>

org.springframework.web.context.ContextLoaderListener

</listener-class>
</listener>

从ContextLoaderListener进入,这是入口,之后会经过refresh()过程,refresh()过程中会将IOC容器初始化完成,其中重点有:load阶段、process阶段、register阶段;接下来会从入口以及后续的三个阶段进行介绍。

二、详解

入口部分:

进入到ContextLoaderListener类,ContextLoaderListener实现了ServletContextListener接口,该接口中有两个default方法,分别是:

//接收Web应用程序初始化过程正在启动的通知

default public void contextInitialized(ServletContextEvent
sce) {}

//接收ServletContext即将关闭的通知

default public void contextDestroyed(ServletContextEvent
sce) {}

ContextLoaderListener类中未保持原有的default,重新做了实现:

/**
* Initialize the root web application context.
*/
@Override
public void contextInitialized(ServletContextEvent event) {
initWebApplicationContext(event.getServletContext());
}

然后进入到父类ContextLoader类的initWebApplicationContext方法中,该方法中会继续通过:configureAndRefreshWebApplicationContext

最终调用到:AbstractApplicationContext中的refresh(),至此进入到IOC的load阶段(下方为abstractApplicationContext的类图)

load过程:refresh()

进入到重要逻辑分析 ——》ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

一步步往里走,到AbstractRefreshableApplicationContext中,具体如下:
protected final void refreshBeanFactory() throws BeansException {
//判断是否已经有beanFactory了,有则销毁
if (hasBeanFactory()) {
//销毁Beans
destroyBeans();
//将BeanFactory的所有相关内容全部置为null,其中包含:
//serializedID为null,并且对象引用本身置为null
//即等GC了
closeBeanFactory();
}
try {
//创建BeanFactory
DefaultListableBeanFactory beanFactory = createBeanFactory();
beanFactory.setSerializationId(getId());
customizeBeanFactory(beanFactory);
//加载BeanDefinitions
loadBeanDefinitions(beanFactory);
synchronized (this.beanFactoryMonitor) {
this.beanFactory = beanFactory;
}
}
catch (IOException ex) {
throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
}

从这里的createBeanFactory()跟进去可以进入看到是new了一个DefaultListableBeanFactory

DefaultListableBeanFactory的类图如下:

之后下一步loadBeanDefinitions(beanFactory)——》XmlWebApplicationContext类的loadBeanDefinitions

中(这里就是会有多个实现的情况出现,直接通过demo单步调试跟进确定程序走向),之后会new一个XmlBeanDefinitionReader实例,是以前面new出来的beanFactory为参数,这个xmlReaderDefinitionReader会作为参数传入到loadBeanDefinitions(XmlBeanDefinitionReader reader)方法中,之后一级一级进行转换,最终进入到:AbstractBeanDefinitionReader类中的LoadDefinitions,这个时候参数经过了前面的各种过程的转换,最终进入到了doLoadBeanDefinitions(inputSource, encodedResource.getResource())

这里做了层层封装,每一次从外层进入到里层的具体实现和调用上,主要变化的是:参数形式,可以看到:从getConfigLocations()得到配置文件的路径,默认路径是:

/** Default config location for the root context. */
public static final String DEFAULT_CONFIG_LOCATION = "/WEB-INF/applicationContext.xml";

再到基于得到的配置路径,对其进行循环loaderDefinitions,在每一层循环中,是针对单一的一个String configLocation,之后将String类型的location转换成Resource,再转换成InputStream,之后转成InputSource,最终在doLoadBeanDefinitions中将直接用InputSource的实例作为参数,层层递进层层转换:

//真正从特定XML文件中加载bean definitions的方法(这里将exception的内容没有全部放进来)

protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)

      throws BeanDefinitionStoreException {

   try {

      //加载————》得到一个doc对象。其实最后一个是DOMParser类的对象

      //至此,整个过程中不断去将File先解析之后再转换成输入流,再最终得到Document doc,加载完成

      Document doc = doLoadDocument(inputSource, resource);

      //注册:具体做的事项是:

      //1、解析xml

      //2、将解析得到的BeanName和beanDefinition都存储到beanDefinitionMap中进行注册

      int count = registerBeanDefinitions(doc, resource);

      if (logger.isDebugEnabled()) {

         logger.debug("Loaded " + count + " bean definitions from " + resource);

      }

      return count;

   }
 

其中,会先doLoadDocument方法调用得到一个DomParser的对象;至此加载完成;然后进入到registerBeanDefinitions的过程,其中:会有process过程 + register过程

process过程:

public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
//new一个BeanDefinitionDocumentReader对象
BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
int countBefore = getRegistry().getBeanDefinitionCount();
//注册BeanDefinitions
//使用documentReader实例调用registerBeanDefinitions方法,具体操作内容包含:
//(1)BeanDefinitionDocumentReader处理Document元素时,将Document元素的解析工作委托给
// BeanDefinitionParserDelegate处理,其中会将beanName做一个唯一值的转换,为后续的register到map中提供前提
//(2)判断BeanDefinitionMap中是否有某个k,v已经存在,有的话更新Definition的v值,无则直接put k,v
//DefaultBeanDefinitionDocumentReader 实现了 BeanDefinitionDocumentReader接口
documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
return getRegistry().getBeanDefinitionCount() - countBefore;
}

其中process过程在registetBeanDefinitions跟进到下一步中,即:DefaultBeanDefinitionDocumentReader类的registerBeanDefinitions方法,看到:doRegisterBeanDefinitions方法,看到这里就可以兴奋一下啦,真正做事情的来了:

其中关键步骤就两部分:第一部分是createDelegate并赋值给delegate字段;之后进行processxml三步曲 preprocess、parse、postprocess

protected void doRegisterBeanDefinitions(Element root) {
//先赋值给parent,之后创建createDelegate重新赋值给delegate
BeanDefinitionParserDelegate parent = this.delegate; //在DefaultBeanDefinitionDocumentReader处理Document元素时,
// 将Document文档内元素具体解析工作委托给BeanDefinitionParserDelegate类来处理
this.delegate = createDelegate(getReaderContext(), root, parent); if (this.delegate.isDefaultNamespace(root)) {
String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
if (StringUtils.hasText(profileSpec)) {
String[] specifiedProfiles = StringUtils.tokenizeToStringArray(
profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
// We cannot use Profiles.of(...) since profile expressions are not supported
// in XML config. See SPR-12458 for details.
if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {
if (logger.isDebugEnabled()) {
logger.debug("Skipped XML bean definition file due to specified profiles [" + profileSpec +
"] not matching: " + getReaderContext().getResource());
}
return;
}
}
} //预处理Xml
preProcessXml(root);
//解析BeanDefinitions
parseBeanDefinitions(root, this.delegate);
//后置处理Xml
postProcessXml(root); this.delegate = parent;
}

进入到parseBeanDefinitions(root, this.delegate)中,跟到具体实现中:

//处理给定的bean节点,解析bean definitions,并且通过registry进行注册
protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
//这个BeanDefinitionHolder从命名上来看:
//分析含义是:用来包裹存储一个BeanDefinition的容器,因为叫做Holder
//实际是:确实是用来存储这个BeanDefinition的包裹,其中对beanName做了一个特定处理,将之转换成了唯一值 //并且进行详细的解析,包含:对于set、list、map等
BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
if (bdHolder != null) {
bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
try {
// Register the final decorated instance.
//得到了一个自认为完备的beanDefinition了,然后进行注册
BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
}
catch (BeanDefinitionStoreException ex) {
getReaderContext().error("Failed to register bean definition with name '" +
bdHolder.getBeanName() + "'", ele, ex);
}
// Send registration event.
getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
}
}

register过程:

从上面代码中的BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry())会进入到类BeanDefinitionUtils对static方法registerBeanDefinition的调用:

public static void registerBeanDefinition(
BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)
throws BeanDefinitionStoreException { // Register bean definition under primary name.
String beanName = definitionHolder.getBeanName();
//根据name进行注册,因为name是唯一性的了
//具体里面就是直接调用了map.put,将其放入到beanDefinitionMap中
registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition()); // Register aliases for bean name, if any.
String[] aliases = definitionHolder.getAliases();
if (aliases != null) {
for (String alias : aliases) {
registry.registerAlias(beanName, alias);
}
}
}

之后跟进到registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition()) 最终会进入到DefaultListableBeanFactory类中:

摘取方法中的关键逻辑:

public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
throws BeanDefinitionStoreException { //从map中取出beanName的key对应的Definition的对象
BeanDefinition existingDefinition = this.beanDefinitionMap.get(beanName);
if (existingDefinition != null) { }
//为map中的beanName的key值重新设置对应的v值
this.beanDefinitionMap.put(beanName, beanDefinition); }

其中beanDefintionMap的定义如下:

/** Map of bean definition objects, keyed by bean name. */
private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>(256);

二、将以上梳理的整个过程通过时序图的方式,更清晰地展现出调用关系:(图片若无法看清的,可以查看大图或者下载)

【Spring源码解析】—— 结合SpringMVC过程理解IOC容器初始化的更多相关文章

  1. spring源码学习之路---深度分析IOC容器初始化过程(四)

    作者:zuoxiaolong8810(左潇龙),转载请注明出处,特别说明:本博文来自博主原博客,为保证新博客中博文的完整性,特复制到此留存,如需转载请注明新博客地址即可. 最近由于工作和生活,学习耽搁 ...

  2. Spring源码解析二:IOC容器初始化过程详解

    IOC容器初始化分为三个步骤,分别是: 1.Resource定位,即BeanDefinition的资源定位. 2.BeanDefinition的载入 3.向IOC容器注册BeanDefinition ...

  3. spring 源码解析

    1. [文件] spring源码.txt ~ 15B     下载(167) ? 1 springн┤┬вио╬Ш: 2. [文件] spring源码分析之AOP.txt ~ 15KB     下载( ...

  4. Spring源码解析——循环依赖的解决方案

    一.前言 承接<Spring源码解析--创建bean>.<Spring源码解析--创建bean的实例>,我们今天接着聊聊,循环依赖的解决方案,即创建bean的ObjectFac ...

  5. Spring源码解析系列汇总

    相信我,你会收藏这篇文章的 本篇文章是这段时间撸出来的Spring源码解析系列文章的汇总,总共包含以下专题.喜欢的同学可以收藏起来以备不时之需 SpringIOC源码解析(上) 本篇文章搭建了IOC源 ...

  6. Spring源码解析之八finishBeanFactoryInitialization方法即初始化单例bean

    Spring源码解析之八finishBeanFactoryInitialization方法即初始化单例bean 七千字长文深刻解读,Spirng中是如何初始化单例bean的,和面试中最常问的Sprin ...

  7. Spring源码解析 - AbstractBeanFactory 实现接口与父类分析

    我们先来看类图吧: 除了BeanFactory这一支的接口,AbstractBeanFactory主要实现了AliasRegistry和SingletonBeanRegistry接口. 这边主要提供了 ...

  8. Spring源码解析-ioc容器的设计

    Spring源码解析-ioc容器的设计 1 IoC容器系列的设计:BeanFactory和ApplicatioContext 在Spring容器中,主要分为两个主要的容器系列,一个是实现BeanFac ...

  9. Spring源码解析之PropertyPlaceholderHelper(占位符解析器)

    Spring源码解析之PropertyPlaceholderHelper(占位符解析器) https://blog.csdn.net/weixin_39471249/article/details/7 ...

随机推荐

  1. AX2012 ERP 维度相关表数据关系图

    AX2012比AX2009可怜的几个维度来说,太丰富了,可以无数个啊.维度多了,如何使用以及管理是个问题.这个需要在做调研时,充分分析讨论确定.以下为维度表关联关系,在做SQL取值时需要了解下,比如在 ...

  2. hex转mif文件 verilog

    用FPGA来跑ARM 核的时候,刚开始将Keil编译产生的hex文件拿来仿真和下到板子上的时候,发现程序运行不正确.细细观察仿真波形发现,在Altera的ROM IP中直接调用Keil产生的hex文件 ...

  3. 编译在docker alpine中可用的go程序

    get docker image docker pull golang build docker run -it --rm -v `pwd`:/root/src -w /root/src golang ...

  4. bs4源码

    Beautiful源码: """Beautiful Soup Elixir and Tonic "The Screen-Scraper's Friend&quo ...

  5. db powerdesign CDM、LDM、PDM、OOM的区别

        导读 在本篇文章中,你将会了解到PowerDesigner工具中的三种模型CDM,OOM,PDM的区别和联系. PowerDesigner 简称PD,是一种数据建模工具,适合于开发大型应用系统 ...

  6. springboot学习一:快速搭建springboot项目

    1.idea创建springboot工程 JDK选择1.8以上的版本 选择springboot的版本和添加配置项 新建一个HelloController,测试 访问 http://localhost: ...

  7. Laragon+PHP7中开启xdebug

    状态 :laragon+php7.2,按管方做法要求用的是xdebug2.7.结果2.7版本放进去一打断点就挂了,于是换成2.6的版本, php.ini中配置如下: [Xdebug] zend_ext ...

  8. ubuntu10.04 搭建海思开发环境

    (1)Ubuntu 10.04.4 LTS (Lucid Lynx) 下载地址:http://old-releases.ubuntu.com/releases/lucid/ (2)passwd roo ...

  9. FPGA——流水灯(一)

    对于FPGA的结构原理,先不进行全面的了解,先能根据教程程序看得懂,写得出来跑起来.慢慢的了解程序运行的原理,各种语法的使用. 今天对流水的程序有一个认识,熟悉软件的使用,语法规则,原理.以正点原子的 ...

  10. frist Django app — 三、 View

    前面已经说过了Django中model的一些用法,包括orm,以及操作的api,接下来就是搭一些简单的界面学习view——Django中的view.主要介绍以下两个方面: url映射 请求处理 模板文 ...