关于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. python 字符串转16进制函数

    需要用python处理16进制的文本,比如像下面这个文本 40 80 C0 40 80 C0 40 80 C0 40 80 C0 40 BF CC 40 80 C0 40 80 C0 40 80 C0 ...

  2. solidworks的工程图模板文件和图纸格式文件

    工程图模板文件:drwdot,这个文件是在新建工程图的时候,可以选择的,如下. 图纸格式文件:slddrt,这个文件是进入工程图环境,编辑[图纸属性]的时候,可以选择的.如下. 怎么定制上述两种文件? ...

  3. Kafka connect in practice(3): distributed mode mysql binlog ->kafka->hive

    In the previous post Kafka connect in practice(1): standalone, I have introduced about the basics of ...

  4. 前三次OO作业小结

    I used to be enamored of object-oriented programming. I'm now finding myself leaning toward believin ...

  5. Linux高级指令

    一.hostname指令 作用:操作服务器的主机名(读取,设置) #hostname    作用:表示输出完整的主机名 #hostname -f    作用:表示输出当前主机名中的FQDN(权限定域名 ...

  6. POI导入demo

    前言 使用上篇博文的导入方法,写一个简单的导入demo.其实有了工具类之后就没啥难度了,也就只简单的拿数据.先写个简单的,然后想办法实现动态读取吧,这样读取其实还是比较烦的,每次该模板都要改代码,说到 ...

  7. 2018-2019-2 20165205 网络攻防Exp3免杀原理与实践

    2018-2019-2 20165205 网络攻防Exp3免杀原理与实践 一.实践内容 1.1正确使用msf编码器,msfvenom生成如jar之类的其他文件,veil-evasion,加壳工具,使用 ...

  8. 通过日志来看Spring跨库更新操作的事务

    场景介绍: 一个项目俩个数据源,连接俩个不同的库 数据源初始化 @Configuration @MapperScan(basePackages = "com.qing.mapper.paym ...

  9. ubuntu装bochs的常见问题

    1.Message: dlopen failed for module ‘x’: file not found 原因 未安装bochs-x的缘故 解决办法 sudo apt-get install b ...

  10. 必做作业3:短视频编辑app原型化系统

    本app立足于打造短视频分享交流社区,app不仅有视频编辑的功能,还有视频的分享和收藏功能.系统有登录.注册.找回密码的功能,可以进行账号资料管理,并可以管理自己的视频.分享和收藏.系统可以对视频进行 ...