【042期】面试再被问到 Spring 容器 IOC 初始化过程,就这样“砸”他!

这一步无需太过在意!

第二步:既然是new ClassPathXmlApplicationContext() 那么就调用构造器嘛!

第三步:

第四步:

好,我们跟着第三步中的super(parent),再结合上面第三节的第 6 小点 UML 图一步一步跟踪,然后我们来到AbstractApplicationContext的这个方法:

那么里边的resourcePatternResolver的类型是什么呢?属于第三节说的 6 大步骤的哪个部分呢?通过跟踪可以看到它的类型是ResourcePatternResolver类型的,而ResourcePatternResolver又是继承了ResourceLoader接口,因此属于加载资源模块,如果还不清晰,咱们再看看ResourcePatternResolver的源码即可,如下图:

对吧!不仅继承ResourceLoader接口,而且只定义一个getResources()方法用于返回Resource[]资源集合。再者,这个接口还使用了策略模式,其具体的实现都在实现类当中,好吧!来看看 UML 图就知道了!

PathMatchingResourcePatternResolver这个实现类呢!它就是用来解释不同路径资源的,比如你传入的资源路径有可能是一个常规的url, 又或者有可能是以classpath*前缀,都交给它处理。

ServletContextResourcePatternResolver这个实现类顾名思义就是用来加载Servlet上下文的,通常用在 web 中。

第五步:

接着第四步的方法,我们在未进入第四步的方法时,此时会对AbstractApplicationContext进行实例化,此时this对象的某些属性被初始化了(如日志对象),如下图:

接着进入getResourcePatternResolver()方法:

第四步说了,PathMatchingResourcePatternResolver用来处理不同的资源路径的,怎么处理,我们先进去看看!

如果找到,此时控制台会打印找到用于OSGi包URL解析的Equinox FileLocator日志。没打印很明显找不到!

运行完成返回setParent()方法。

第六步:

如果父代是非null,,则该父代与当前this应用上下文环境合并。显然这一步并没有做什么事!parent显然是null的,那么就不合并嘛!还是使用当前this的环境。

做个总结:前六步基本上做了两件事:

  • 1、初始化相关上下文环境,也就是初始化ClassPathXmlApplicationContext实例

  • 2、获得一个resourcePatternResolver对象,方便第七步的资源解析成Resource对象

第七步:

第七步又回到刚开始第三步的代码,因为我们前面 6 步已经完成对super(parent)的追踪。让我们看看setConfigLocation()方法是怎么一回事~

/**

  • Set the config locations for this application context.//未应用上下文设置资源路径

  • If not set, the implementation may use a default as appropriate.//如果未设置,则实现可以根据需要使用默认值。

*/

public void setConfigLocations(String… locations) {

if (locations != null) {//非空

Assert.noNullElements(locations, “Config locations must not be null”);//断言保证locations的每个元素都不为null

this.configLocations = new String[locations.length];

for (int i = 0; i < locations.length; i++) {

this.configLocations[i] = resolvePath(locations[i]).trim();//去空格,很好奇resolvePath做了什么事情?

}

}

else {

this.configLocations = null;

}

}

进入resolvePath()方法看看:

/**

  • 解析给定的资源路径,必要时用相应的环境属性值替换占位符,应用于资源路径配置。

  • Resolve the given path, replacing placeholders with corresponding

  • environment property values if necessary. Applied to config locations.

  • @param path the original file path

  • @return the resolved file path

  • @see org.springframework.core.env.Environment#resolveRequiredPlaceholders(String)

*/

protected String resolvePath(String path) {

return getEnvironment().resolveRequiredPlaceholders(path);

}

进入getEnvironment()看看:

/**

  • {@inheritDoc}

  • If {@code null}, a new environment will be initialized via

  • {@link #createEnvironment()}.

*/

@Override

public ConfigurableEnvironment getEnvironment() {

if (this.environment == null) {

this.environment = createEnvironment();

}

return this.environment;

}

进入createEnvironment(), 方法,我们看到在这里创建了一个新的StandardEnviroment对象,它是Environment的实现类,表示容器运行的环境,比如 JDK 环境,Servlet 环境,Spring 环境等等,每个环境都有自己的配置数据,如System.getProperties()System.getenv()等可以拿到 JDK 环境数据;ServletContext.getInitParameter()可以拿到 Servlet 环境配置数据等等, 也就是说 Spring 抽象了一个Environment来表示环境配置。

生成的StandardEnviroment对象并没有包含什么内容,只是一个标准的环境,所有的属性都是默认值。

总结:对传入的path进行路径解析

第八步:这一步是重头戏

先做个小结:到现在为止,我们拥有了以下实例:

现在代码运行到如下图的refresh()方法:

看一下这个方法的内容是什么?

@Override

public void refresh() throws BeansException, IllegalStateException {

synchronized (this.startupShutdownMonitor) {

// 刷新前准备工作,包括设置启动时间,是否激活标识位,初始化属性源(property source)配置

prepareRefresh();

// 创建beanFactory(过程是根据xml为每个bean生成BeanDefinition并注册到生成的beanFactory

ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

//准备创建好的beanFactory(给beanFactory设置ClassLoader,设置SpEL表达式解析器,设置类型转化器【能将xml String类型转成相应对象】,

//增加内置ApplicationContextAwareProcessor对象,忽略各种Aware对象,注册各种内置的对账对象【BeanFactory,ApplicationContext】等,

//注册AOP相关的一些东西,注册环境相关的一些bean

prepareBeanFactory(beanFactory);

try {

// 模板方法,为容器某些子类扩展功能所用(工厂后处理器)这里可以参考BeanFactoryPostProcessor接口的postProcessBeanFactory方法

postProcessBeanFactory(beanFactory);

// 调用所有BeanFactoryPostProcessor注册为Bean

invokeBeanFactoryPostProcessors(beanFactory);

// 注册所有实现了BeanPostProcessor接口的Bean

registerBeanPostProcessors(beanFactory);

// 初始化MessageSource,和国际化相关

initMessageSource();

// 初始化容器事件传播器

initApplicationEventMulticaster();

// 调用容器子类某些特殊Bean的初始化,模板方法

onRefresh();

// 为事件传播器注册监听器

registerListeners();

// 初始化所有剩余的bean(普通bean)

finishBeanFactoryInitialization(beanFactory);

// 初始化容器的生命周期事件处理器,并发布容器的生命周期事件

finishRefresh();

}

catch (BeansException ex) {

if (logger.isWarnEnabled()) {

logger.warn("Exception encountered during context initialization - " +

"cancelling refresh attempt: " + ex);

}

// 销毁已创建的bean

destroyBeans();

// 重置active标志

cancelRefresh(ex);

throw ex;

}

finally {

//重置一些缓存

resetCommonCaches();

}

}

}

在这里我想说一下,这个refresh()方法其实是一个模板方法, 很多方法都让不同的实现类去实现,但该类本身也实现了其中一些方法,并且这些已经实现的方法是不允许子类重写的,比如:prepareRefresh()方法。更多模板方法设计模式,可看我之前的文章 谈一谈我对‘模板方法’设计模式的理解(Template)。

先进入prepareRefresh()方法:

/**

  • Prepare this context for refreshing, setting its startup date and

  • active flag as well as performing any initialization of property sources.

*/

protected void prepareRefresh() {

this.startupDate = System.currentTimeMillis();//设置容器启动时间

this.closed.set(false);//容器关闭标志,是否关闭?

this.active.set(true);//容器激活标志,是否激活?

if (logger.isInfoEnabled()) {//运行到这里,控制台就会打印当前容器的信息

logger.info("Refreshing " + this);

}

// 空方法,由子类覆盖实现,初始化容器上下文中的property文件

initPropertySources();

//验证标记为必需的所有属性均可解析,请参阅ConfigurablePropertyResolver#setRequiredProperties

getEnvironment().validateRequiredProperties();

//允许收集早期的ApplicationEvents,一旦多播器可用,即可发布…

this.earlyApplicationEvents = new LinkedHashSet();

}

控制台输出:

三月 22, 2018 4:21:13 下午 org.springframework.context.support.ClassPathXmlApplicationContext prepareRefresh

信息: Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@96532d6: startup date [Thu Mar 22 16:21:09 CST 2018]; root of context hierarchy

第九步:

进入obtainFreshBeanFactory()方法:

/**

  • 告诉子类刷新内部bean工厂(子类是指AbstractApplicationContext的子类,我们使用的是ClassPathXmlApplicationContext)

  • Tell the subclass to refresh the internal bean factory.

*/

protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {

refreshBeanFactory();//刷新Bean工厂,如果已经存在Bean工厂,那就关闭并销毁,再创建一个新的bean工厂

ConfigurableListableBeanFactory beanFactory = getBeanFactory();//获取新创建的Bean工厂

if (logger.isDebugEnabled()) {

logger.debug("Bean factory for " + getDisplayName() + ": " + beanFactory);//控制台打印

}

return beanFactory;

}

进入refreshBeanFactory()方法:

/**

  • 该实现执行该上下文的基础Bean工厂的实际刷新,关闭以前的Bean工厂(如果有的话)以及为该上下文的生命周期的下一阶段初始化新鲜的Bean工厂。

  • This implementation performs an actual refresh of this context’s underlying

  • bean factory, shutting down the previous bean factory (if any) and

  • initializing a fresh bean factory for the next phase of the context’s lifecycle.

*/

@Override

protected final void refreshBeanFactory() throws BeansException {

if (hasBeanFactory()) {//如果已有bean工厂

destroyBeans();//销毁

closeBeanFactory();//关闭

}

try {

DefaultListableBeanFactory beanFactory = createBeanFactory();//创建一个新的bean工厂

beanFactory.setSerializationId(getId());//为序列化目的指定一个id,如果需要,可以将此BeanFactory从此id反序列化回BeanFactory对象。

//定制容器,设置启动参数(bean可覆盖、循环引用),开启注解自动装配

customizeBeanFactory(beanFactory);

将所有BeanDefinition载入beanFactory中,此处依旧是模板方法,具体由子类实现

loadBeanDefinitions(beanFactory);

//beanFactory同步赋值

synchronized (this.beanFactoryMonitor) {

this.beanFactory = beanFactory;

}

}

catch (IOException ex) {

throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);

}

}

总结:这一步主要的工作就是判断刷新容器前是否已经有 beanfactory 存在,如果有,那么就销毁旧的 beanfactory, 那么就销毁掉并且创建一个新的 beanfactory 返回给容器,同时将 xml 文件的BeanDefinition注册到 beanfactory 中。如果不太清楚可以回过头看看我们的第三节第5点内容

第十步:

进入第九步的loadBeanDefinitions(beanFactory)方法中去take a look:

/**

  • 使用XmlBeanDefinitionReader来加载beandefnition,之前说过使用reader机制加载Resource资源变为BeanDefinition对象

  • Loads the bean definitions via an XmlBeanDefinitionReader.

  • @see org.springframework.beans.factory.xml.XmlBeanDefinitionReader

  • @see #initBeanDefinitionReader

  • @see #loadBeanDefinitions

*/

@Override

protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {

// 创建XmlBeanDefinitionReader对象

XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);

// 使用当前上下文Enviroment中的Resource配置beanDefinitionReader,因为beanDefinitionReader要将Resource解析成BeanDefinition嘛!

beanDefinitionReader.setEnvironment(this.getEnvironment());

beanDefinitionReader.setResourceLoader(this);

beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));

//初始化这个reader

initBeanDefinitionReader(beanDefinitionReader);

//将beandefinition注册到工厂中(这一步就是将bean保存到Map中)

loadBeanDefinitions(beanDefinitionReader);

}

控制台输出:

三月 22, 2018 5:09:40 下午 org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions

信息: Loading XML bean definitions from class path resource [applicationContext.xml]

第十一步:

进入prepareBeanFactory(beanFactory)方法:

//设置bean类加载器

//设置Spring语言表达式(SpEL)解析器

//扫描ApplicationContextAware bean

//注册类加载期类型切面织入(AOP)LoadTimeWeaver

//为各种加载进入beanFactory的bean配置默认环境

第十二步:

postProcessBeanFactory(beanFactory)方法:

postProcessBeanFactory同样作为一个模板方法,由子类来提供具体的实现,子类可以有自己的特殊对BeanDefinition后处理方法,即子类可以在这对前面生成的BeanDefinition,即bean的元数据再处理。比如修改某个beanid/name属性、scope属性、lazy-init属性等。

第十三步:

invokeBeanFactoryPostProcessors(beanFactory)方法:

该方法调用所有的BeanFactoryPostProcessor,它是一个接口,实现了此接口的类需重写postProcessBeanFactory()这个方法,可以看出该方法跟第十二步的方法是一样的,只不过作为接口,更多的是提供给开发者来对生成的BeanDefinition做处理,由开发者提供处理逻辑。

第十四步:

其余剩下的方法基本都是像初始化消息处理源,初始化容器事件,注册bean监听器到事件传播器上,最后完成容器刷新。

五、总结

恭喜我,我终于写完了,同样也恭喜你,你也阅读完了。

我很佩服我自己能花这么长时间进行总结发布,之所以要进行总结,那是因为小编还是赞同好记性不如烂笔头的说法。

你不记,你过阵子就会忘记,你若记录,你过阵子也会忘记!区别在于忘记了,可以回过头在很短的时间内进行回忆,查漏补缺,减少学习成本。

再者,我认为我分析的还不是完美的,缺陷很多,因此我将我写的所有文章发布出来和大家探讨交流,汕头大学有校训说得非常地好,那就是说之知识是用来共享的,因为共享了,知识才能承前启后。

现在再梳理一下 Spring 初始化过程:

  • 1、首先初始化上下文,生成ClassPathXmlApplicationContext对象,在获取resourcePatternResolver对象将xml解析成Resource对象。

  • 2、利用 1 生成的 context、resource 初始化工厂,并将 resource 解析成 beandefinition, 再将 beandefinition 注册到 beanfactory 中。

朋友们,发现毛病,请评论告诉小编,一起交流一起交流!

作者: 拥抱心中的梦想

juejin.cn/post/6844903581678600206

往期精选  点击标题可跳转

【032期】2021年 Java 面试中 Linux 最高频的五个基本面试题

【033期】面试官问:说一说 Spring 中接口 bean 是如何注入的吗?

【034期】美团面试题:JVM 堆内存溢出后,其他线程是否可继续工作?

【035期】面试官问:什么是耦合?解耦合的方法有哪几种?

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)

img

写在最后

很多人感叹“学习无用”,实际上之所以产生无用论,是因为自己想要的与自己所学的匹配不上,这也就意味着自己学得远远不够。无论是学习还是工作,都应该有主动性,所以如果拥有大厂梦,那么就要自己努力去实现它。

最后祝愿各位身体健康,顺利拿到心仪的offer!

由于文章的篇幅有限,所以这次的蚂蚁金服和京东面试题答案整理在了PDF文档里

蚂蚁、京东Java岗4面:原理+索引+底层+分布式+优化等,已拿offer

蚂蚁、京东Java岗4面:原理+索引+底层+分布式+优化等,已拿offer

蚂蚁、京东Java岗4面:原理+索引+底层+分布式+优化等,已拿offer
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!
t-1713520304046)]

[外链图片转存中…(img-z7Zn5eYq-1713520304048)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)

img

写在最后

很多人感叹“学习无用”,实际上之所以产生无用论,是因为自己想要的与自己所学的匹配不上,这也就意味着自己学得远远不够。无论是学习还是工作,都应该有主动性,所以如果拥有大厂梦,那么就要自己努力去实现它。

最后祝愿各位身体健康,顺利拿到心仪的offer!

由于文章的篇幅有限,所以这次的蚂蚁金服和京东面试题答案整理在了PDF文档里

[外链图片转存中…(img-ie0tI6Ys-1713520304049)]

[外链图片转存中…(img-0IeGXqxk-1713520304050)]

[外链图片转存中…(img-Q9ys9Av2-1713520304052)]
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值