SpringBoot – 06 – SpringBoot 2.x 启动流程源码解析

本文基于 SpringBoot 2.3.4.RELEASE 版本进行分析


1、启动类

@SpringBootApplication
public class SpringbootTestApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringbootTestApplication.class, args);
    }
}

该类主要为项目启动提供入口,通过 main() 方法即可启动整个 SpringBoot 项目

2、@SpringBootApplication 注解

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
		@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
    ...
}

该注解看似有那么多其他注解修饰,实际上我们只需要重点关注 @SpringBootConfiguration@EnableAutoConfiguration@ComponentScan 这个三个注解即可

  • @SpringBootConfiguration

    • 继承自 @Configuration 注解,作用与其一致,用于标记当前类为配置类,相当于 XML 配置中的 <beans></beans>
  • @EnableAutoConfiguration

    • 开启自动配置,将所有符合自动配置条件的 Bean 对象加载进 IoC 容器中
  • @ComponentScan

    • 开启组件扫描,默认扫描当前类所在的包及其子包中的所有 Bean 对象,相当于 XML 配置中的 <context:component-scan></<context:component-scan>

3、SpringApplication 类

该类主要用于引导和启动一个 SpringBoot 应用程序

4、run() 方法

public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
	return run(new Class<?>[] { primarySource }, args);
}

public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
	return new SpringApplication(primarySources).run(args);
}

run() 方法共有两个参数,如下所示

参数作用
primarySource需要加载的资源类
args应用程序参数 (通常通过 main() 方法进行传递)

5、SpringApplication 对象

run()方法中,会先创建一个 SpringApplication 对象,然后再调用它的 run() 方法

SpringApplication 对象创建流程如下所示

public SpringApplication(Class<?>... primarySources) {
	this(null, primarySources);
}

public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
    // 初始化资源加载器为 null
	this.resourceLoader = resourceLoader;
	// 断言判断要加载的资源是否不为 null,如果为 null,则会报错
	Assert.notNull(primarySources, "PrimarySources must not be null");
	// 初始化要加载的资源类集合并去重
	this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
	// 推断当前 Web 引用类型
	this.webApplicationType = WebApplicationType.deduceFromClasspath();
	// 设置应用上下文初始化器
	setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
	// 设置监听器
	setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
	// 推断主入口应用类
	this.mainApplicationClass = deduceMainApplicationClass();
}
5.1、推断当前 Web 引用类型
this.webApplicationType = WebApplicationType.deduceFromClasspath();

static WebApplicationType deduceFromClasspath() {
	if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null) && !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null)
			&& !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) {
		return WebApplicationType.REACTIVE;
	}
	for (String className : SERVLET_INDICATOR_CLASSES) {
		if (!ClassUtils.isPresent(className, null)) {
			return WebApplicationType.NONE;
		}
	}
	return WebApplicationType.SERVLET;
}

该方法主要用于根据类路径下是否有对应类型的项目来推断出不同的应用类型,共有以下三种应用类型

类型含义
REACTIVE响应式 Web 项目
NONE非 Web 项目
SERVLETServlet Web 项目
5.2 设置应用上下文初始化器
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));

先来看下 ApplicationContextInitializer 接口

public interface ApplicationContextInitializer<C extends ConfigurableApplicationContext> {

	void initialize(C applicationContext);

}

该接口主要用于初始化给定的应用程序上下文

再来看下 getSpringFactoriesInstances() 方法

private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) {
	return getSpringFactoriesInstances(type, new Class<?>[] {});
}

private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
    // 获取当前应用上下文类加载器
	ClassLoader classLoader = getClassLoader();
	// 获取 ApplicationContextInitializer 实例名称集合并去重
	Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
	// 根据指定类路径创建初始化实例列表
	List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
	// 对初始化实例列表进行排序
	AnnotationAwareOrderComparator.sort(instances);
    // 返回初始化实例列表
	return instances;
}
  1. 获取 ApplicationContextInitializer 实例名称集合并去重

    Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
    
    public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
    
    public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
    	String factoryTypeName = factoryType.getName();
    	return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
    }
    
    private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
    	MultiValueMap<String, String> result = cache.get(classLoader);
    	if (result != null) {
    		return result;
    	}
    
    	try {
    		Enumeration<URL> urls = (classLoader != null ?
    				classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
    				ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
    		result = new LinkedMultiValueMap<>();
    		while (urls.hasMoreElements()) {
    			URL url = urls.nextElement();
    			UrlResource resource = new UrlResource(url);
    			Properties properties = PropertiesLoaderUtils.loadProperties(resource);
    			for (Map.Entry<?, ?> entry : properties.entrySet()) {
    				String factoryTypeName = ((String) entry.getKey()).trim();
    				for (String factoryImplementationName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
    					result.add(factoryTypeName, factoryImplementationName.trim());
    				}
    			}
    		}
    		cache.put(classLoader, result);
    		return result;
    	}
    	catch (IOException ex) {
    		throw new IllegalArgumentException("Unable to load factories from location [" +
    				FACTORIES_RESOURCE_LOCATION + "]", ex);
    	}
    }
    

    该方法主要用于加载 META-INF/spring.factories 文件 (此处 spring.factories 文件位于 spring-boot-autoconfigure-2.3.4.RELEASE.jar!/META-INF/ 目录下),并根据参数的不同获取不同配置类的全限定类名

    此处 loadFactoryNames() 方法中的参数为 ApplicationContextInitializer.class,即获取 ApplicationContextInitializer 接口所有配置类的全限定类名
    在这里插入图片描述
    在这里插入图片描述

  2. 根据指定类路径创建初始化实例列表

    List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
    
    private <T> List<T> createSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes,
    		ClassLoader classLoader, Object[] args, Set<String> names) {
    	List<T> instances = new ArrayList<>(names.size());
    	for (String name : names) {
    		try {
    			Class<?> instanceClass = ClassUtils.forName(name, classLoader);
    			Assert.isAssignable(type, instanceClass);
    			Constructor<?> constructor = instanceClass.getDeclaredConstructor(parameterTypes);
    			T instance = (T) BeanUtils.instantiateClass(constructor, args);
    			instances.add(instance);
    		}
    		catch (Throwable ex) {
    			throw new IllegalArgumentException("Cannot instantiate " + type + " : " + name, ex);
    		}
    	}
    	return instances;
    }
    

    该方法主要根据 loadFactoryNames() 方法获得的配置类的全限定类名来实例化配置类

5.3 设置监听器
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));

先来看下 ApplicationListener 接口

@FunctionalInterface
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {

	void onApplicationEvent(E event);

}

该接口主要用于监听 ApplicationEvent 类型的事件,常用的 ApplicationEvent 类型有以下几种

事件含义
ApplicationStartedEvent项目启动时执行的事件
ApplicationFailedEvent项目启动异常时执行的事件

再来看下 getSpringFactoriesInstances() 方法,该方法的调用与之前步骤 5.2 中设置应用上下文初始化器是一样的,只是传入的参数不同,此处传入的参数为 ApplicationListener.class,其在 META-INF/spring.factories 文件 (此处 spring.factories 文件位于 spring-boot-autoconfigure-2.3.4.RELEASE.jar!/META-INF/ 目录下) 中的配置类全限定类名如下所示

在这里插入图片描述

在这里插入图片描述

5.4 推断主入口应用类
this.mainApplicationClass = deduceMainApplicationClass();

private Class<?> deduceMainApplicationClass() {
	try {
		StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();
		for (StackTraceElement stackTraceElement : stackTrace) {
			if ("main".equals(stackTraceElement.getMethodName())) {
				return Class.forName(stackTraceElement.getClassName());
			}
		}
	}
	catch (ClassNotFoundException ex) {
		// Swallow and continue
	}
	return null;
}

该方法主要用于获取启动类,获取的方法有些特殊,先构造一个运行时异常并获取异常栈数组,然后遍历异常栈数组,筛选出方法名为 main 的栈帧,最后通过启动类的全限定类名来获取启动类并返回

6、run() 方法详解

在了解完 SpringApplication 对象的创建流程后,我们再来详细地了解下 run() 方法的业务流程

public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
	return run(new Class<?>[] { primarySource }, args);
}

public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
	return new SpringApplication(primarySources).run(args);
}

public ConfigurableApplicationContext run(String... args) {
    // 创建并启动计时监控类
	StopWatch stopWatch = new StopWatch();
	stopWatch.start();
	// 初始化应用上下文
	ConfigurableApplicationContext context = null;
	// 初始化异常报告类集合
	Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
	// 设置系统属性 java.awt.headless 的值,默认为 true
	configureHeadlessProperty();
	// 创建并启动应用运行监听器
	SpringApplicationRunListeners listeners = getRunListeners(args);
	listeners.starting();
	try {
	    // 创建应用参数
		ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
		// 根据应用运行监听器和应用参数来准备应用环境
		ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
		// 设置是否跳过对 BeanInfo 类的搜索
		configureIgnoreBeanInfo(environment);
		// 创建 Banner 打印类
		Banner printedBanner = printBanner(environment);
		// 创建应用上下文
		context = createApplicationContext();
		// 创建异常报告类集合
		exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
				new Class[] { ConfigurableApplicationContext.class }, context);
		// 准备应用上下文
		prepareContext(context, environment, listeners, applicationArguments, printedBanner);
		// 刷新应用上下文
		refreshContext(context);
		// 回调函数 (在刷新应用上下文后调用)
		afterRefresh(context, applicationArguments);
		// 停止计时监控类
		stopWatch.stop();
		/*
		 * 判断是否记录应用程序信息
		 * 在配置文件中使用 spring.main.log-startup-info 参数进行控制
		 */
		if (this.logStartupInfo) {
		    // 如果要记录,则在启动时记录应用程序信息
			new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
		}
		// 发布应用上下文并启动
		listeners.started(context);
		// 调用 Runner 运行类 (ApplicationRunner、CommandLineRunner)
		callRunners(context, applicationArguments);
	}
	catch (Throwable ex) {
	    // 处理程序启动失败的情况
		handleRunFailure(context, ex, exceptionReporters, listeners);
		throw new IllegalStateException(ex);
	}

	try {
	    // 发布应用上下文并运行
		listeners.running(context);
	}
	catch (Throwable ex) {
		handleRunFailure(context, ex, exceptionReporters, null);
		throw new IllegalStateException(ex);
	}
	// 返回应用上下文
	return context;
}

现在让我们来逐步了解下它的业务流程

6.1 创建并启动计时监控类
StopWatch stopWatch = new StopWatch();
stopWatch.start();

public void start() throws IllegalStateException {
	start("");
}

public void start(String taskName) throws IllegalStateException {
	if (this.currentTaskName != null) {
		throw new IllegalStateException("Can't start StopWatch: it's already running");
	}
	this.currentTaskName = taskName;
	this.startTimeNanos = System.nanoTime();
}

该方法主要用于记录当前任务名称 (默认为空字符串) 以及程序启动时间

6.2 设置系统属性 java.awt.headless 的值,默认为 true
configureHeadlessProperty();

private void configureHeadlessProperty() {
	System.setProperty(SYSTEM_PROPERTY_JAVA_AWT_HEADLESS,
			System.getProperty(SYSTEM_PROPERTY_JAVA_AWT_HEADLESS, Boolean.toString(this.headless)));
}

该方法主要用于设置系统属性 java.awt.headless 的值,其具体作用如下

Headless模式是系统的一种配置模式。在该模式下,系统缺少了显示设备、键盘或鼠标

Headless模式虽然不是我们愿意见到的,但事实上我们却常常需要在该模式下工作,尤其是服务器端程序开发者。因为服务器(如提供Web服务的主机)往往可能缺少前述设备,但又需要使用他们提供的功能,生成相应的数据,以提供给客户端(如浏览器所在的配有相关的显示设备、键盘和鼠标的主机

一般是在程序开始激活headless模式,告诉程序,现在你要工作在Headless mode下,就不要指望硬件帮忙了,你得自力更生,依靠系统的计算能力模拟出这些特性来

参考自:java.awt.headless 参数说明

6.3 创建并启动应用运行监听器
  1. 创建应用运行监听器

    SpringApplicationRunListeners listeners = getRunListeners(args);
    
    private SpringApplicationRunListeners getRunListeners(String[] args) {
    	Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };
    	return new SpringApplicationRunListeners(logger,
    			getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args));
    }
    

    该方法创建运行监听器的流程与之前步骤 5.2 中设置应用上下文初始化器是一样的,只是传入的参数不同,此处传入的参数为 SpringApplicationRunListener.class,其在 META-INF/spring.factories 文件 (此处 spring.factories 文件位于 spring-boot-2.3.4.RELEASE.jar!/META-INF/ 目录下) 中的配置类全限定类名如下所示
    在这里插入图片描述
    在这里插入图片描述

  2. 启动应用运行监听器

    listeners.starting();
    
    void starting() {
    	for (SpringApplicationRunListener listener : this.listeners) {
    		listener.starting();
    	}
    }
    
    public interface SpringApplicationRunListener {
    
    	default void starting() {
    	}
    }
    

    starting() 方法内部调用了 SpringApplicationRunListener 接口的 starting() 方法 (在实现类中进行实现),在首次启动 run() 方法时立即调用,可用于非常早期的初始化

6.4 创建应用参数类
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
6.5 根据应用运行监听器和应用参数来准备应用环境
ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);

private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
		ApplicationArguments applicationArguments) {
	// 获取或创建应用环境
	ConfigurableEnvironment environment = getOrCreateEnvironment();
	// 配置应用环境
	configureEnvironment(environment, applicationArguments.getSourceArgs());
	// 添加参数至应用环境
	ConfigurationPropertySources.attach(environment);
	// 回调函数 (在应用环境准备好之后调用)
	listeners.environmentPrepared(environment);
	// 将应用环境绑定至应用
	bindToSpringApplication(environment);
	// 判断是否为自定义环境
	if (!this.isCustomEnvironment) {
	    // 如果为非自定义环境,则使用指定类加载器来创建应用环境
		environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,
				deduceEnvironmentClass());
	}
	// 添加参数至应用环境
	ConfigurationPropertySources.attach(environment);
	// 返回应用环境
	return environment;
}
  1. 获取或创建应用环境

    ConfigurableEnvironment environment = getOrCreateEnvironment();
    
    private ConfigurableEnvironment getOrCreateEnvironment() {
    	if (this.environment != null) {
    		return this.environment;
    	}
    	switch (this.webApplicationType) {
    	case SERVLET:
    		return new StandardServletEnvironment();
    	case REACTIVE:
    		return new StandardReactiveWebEnvironment();
    	default:
    		return new StandardEnvironment();
    	}
    }
    

    该方法主要用于获取或创建应用环境,共有以下三类

    应用环境含义
    StandardServletEnvironment标准 Servlet 环境
    StandardReactiveWebEnvironment标准响应式 Web 环境
    StandardEnvironment标准环境
  2. 配置应用环境

    configureEnvironment(environment, applicationArguments.getSourceArgs());
    
    protected void configureEnvironment(ConfigurableEnvironment environment, String[] args) {
    	if (this.addConversionService) {
    		ConversionService conversionService = ApplicationConversionService.getSharedInstance();
    		environment.setConversionService((ConfigurableConversionService) conversionService);
    	}
    	configurePropertySources(environment, args);
    	configureProfiles(environment, args);
    }
    

    先来看 applicationArguments.getSourceArgs() 这个方法,该方法用于获取程序启动时添加的命令行参数,如:--server.port:8888 (用于指定程序端口号为 8888)
    在这里插入图片描述

    @Component
    public class MyApplicationRunner implements ApplicationRunner {
    
        @Override
        public void run(ApplicationArguments args) throws Exception {
            // [--server.port=8080]
            System.out.println(Arrays.toString(args.getSourceArgs()));
        }
    }
    

    如上所示,当程序启动时,会输出添加的命令行参数 --server.port=8888

    再来看 configurePropertySources(environment, args) 这个方法,该方法用于 property sources 的配置

    protected void configurePropertySources(ConfigurableEnvironment environment, String[] args) {
        // 获取应用环境参数
    	MutablePropertySources sources = environment.getPropertySources();
    	/*
    	 * 如果默认环境参数不为 null,则将其添加进应用环境参数
    	 * 可以在启动类中使用 SpringApplicationBuilder 类的 properties() 方法进行赋值
    	 */
    	if (this.defaultProperties != null && !this.defaultProperties.isEmpty()) {
    		sources.addLast(new MapPropertySource("defaultProperties", this.defaultProperties));
    	}
    	// 如果存在命令行参数,则将其添加进应用环境参数
    	if (this.addCommandLineProperties && args.length > 0) {
    		String name = CommandLinePropertySource.COMMAND_LINE_PROPERTY_SOURCE_NAME;
    		/**
    		 * 如果存在同名的环境参数,则命令行参数会覆盖已有的环境参数
    		 * 如:(--server.port:8888) 会覆盖默认的 8080 端口
    		 */
    		if (sources.contains(name)) {
    			PropertySource<?> source = sources.get(name);
    			CompositePropertySource composite = new CompositePropertySource(name);
    			composite.addPropertySource(
    					new SimpleCommandLinePropertySource("springApplicationCommandLineArgs", args));
    			composite.addPropertySource(source);
    			sources.replace(name, composite);
    		}
    		else {
    			sources.addFirst(new SimpleCommandLinePropertySource(args));
    		}
    	}
    }
    

    最后来看 configureProfiles(environment, args) 这个方法,该方法用于 profiles 的配置

    protected void configureProfiles(ConfigurableEnvironment environment, String[] args) {
        /*
         * 初始化 profiles 集合,初始元素为 this.additionalProfiles
         * 可以在启动类中使用 SpringApplicationBuilder 类的 profiles() 方法进行赋值
         */
    	Set<String> profiles = new LinkedHashSet<>(this.additionalProfiles);
    	/*
    	 * 将命令行参数 () 中指定的 profile 添加进 profiles 集合
    	 * 如:在程序启动时,添加命令行参数 (--spring.profiles.active=dev)
    	 *     则程序会将 application-dev.yml 作为主配置文件进行加载
    	 */
    	profiles.addAll(Arrays.asList(environment.getActiveProfiles()));
    	// 将 profiles 集合添加进应用环境变量
    	environment.setActiveProfiles(StringUtils.toStringArray(profiles));
    }
    
6.6 设置是否跳过对 BeanInfo 实现类的搜索
configureIgnoreBeanInfo(environment);

private void configureIgnoreBeanInfo(ConfigurableEnvironment environment) {
	if (System.getProperty(CachedIntrospectionResults.IGNORE_BEANINFO_PROPERTY_NAME) == null) {
		Boolean ignore = environment.getProperty("spring.beaninfo.ignore", Boolean.class, Boolean.TRUE);
		System.setProperty(CachedIntrospectionResults.IGNORE_BEANINFO_PROPERTY_NAME, ignore.toString());
	}
}

该方法主要根据配置文件中的 spring.beaninfo.ignore 参数来控制是否跳过对 BeanInfo 实现类的搜索 (BeanInfo 接口主要用于提供 bean 相关的方法、属性、时间以及其他功能)

6.7 创建 Banner 打印类
Banner printedBanner = printBanner(environment);

该方法主要用于在程序启动时,输出 Banner 信息,我们可以在 resource 目录下自定义一个名为 banner.txt 的文件,则在程序启动时会覆盖默认的 Banner 信息
在这里插入图片描述

6.8 创建应用上下文
context = createApplicationContext();

protected ConfigurableApplicationContext createApplicationContext() {
	Class<?> contextClass = this.applicationContextClass;
	if (contextClass == null) {
		try {
			switch (this.webApplicationType) {
			case SERVLET:
				contextClass = Class.forName(DEFAULT_SERVLET_WEB_CONTEXT_CLASS);
				break;
			case REACTIVE:
				contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);
				break;
			default:
				contextClass = Class.forName(DEFAULT_CONTEXT_CLASS);
			}
		}
		catch (ClassNotFoundException ex) {
			throw new IllegalStateException(
					"Unable create a default ApplicationContext, please specify an ApplicationContextClass", ex);
		}
	}
	return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
}

该方法主要用于创建应用上下文,共有以下三类

应用上下文含义
DEFAULT_SERVLET_WEB_CONTEXT_CLASSServlet Web 应用上下文
StandardReactiveWebEnvironment响应式 Web 应用上下文
DEFAULT_CONTEXT_CLASS默认应用上下文
6.9 创建异常报告类集合
exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
		new Class[] { ConfigurableApplicationContext.class }, context);

该方法创建异常报告类集合的流程与之前步骤 5.2 中设置应用上下文初始化器是一样的,只是传入的参数不同,此处传入的参数为 SpringBootExceptionReporter.class,其在 META-INF/spring.factories 文件 (此处 spring.factories 文件位于 spring-boot-2.3.4.RELEASE.jar!/META-INF/ 目录下) 中的配置类全限定类名如下所示
在这里插入图片描述
在这里插入图片描述

6.10 准备应用上下文
prepareContext(context, environment, listeners, applicationArguments, printedBanner);

private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment,
		SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {
	// 绑定环境至应用上下文
	context.setEnvironment(environment);
	// 配置应用上下文的 BeanName 生成器以及资源加载器
	postProcessApplicationContext(context);
	// 应用上下文初始化器初始化应用上下文
	applyInitializers(context);
	// 回调函数 (在应用上下文已创建和准备但尚未加载之前调用)
	listeners.contextPrepared(context);
	/*
	 * 判断是否记录应用程序信息
	 * 在配置文件中使用 spring.main.log-startup-info 参数进行控制
	 */
	if (this.logStartupInfo) {
		logStartupInfo(context.getParent() == null);
		logStartupProfileInfo(context);
	}
	// 获取 Bean 工厂类
	ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
	// 注册特殊单例 Bean 对象 (springApplicationArguments)
	beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
	if (printedBanner != null) {
	    // 注册特殊单例 Bean 对象 (springBootBanner)
		beanFactory.registerSingleton("springBootBanner", printedBanner);
	}
	if (beanFactory instanceof DefaultListableBeanFactory) {
	    // 设置是否允许覆盖 Bean
		((DefaultListableBeanFactory) beanFactory)
				.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
	}
	// 判断是否懒加载
	if (this.lazyInitialization) {
		context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());
	}
	// 加载所有资源
	Set<Object> sources = getAllSources();
	Assert.notEmpty(sources, "Sources must not be empty");
	// 加载 Bean 对象到应用上下文
	load(context, sources.toArray(new Object[0]));
	// 回调函数 (在应用上下文已加载但尚未刷新之前调用)
	listeners.contextLoaded(context);
}
  1. 配置应用上下文的 BeanName 生成器以及资源加载器

    postProcessApplicationContext(context);
    
    protected void postProcessApplicationContext(ConfigurableApplicationContext context) {
        // 配置 BeanName 生成器
    	if (this.beanNameGenerator != null) {
    		context.getBeanFactory().registerSingleton(AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR,
    				this.beanNameGenerator);
    	}
    	// 配置资源加载器
    	if (this.resourceLoader != null) {
    		if (context instanceof GenericApplicationContext) {
    			((GenericApplicationContext) context).setResourceLoader(this.resourceLoader);
    		}
    		if (context instanceof DefaultResourceLoader) {
    			((DefaultResourceLoader) context).setClassLoader(this.resourceLoader.getClassLoader());
    		}
    	}
    	// 判断是否添加转换服务
    	if (this.addConversionService) {
    		context.getBeanFactory().setConversionService(ApplicationConversionService.getSharedInstance());
    	}
    }
    
  2. 应用上下文初始化器初始化应用上下文

    applyInitializers(context);
    
    protected void applyInitializers(ConfigurableApplicationContext context) {
    	for (ApplicationContextInitializer initializer : getInitializers()) {
    		Class<?> requiredType = GenericTypeResolver.resolveTypeArgument(initializer.getClass(),
    				ApplicationContextInitializer.class);
    		Assert.isInstanceOf(requiredType, context, "Unable to call initializer.");
    		initializer.initialize(context);
    	}
    }
    

    该方法主要用于让所有的应用上下文初始化器调用 initialize() 方法来初始化应用上下文,此处的获取的所有初始化器在步骤 5.2 中进行设置

6.11 发布应用上下文并启动
listeners.started(context);

void started(ConfigurableApplicationContext context) {
	for (SpringApplicationRunListener listener : this.listeners) {
		listener.started(context);
	}
}

default void started(ConfigurableApplicationContext context) {
}

public void started(ConfigurableApplicationContext context) {
	context.publishEvent(new ApplicationStartedEvent(this.application, this.args, context));
	AvailabilityChangeEvent.publish(context, LivenessState.CORRECT);
}

该方法主要用于循环遍历所有的监听器,并调用其 started() 方法,此时应用上下文已刷新且程序已启动,但尚未调用 CommandLineRunner 和 ApplicationRunner

6.12 调用 Runner 运行类 (ApplicationRunner、CommandLineRunner)
callRunners(context, applicationArguments);

private void callRunners(ApplicationContext context, ApplicationArguments args) {
	List<Object> runners = new ArrayList<>();
	runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());
	runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());
	AnnotationAwareOrderComparator.sort(runners);
	for (Object runner : new LinkedHashSet<>(runners)) {
		if (runner instanceof ApplicationRunner) {
			callRunner((ApplicationRunner) runner, args);
		}
		if (runner instanceof CommandLineRunner) {
			callRunner((CommandLineRunner) runner, args);
		}
	}
}

该方法主要用于调用 ApplicationRunner、CommandLineRunner 中的 run() 方法,关于两者的区别可以参考如下文章:SpringBoot – 06 – ApplicationRunner和CommandLineRunner的区别

6.13 发布应用上下文并运行
listeners.running(context);

void running(ConfigurableApplicationContext context) {
	for (SpringApplicationRunListener listener : this.listeners) {
		listener.running(context);
	}
}

default void running(ConfigurableApplicationContext context) {
}

public void running(ConfigurableApplicationContext context) {
	context.publishEvent(new ApplicationReadyEvent(this.application, this.args, context));
	AvailabilityChangeEvent.publish(context, ReadinessState.ACCEPTING_TRAFFIC);
}

该方法主要用于循环遍历所有的监听器,并调用其 running() 方法,此时应用上下文已刷新且程序已启动,同时已调用所有的 CommandLineRunner 和 ApplicationRunner

7、归纳总结

SpringBoot 启动流程如下

  1. 启动 SpringBoot 项目

  2. 创建 SpringApplication 对象

    1. 初始化要加载的资源类

    2. 根据 spring.factories 文件创建并设置相应的应用上下文初始化器

    3. 根据 spring.factories 文件创建并设置相应的监听器

  3. 调用 run() 方法

    1. 创建并启动计时监控类

    2. 创建并启动应用运行监听器

    3. 创建应用参数类

    4. 根据应用运行监听器以及应用参数来创建并准备应用环境

    5. 创建并准备应用上下文,将应用环境绑定至应用上下文

    6. 上下文初始化器初始化应用上下文

    7. 发布应用上下文并运行

8、参考资料

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值