目录
一、IOC容器的刷新环节快速回顾
二、初始化前的预处理prepareRefresh源码分析
三、初始化属性源
(一)GenericWebApplicationContext初始化属性源
(二)StaticWebApplicationContext初始化属性源
四、初始化早期事件集合
五、总结
干货分享,感谢您的阅读!
在很早之前我们单独写过一篇文章《分析SpringBoot启动配置原理》,具体可见:
分析SpringBoot启动配置原理https://blog.csdn.net/xiaofeng10330111/article/details/130903779?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522171664383116800186545975%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fblog.%2522%257D&request_id=171664383116800186545975&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~blog~first_rank_ecpm_v1~rank_v31_ecpm-21-130903779-null-null.nonecase&utm_term=Spring&spm=1018.2226.3001.4450其中IOC容器的刷新环节可当重点分析,值得在读源码时进行深入分析,我们会从多个方向上再次进行分析回顾和学习。
一、IOC容器的刷新环节快速回顾
我们将AbstractApplicationContext的refresh方法源码提取并进行重点代码标注说明如下:
public abstract class AbstractApplicationContext implements ApplicationContext {@Overridepublic void refresh() throws BeansException, IllegalStateException {synchronized (this.startupShutdownMonitor) {// 准备上下文环境,包括初始化工厂、后置处理器等prepareRefresh();// 创建并初始化 BeanFactoryConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();// 设置 BeanFactory 的类加载器、资源加载器等prepareBeanFactory(beanFactory);try {// 允许子类对 BeanFactory 进行进一步的自定义处理postProcessBeanFactory(beanFactory);// 调用 BeanFactoryPostProcessors 进行后置处理invokeBeanFactoryPostProcessors(beanFactory);// 注册 BeanPostProcessors,用于对 Bean 实例进行后置处理registerBeanPostProcessors(beanFactory);// 初始化消息源initMessageSource();// 初始化事件广播器initApplicationEventMulticaster();// 初始化其他特殊 BeanonRefresh();// 注册关闭钩子registerListeners();// 初始化所有剩余的单例 BeanfinishBeanFactoryInitialization(beanFactory);// 完成上下文刷新finishRefresh();} catch (BeansException ex) {if (logger.isWarnEnabled()) {logger.warn("Exception encountered during context initialization - " +"cancelling refresh attempt: " + ex);}// 销毁已创建的 Bean,关闭容器destroyBeans();// 重置容器刷新标志,允许再次刷新cancelRefresh(ex);// 把异常重新抛出,允许调用者处理throw ex;} finally {// 重置已注册的 JVM 关闭钩子resetCommonCaches();}}}
}
以上内容可多次翻看并理解,本文将关注初始化前的预处理prepareRefresh专项。
二、初始化前的预处理prepareRefresh源码分析
prepareRefresh()
方法的设计和实现体现了 Spring 容器在初始化之前做好了各种准备工作,以确保容器在刷新过程中能够顺利进行,并且应用程序能够正确地运行。我们直接展示源码如下:
protected void prepareRefresh() {this.startupDate = System.currentTimeMillis();this.closed.set(false);this.active.set(true);if (this.logger.isDebugEnabled()) {if (this.logger.isTraceEnabled()) {this.logger.trace("Refreshing " + this);} else {this.logger.debug("Refreshing " + this.getDisplayName());}}// 初始化属性源this.initPropertySources();// 验证必需的属性this.getEnvironment().validateRequiredProperties();// 复制早期应用程序监听器以供稍后使用if (this.earlyApplicationListeners == null) {this.earlyApplicationListeners = new LinkedHashSet(this.applicationListeners);} else {this.applicationListeners.clear();this.applicationListeners.addAll(this.earlyApplicationListeners);}// 初始化早期事件集合this.earlyApplicationEvents = new LinkedHashSet();
}
我们通过这个源码可见prepareRefresh()
方法的主要步骤,简要说明如下:
步骤 | 说明 |
---|---|
记录时间戳和设置状态标志 | 记录容器的启动时间戳,并设置容器的状态标志,用于跟踪容器的状态。 |
日志记录 | 根据日志级别记录容器的刷新过程,提供对容器启动过程的可视化和追踪。 |
属性源初始化和属性验证 | 初始化属性源,以确保应用程序在后续的运行过程中能够正确地获取配置属性,并验证必需的属性是否已经设置。 |
处理早期应用程序监听器 | 复制早期应用程序监听器以供稍后使用,以确保在容器刷新过程中能够保留早期监听器的设置。 |
初始化早期事件集合 | 初始化一个早期事件集合,用于存储在容器刷新过程中产生的早期事件,以便后续处理。 |
针对其中的主要内容我们进行展开分析一下。
三、初始化属性源
直接展开源码分析:
protected void initPropertySources() {}
无论是 AbstractRefreshableWebApplicationContext
、GenericWebApplicationContext
还是 StaticWebApplicationContext
,它们都具有 initPropertySources()
方法的实现,用于初始化容器的属性源,确保容器能够正确地获取应用程序的配置信息,因此在创建这些应用程序上下文时都可能会调用这个方法。
(一)GenericWebApplicationContext
初始化属性源
具体源码展示:
protected void initPropertySources() {ConfigurableEnvironment env = this.getEnvironment();if (env instanceof ConfigurableWebEnvironment) {((ConfigurableWebEnvironment)env).initPropertySources(this.servletContext, (ServletConfig)null);}} ====================================================
package org.springframework.web.context;import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.lang.Nullable;public interface ConfigurableWebEnvironment extends ConfigurableEnvironment {void initPropertySources(@Nullable ServletContext var1, @Nullable ServletConfig var2);
}====================================================
package org.springframework.web.context.support;import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import org.springframework.core.env.MutablePropertySources;
import org.springframework.core.env.PropertySource;
import org.springframework.core.env.StandardEnvironment;
import org.springframework.jndi.JndiLocatorDelegate;
import org.springframework.jndi.JndiPropertySource;
import org.springframework.lang.Nullable;
import org.springframework.web.context.ConfigurableWebEnvironment;public class StandardServletEnvironment extends StandardEnvironment implements ConfigurableWebEnvironment {public static final String SERVLET_CONTEXT_PROPERTY_SOURCE_NAME = "servletContextInitParams";public static final String SERVLET_CONFIG_PROPERTY_SOURCE_NAME = "servletConfigInitParams";public static final String JNDI_PROPERTY_SOURCE_NAME = "jndiProperties";public StandardServletEnvironment() {}protected void customizePropertySources(MutablePropertySources propertySources) {propertySources.addLast(new PropertySource.StubPropertySource("servletConfigInitParams"));propertySources.addLast(new PropertySource.StubPropertySource("servletContextInitParams"));if (JndiLocatorDelegate.isDefaultJndiEnvironmentAvailable()) {propertySources.addLast(new JndiPropertySource("jndiProperties"));}super.customizePropertySources(propertySources);}public void initPropertySources(@Nullable ServletContext servletContext, @Nullable ServletConfig servletConfig) {WebApplicationContextUtils.initServletPropertySources(this.getPropertySources(), servletContext, servletConfig);}
}
可以了解到 GenericWebApplicationContext
在初始化属性源时,会通过调用环境对象的 initPropertySources()
方法来实现。而环境对象通常是 StandardServletEnvironment
类的实例,它提供了对 Servlet 环境的特定支持,例如添加 Servlet 相关的属性源。
也即是说当我们在 Spring MVC 中创建一个基于 Java 配置的 Web 应用程序时,通常会使用 GenericWebApplicationContext
来管理应用程序上下文。在初始化容器时,GenericWebApplicationContext
会调用 initPropertySources()
方法来初始化属性源,这些属性源可能包括 Servlet 上下文参数、Servlet 配置参数以及 JNDI 属性等。这样,我们就能够在应用程序中方便地获取这些配置信息,例如通过 @Value
注解或 Environment
对象。
提到Environment
对象,可以扩展延读到:
重看Spring聚焦Environment分析-CSDN博客文章浏览阅读2.6k次,点赞17次,收藏12次。Environment模块在 Spring 中主要负责管理应用程序的配置和环境(定义为一组 profile配置文件)相关的信息,每个 profile 对应一个特定的应用程序部署环境,比如开发、测试、生产等。在这些 profile 中,可以包含各种属性,比如数据库连接信息、服务器端口、日志级别等。而对应的属性在 Spring 中被表示为键值对,其中键是属性的名称,值是属性的取值。属性可以通过不同的方式进行配置,比如在属性文件中、通过系统属性、操作系统环境变量等。https://blog.csdn.net/xiaofeng10330111/article/details/138143106?spm=1001.2014.3001.5501https://blog.csdn.net/xiaofeng10330111/article/details/138143106?spm=1001.2014.3001.5501在 Spring Boot 应用程序启动时会自动加载 application.properties
文件到 Environment
中。Spring Boot 的自动配置功能会根据这些配置属性来自动配置应用程序的各种组件和功能。
注意,虽然 initPropertySources()
方法在容器初始化时会起到一定的作用,但是在 Spring Boot 应用程序中,读取 application.properties
文件的功能主要是由 Spring Boot 框架本身负责的,它会将这些配置属性加载到 Environment
中,供整个应用程序使用。
(二)StaticWebApplicationContext
初始化属性源
具体源码展示:
protected void initPropertySources() {WebApplicationContextUtils.initServletPropertySources(this.getEnvironment().getPropertySources(), this.servletContext, this.servletConfig);}=======================================================public static void initServletPropertySources(MutablePropertySources sources, @Nullable ServletContext servletContext, @Nullable ServletConfig servletConfig) {Assert.notNull(sources, "'propertySources' must not be null");String name = "servletContextInitParams";if (servletContext != null && sources.contains(name) && sources.get(name) instanceof PropertySource.StubPropertySource) {sources.replace(name, new ServletContextPropertySource(name, servletContext));}name = "servletConfigInitParams";if (servletConfig != null && sources.contains(name) && sources.get(name) instanceof PropertySource.StubPropertySource) {sources.replace(name, new ServletConfigPropertySource(name, servletConfig));}}
可以了解到 StaticWebApplicationContext
在初始化属性源时,会调用 WebApplicationContextUtils.initServletPropertySources()
方法来初始化 Servlet 相关的属性源。这些属性源通常包括了 Servlet 上下文参数和 Servlet 配置参数,它们是通过 ServletContextPropertySource
和 ServletConfigPropertySource
来表示的。功能和常规的基本一致。
四、初始化早期事件集合
在上面的源码中可以直观的看到有个早期事件集合的初始化:
// 初始化早期事件集合
this.earlyApplicationEvents = new LinkedHashSet();
这段代码的作用是创建一个空的 LinkedHashSet
对象并赋值给 earlyApplicationEvents
变量,从而初始化了早期事件集合。
我们知道Spring框架是事件驱动的,它提供了一套事件机制用于在应用程序中处理各种事件。在容器的生命周期中,可能会产生各种事件,例如容器初始化完成事件、Bean初始化事件等。
在容器刷新过程中,一些事件可能会在容器完全初始化之前就已经发生。这些早期事件通常是在容器初始化的早期阶段触发的,例如在BeanFactory被创建之后但是Bean的实例化尚未开始之前。为了能够捕获并处理这些早期事件,Spring使用一个早期事件集合来存储这些事件。
在容器准备刷新之前在 prepareRefresh()
方法中会初始化早期事件集合,也就是上面的这个空的集合对象,主要用于后续能够添加早期事件。
也就是说,在容器刷新的过程中,如果产生了早期事件,就会将这些事件添加到早期事件集合中。这样,在容器刷新完成后,就可以从早期事件集合中获取这些事件,并进行后续的处理,例如执行事件监听器或发布事件通知。
一旦容器刷新完成,就可以对早期事件集合中的事件进行后续处理。这可能包括执行事件监听器、发布事件通知、执行一些初始化操作等。
五、总结
本文深入探讨了Spring IOC容器的刷新过程,主要集中在AbstractApplicationContext
的refresh()
方法的源码分析及其关键环节。文本文首先回顾了IOC容器的刷新流程,强调了各个步骤的重要性,例如准备环境、初始化BeanFactory、注册监听器和处理事件等。通过对prepareRefresh()
方法的分析,读者可以看到Spring在容器初始化之前所做的准备工作,包括时间戳记录、状态标志设置和属性源的初始化。这些操作确保了容器在刷新过程中能够顺利进行。
接着,本文详细阐述了属性源初始化的具体实现,特别是在GenericWebApplicationContext
和StaticWebApplicationContext
中的initPropertySources()
方法。这一部分强调了如何从Servlet环境中获取配置属性,为应用程序提供必要的配置信息。此外,本文还探讨了早期事件集合的初始化及其在事件驱动机制中的作用,指出这些早期事件如何在容器刷新过程中的关键时刻被捕获和处理。
最后,本文提到了一些关键方法的异常处理机制,如destroyBeans()
和cancelRefresh()
,强调了Spring容器在面临异常时的健壮性。整体而言,本文系统地分析了Spring容器的启动过程,为开发者理解和应用Spring框架提供了重要参考。
总结而言,本文不仅提供了丰富的源码分析,还强调了在实际应用中的潜在问题和最佳实践,对希望深入理解Spring框架内部机制的读者来说,具有很高的实用价值和参考意义。