SpringBoot(十四)启动流程分析之refreshContext()

本文深入分析了SpringBoot启动过程中的refreshContext()方法,详细讲解了从准备刷新、bean工厂的准备、BeanFactoryPostProcessor与BeanPostProcessor的调用,到初始化信息源、事件多播等关键步骤,揭示了SpringBoot内部运行机制。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

SpringBoot版本:2.1.1     ==》启动流程分析汇总

接上篇Spring Boot 2.1.1(十三)启动流程分析之准备应用上下文

目录

流程分析 

1、准备刷新

 子类prepareRefresh()方法

父类prepareRefresh()方法

2、通知子类刷新内部bean工厂

3、准备bean工厂

4、允许上下文子类对bean工厂进行后置处理

5、调用已注册的BeanFactoryPostProcessors Bean

先调用BeanDefinitionRegistryPostProcessors

再调用BeanFactoryPostProcessor

6、注册BeanPostProcessor

7、初始化信息源

8、注册SimpleApplicationEventMulticaster

9、onRefresh()

10、注册Listener

11、完成beanFactory初始化,初始化所有剩余的单例bean

12、完成刷新


public ConfigurableApplicationContext run(String... args) {
            .... 
	try {
            //本篇内容从本行开始记录  
            refreshContext(context);
           //本篇内容记录到这,后续更新
            ....
        }
	catch (Throwable ex) {
		handleRunFailure(context, ex, exceptionReporters, listeners);
		throw new IllegalStateException(ex);
	}
}

流程分析 

感觉这篇内容会有很多呀!怼它!来看源码。

private void refreshContext(ConfigurableApplicationContext context) {
	refresh(context);
	if (this.registerShutdownHook) {
	    try {
		    context.registerShutdownHook();
	    }
	    catch (AccessControlException ex) {
	    	// Not allowed in some environments.
	    }
	}
}

最终调用父类AbstractApplicationContext的refresh()方法。 

protected void refresh(ApplicationContext applicationContext) {
	Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext);
	((AbstractApplicationContext) applicationContext).refresh();
}
@Override
public final void refresh() throws BeansException, IllegalStateException {		
    try {
	     super.refresh();
    }
    catch (RuntimeException ex) {
	    stopAndReleaseWebServer();
	    throw ex;
    }
}

 可以看到refresh中的步骤都是单个单个的方法,很方便看,下面一个一个方法讲。

@Override
public void refresh() throws BeansException, IllegalStateException {
	synchronized (this.startupShutdownMonitor) {
	    // 准备刷新
	    prepareRefresh();

	    // 通知子类刷新内部bean工厂
	    ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

	    // 准备bean工厂以便在此上下文中使用
	    prepareBeanFactory(beanFactory);

	    try {
	    	// 允许上下文子类中对bean工厂进行后处理
	    	postProcessBeanFactory(beanFactory);

	    	// 在bean创建之前调用BeanFactoryPostProcessors后置处理方法
	    	invokeBeanFactoryPostProcessors(beanFactory);

	    	// 注册BeanPostProcessor
	    	registerBeanPostProcessors(beanFactory);

	    	// 注册DelegatingMessageSource
	    	initMessageSource();

	    	// 注册multicaster
	    	initApplicationEventMulticaster();

	    	// 创建内置的Servlet容器
	    	onRefresh();

	    	// 注册Listener
	    	registerListeners();

	    	// 完成BeanFactory初始化,初始化剩余单例bean
	    	finishBeanFactoryInitialization(beanFactory);

	    	// 发布对应事件
	    	finishRefresh();
	    }

	    catch (BeansException ex) {
	    	if (logger.isWarnEnabled()) {
	    		logger.warn("Exception encountered during context initialization - " +
	    					"cancelling refresh attempt: " + ex);
	    	}

	    	// Destroy already created singletons to avoid dangling resources.
	    	destroyBeans();

	    	// Reset 'active' flag.
	    	cancelRefresh(ex);

	    	// Propagate exception to caller.
	    	throw ex;
	    }

	    finally {
	    	// Reset common introspection caches in Spring's core, since we
	    	// might not ever need metadata for singleton beans anymore...
	    	resetCommonCaches();
	    }
    }
}

开始之前先把AnnotationConfigServletWebServerApplicationContext类图放这:

1、准备刷新

先调用子类重写的方法,再调用父类方法。还记得前面讲过在创建AnnotationConfigServletWebServerApplicationContext的时候构造方法中实例化了一个ClassPathBeanDefinitionScanner。

@Override
protected void prepareRefresh() {
	this.scanner.clearCache();
	super.prepareRefresh();
}

 子类prepareRefresh()方法

 在其父类ClassPathScanningCandidateComponentProvider中有一个MetadataReaderFactory(接口)工厂对象,判断该对象是否是CachingMetadataReaderFactory这个特定类或者是它的子类的一个实例,返回Boolean值,是就是true,则清除缓存。该类中有个Map,用来缓存每个Spring资源句柄(即每个“.class”文件)的MetadataReader实例。

缓存Map如果是LocalResourceCache(可以看到该类继承了LinkedHashMap),执行的就是LinkedHashMap的clear()方法了;

else如果缓存不为空,就是重新new一个LocalResourceCache。

父类prepareRefresh()方法

protected void prepareRefresh() {
        //系统启动时间
	this.startupDate = System.currentTimeMillis();
        //是否关闭标识,false
	this.closed.set(false);
        //是否活跃标识,true
	this.active.set(true);

	if (logger.isDebugEnabled()) {
		if (logger.isTraceEnabled()) {
			logger.trace("Refreshing " + this);
		}
		else {
			logger.debug("Refreshing " + getDisplayName());
		}
	}

	// 调用子类GenericWebApplicationContext重写后的方法替换servlet相关属性源
	initPropertySources();

	
	// 这里是验证由ConfigurablePropertyResolver#setRequiredProperties()方法指定的属性,解析为非空值,如果没有设置的话这个方法就不会执行什么操作。
	getEnvironment().validateRequiredProperties();

	// Allow for the collection of early ApplicationEvents,
	// to be published once the multicaster is available...
	this.earlyApplicationEvents = new LinkedHashSet<>();
}

 看下最后这个方法,initServlet属性源,方法注释是这么说的将基于Servlet的StubPropertySource替换为使用给定servletContext和servletConfig对象填充的实例。此方法可以被调用任意次数,但将用相应的实际属性源替换为StubPropertySource一次且仅一次。

看下if判断里的条件,servletContext不为空,source中存在指定name的的属性源,且该属性源要是StubPropertySource的类型或者是其子类。也就是当第一次调用以后,该属性源就被替换成了ServletContextPropertySource和ServletConfigPropertySource,所以之后的调用最后一个判断条件就不会成立了。

public static void initServletPropertySources(MutablePropertySources sources,
			@Nullable ServletContext servletContext, @Nullable ServletConfig servletConfig) {

	Assert.notNull(sources, "'propertySources' must not be null");
        //servletContextInitParams
	String name = StandardServletEnvironment.SERVLET_CONTEXT_PROPERTY_SOURCE_NAME;
	if (servletContext != null && sources.contains(name) && sources.get(name) instanceof StubPropertySource) {
		sources.replace(name, new ServletContextPropertySource(name, servletContext));
	}
        //servletConfigInitParams
	name = StandardServletEnvironment.SERVLET_CONFIG_PROPERTY_SOURCE_NAME;
	if (servletConfig != null && sources.contains(name) && sources.get(name) instanceof StubPropertySource) {
		sources.replace(name, new ServletConfigPropertySource(name, servletConfig));
	}
}

 看validateRequiredProperties()方法前,先看下Environment的类图。这里是验证由ConfigurablePropertyResolver#setRequiredProperties()方法指定的属性,解析为非空值,如果没有设置的话这个方法就不会执行什么操作,所以这里就略过了。

方法最后new了一个LinkedHashSet,收集早期事件,如果multicaster 有用就会广播事件。

prepareRefresh()就执行完了。

2、通知子类刷新内部bean工厂

 可以看到refreshBeanFactory()方法上的注释说的,什么都不做:我们拥有一个内部beanfactory,并依靠调用方通过我们的公共方法(或beanfactory)注册bean。

前面介绍GenericApplicationContext说了与每次刷新创建新的内部beanfactory实例的其他applicationContext实现不同,此上下文的内部beanfactory从一开始就可用,以便能够在其上注册bean定义。只能调用一次refresh()。

所以在该方法内只设置了SerializationId,该id是在准备应用上下文时调用ContextIdApplicationContextInitializer时设置的id,在setSerializationId方法中,使用id做key,new了一个弱引用对象为value,添加到了serializableFactories中,DefaultListableBeanFactory为被弱引用对象;如果需要,可以通过id得到引用对象,在通过get()方法得到DefaultListableBeanFactory对象。

 

3、准备bean工厂

protected void prepareBeanFactory(ConfigurableListableBeanFactory beanFactory) {
	//通知内部bean工厂使用上下文的类加载器
	beanFactory.setBeanClassLoader(getClassLoader());
        //为bean定义值中的表达式指定解析策略,即解析el表达式,默认"#{"开头,"}"结尾
	beanFactory.setBeanExpressionResolver(new StandardBeanExpressionResolver(beanFactory.getBeanClassLoader()));
        //在这
Spring Boot 应用程序的启动流程是一个高度自动化且模块化的过程,主要依赖于 `SpringApplication` 类和其运行时的上下文管理机制。以下是 Spring Boot 启动流程的详细说明: ### 启动类与主方法 每个 Spring Boot 项目都必须定义一个启动类,并通过 `main()` 方法作为程序入口点。该类通常使用 `@SpringBootApplication` 注解来启用自动配置、组件扫描和配置类的支持。例如: ```java @SpringBootApplication public class DemoApplication { public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); } } ``` 此代码段中的 `SpringApplication.run()` 是核心方法,负责初始化并运行整个 Spring Boot 应用程序 [^1]。 ### SpringApplication 的初始化 在调用 `run()` 方法后,Spring Boot 开始进行一系列内部操作,包括: - **推断应用类型**:根据类路径判断是普通应用(NONE)、Web 应用(SERVLET)还是响应式 Web 应用(REACTIVE)。 - **加载监听器**:注册 `SpringApplicationRunListener` 监听器以支持生命周期事件的发布与处理 [^3]。 - **准备环境配置**:加载系统环境变量、命令行参数以及配置文件(如 `application.properties` 或 `application.yml`)。这些配置会用于设置 `SpringApplication` 实例的属性 。 ### 上下文的创建与刷新 Spring Boot 在启动过程中会创建一个合适的 `ApplicationContext` 并对其进行刷新操作: - **关联环境配置到上下文**:将之前加载的配置信息绑定到上下文中,以便后续使用 [^4]。 - **执行扩展点**:触发 `ApplicationContextInitializedEvent` 和 `ApplicationPreparedEvent` 等事件,通知所有相关的监听器 [^4]。 - **注册主配置类**:将带有 `@SpringBootApplication` 注解的启动类注册为配置类,以便容器能够解析其中的 Bean 定义 [^4]。 - **刷新上下文**:调用 `refreshContext(context)` 方法,这是 Spring 框架的核心部分,负责完成以下任务: - 初始化 Bean 工厂。 - 加载 Bean 定义。 - 自动装配 Bean。 - 调用 Bean 的初始化方法。 ### 嵌入式服务器的启动 对于 Web 应用程序,Spring Boot 会在刷新上下文后启动嵌入式的 Web 服务器(如 Tomcat、Jetty 或 Undertow),以监听 HTTP 请求并提供服务 [^2]。 ### 执行启动逻辑 如果应用程序中存在实现了 `CommandLineRunner` 或 `ApplicationRunner` 接口的 Bean,Spring Boot 会在上下文刷新完成后调用它们的 `run()` 方法。这通常用于执行启动时需要的自定义逻辑 [^2]。 ### 构建可执行的 JAR 文件 为了确保 Spring Boot 应用可以独立运行,在构建过程中需要使用 `spring-boot-maven-plugin` 插件重新打包生成可执行的 JAR 文件。以下是在 `pom.xml` 中添加的插件配置示例: ```xml <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <executions> <execution> <goals> <goal>repackage</goal> </goals> </execution> </executions> </plugin> </plugins> </build> ``` 此配置确保了构建出的 JAR 文件包含所有依赖项,并具有正确的启动类信息 [^5]。 ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值