SpringBoot启动

380 阅读4分钟
代码版本 v2.1.7.RELEASE

大致分析一下SpringBoot启动过程中做了什么

一般启动一个SpringBoot项目

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

run的代码如下

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

启动代码分析,做了两件事

  • 创建SpringApplication对象
  • 执行run方法

下面依次分析两个步骤

创建SpringApplication对象

public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
	this.resourceLoader = resourceLoader;
	Assert.notNull(primarySources, "PrimarySources must not be null");
	//初始化主类
	this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
	//判断当前应用类型类型,这个会影响到创建Environment,ConfigApplicationContext类型
	this.webApplicationType = WebApplicationType.deduceFromClasspath();
	//初始化 ApplicationContextInitializer类,之后会在run过程中用到
	setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
	//获取所有监听器,之后会在run过程中用到
	setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
	//获取main函数所在的类
	this.mainApplicationClass = deduceMainApplicationClass();
}

WebApplicationType.deduceFromClasspath()

这个就是通过classpath下是否存在指定的类来判断当前SpringBoot应该属于那种类型,现阶段包含三种类型,之后创建spirng容器(ConfigurableApplicationContext),还有Environment(ConfigurableEnvironment)时候回根据这个类型来判断,创建相应的

  • NONE 非WEB容器,这个类型不会启动web server(内置的tomcat...)
  • SERVLET WEB容器,启动内嵌的web server
  • REACTIVE WEB响应式容器,源码解释是 *The application should run as a reactive web application and should start an embedded reactive web server. *

getSpringFactoriesInstances(...)使用

关于getSpringFactoriesInstances(...) 作用就是从META-INF/spring.factories获取指定的实现类,SpringBoot下spring.factories内容如下,其中 setInitializers(...),setListeners(...)都是通过此方法加载org.springframework.context.ApplicationContextInitializer、org.springframework.context.ApplicationListener下的所有类,然后赋值给 initializers、listeners变量,这两个变量具体作用会在run中体现出来

# PropertySource Loaders
org.springframework.boot.env.PropertySourceLoader=\
org.springframework.boot.env.PropertiesPropertySourceLoader,\
org.springframework.boot.env.YamlPropertySourceLoader

# Run Listeners
org.springframework.boot.SpringApplicationRunListener=\
org.springframework.boot.context.event.EventPublishingRunListener

# Error Reporters
org.springframework.boot.SpringBootExceptionReporter=\
org.springframework.boot.diagnostics.FailureAnalyzers

# Application Context Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer,\
org.springframework.boot.context.ContextIdApplicationContextInitializer,\
org.springframework.boot.context.config.DelegatingApplicationContextInitializer,\
org.springframework.boot.web.context.ServerPortInfoApplicationContextInitializer

# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.boot.ClearCachesApplicationListener,\
org.springframework.boot.builder.ParentContextCloserApplicationListener,\
org.springframework.boot.context.FileEncodingApplicationListener,\
org.springframework.boot.context.config.AnsiOutputApplicationListener,\
org.springframework.boot.context.config.ConfigFileApplicationListener,\
org.springframework.boot.context.config.DelegatingApplicationListener,\
org.springframework.boot.context.logging.ClasspathLoggingApplicationListener,\
org.springframework.boot.context.logging.LoggingApplicationListener,\
org.springframework.boot.liquibase.LiquibaseServiceLocatorApplicationListener

# Environment Post Processors
org.springframework.boot.env.EnvironmentPostProcessor=\
org.springframework.boot.cloud.CloudFoundryVcapEnvironmentPostProcessor,\
org.springframework.boot.env.SpringApplicationJsonEnvironmentPostProcessor,\
org.springframework.boot.env.SystemEnvironmentPropertySourceEnvironmentPostProcessor

# Failure Analyzers
org.springframework.boot.diagnostics.FailureAnalyzer=\
org.springframework.boot.diagnostics.analyzer.BeanCurrentlyInCreationFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.BeanDefinitionOverrideFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.BeanNotOfRequiredTypeFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.BindFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.BindValidationFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.UnboundConfigurationPropertyFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.ConnectorStartFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.NoSuchMethodFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.NoUniqueBeanDefinitionFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.PortInUseFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.ValidationExceptionFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.InvalidConfigurationPropertyNameFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.InvalidConfigurationPropertyValueFailureAnalyzer

# FailureAnalysisReporters
org.springframework.boot.diagnostics.FailureAnalysisReporter=\
org.springframework.boot.diagnostics.LoggingFailureAnalysisReporter

执行run方法

看一下SpringBoot run方法的整体流程,代码如下

public ConfigurableApplicationContext run(String... args) {
	//计时工具,记录启动总共耗时
	StopWatch stopWatch = new StopWatch();
	stopWatch.start();
	ConfigurableApplicationContext context = null;
	Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
	configureHeadlessProperty();
	//获取SpringApplicationRunListener,从META-INF/spring.factories获取 SpringApplicationRunListener,此处为EventPublishingRunListener
	SpringApplicationRunListeners listeners = getRunListeners(args);
	//广播 ApplicationStartingEvent事件
	listeners.starting();
	try {
		ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
		//创建environment,并做准备
		ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
		configureIgnoreBeanInfo(environment);
		Banner printedBanner = printBanner(environment);
		//创建Spring上下文,会根据webApplicationType 类型的不同创建不同的上下文
		context = createApplicationContext();
		exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
				new Class[] { ConfigurableApplicationContext.class }, context);
		//<2>.准备Spring上下文
		prepareContext(context, environment, listeners, applicationArguments, printedBanner);
		//刷新Spring上下文
		refreshContext(context);
		afterRefresh(context, applicationArguments);
		stopWatch.stop();
		if (this.logStartupInfo) {
			new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
		}
		//广播ApplicationStartedEvent事件
		listeners.started(context);
		//从spring中获取所有 ApplicationRunner,CommandLineRunner 执行
		callRunners(context, applicationArguments);
	}
	catch (Throwable ex) {
		handleRunFailure(context, ex, exceptionReporters, listeners);
		throw new IllegalStateException(ex);
	}

	try {
		//广播ApplicationReadyEvent事件
		listeners.running(context);
	}
	catch (Throwable ex) {
		handleRunFailure(context, ex, exceptionReporters, null);
		throw new IllegalStateException(ex);
	}
	return context;
}

prepareEnvironment

这个方法是对环境变量的操作

private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
		ApplicationArguments applicationArguments) {
	// Create and configure the environment 此处也会根据 webApplicationType 判断需要创建什么类型的environment
	ConfigurableEnvironment environment = getOrCreateEnvironment();
	//配置environment,包含PropertySources ,Profiles
	configureEnvironment(environment, applicationArguments.getSourceArgs());
	//广播ApplicationEnvironmentPreparedEvent事件
	listeners.environmentPrepared(environment);
	//将配置文件中spring.main开头的配置绑定在SpringApplication中,可以通过配置文件控制SpringApplication的参数值
	bindToSpringApplication(environment);
	if (!this.isCustomEnvironment) {
		environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,
				deduceEnvironmentClass());
	}
	ConfigurationPropertySources.attach(environment);
	return environment;
}
getOrCreateEnvironment

Environment 里面存放的东西就是从 System中、application.properties/yml文件中的数据

这个方法会根据前文说的 WebApplicationType来具体生成不同的ConfigurableEnvironment,ConfigurableEnvironment作用这个类主要作用就是存放解析全局性的参数,包括,根据WebApplicationType类型生成的类型有以下几种

  • SERVLET 对应 StandardServletEnvironment
  • REACTIVE 对应 StandardReactiveWebEnvironment
  • 默认的 StandardEnvironment
configureEnvironment

设置ConfigurableEnvironment的conversionService 设置ConfigurableEnvironment的MutablePropertySources 设置ConfigurableEnvironment的activeProfiles,从spring.profiles.active获取的 + SpringApplication类中的additionalProfiles的值,例如 我设置 spring.main.allowBeanDefinitionOverriding=false,就能控制Spring两个相同key的bean相同时抛异常

bindToSpringApplication
protected void bindToSpringApplication(ConfigurableEnvironment environment) {
	try {
		Binder.get(environment).bind("spring.main", Bindable.ofInstance(this));
	}
	catch (Exception ex) {
		throw new IllegalStateException("Cannot bind to SpringApplication", ex);
	}
}

看代码意思简介明了,拿取Environment的数据,将spring.mian开头的参数绑定到SpringApplication上,所以SpringApplication上的参数可以通过yml配置

createApplicationContext

这个跟getOrCreateEnvironment方法一样,根据不同的WebApplicationType来确定启动不同的spirng容器

  • SERVLET ->org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext
  • REACTIVE -> org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext
  • 默认 -> org.springframework.context.annotation.AnnotationConfigApplicationContext

prepareContext

代码如下

private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment,
		SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {
	//将常见的Environment 添加到Spring容器中
	context.setEnvironment(environment);
	postProcessApplicationContext(context);
	applyInitializers(context);
	//广播ApplicationContextInitializedEvent事件
	listeners.contextPrepared(context);
	if (this.logStartupInfo) {
		logStartupInfo(context.getParent() == null);
		logStartupProfileInfo(context);
	}
	// Add boot specific singleton beans 将ApplicationArguments参数,spirngBootBanner添加到参数中
	ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
	beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
	if (printedBanner != null) {
		beanFactory.registerSingleton("springBootBanner", printedBanner);
	}
	// 设置allowBeanDefinitionOverriding参数,此参数通过SpringBoot暴露给用户,此参数控制Spring两个相同key的bean是否覆盖
	if (beanFactory instanceof DefaultListableBeanFactory) {
		((DefaultListableBeanFactory) beanFactory)
				.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
	}
	// Load the sources
	Set<Object> sources = getAllSources();
	Assert.notEmpty(sources, "Sources must not be empty");
	load(context, sources.toArray(new Object[0]));
	//广播ApplicationPreparedEvent事件
	listeners.contextLoaded(context);
}

总结

SpringBoot 再启动的时候,做了以下事情

  • 启动计时器
  • 初始化监听器 SpringApplicationRunListeners,里面只包含一个listener(EventPublishingRunListener)
  • 广播 ApplicationStartingEvent事件
  • 创建 DefaultApplicationArguments 此时这个类就是存放通过main函数传入的args
  • 创建 ConfigurableEnvironment
  • 配置 ConfigurableEnvironment,包含PropertySources ,Profiles
  • 广播 ApplicationEnvironmentPreparedEvent事件
  • 拿取ConfigurableEnvironment的数据,将spring.mian开头的参数绑定到SpringApplication上
  • 打印Banner
  • 创建 ConfigurableApplicationContext (spirng context)
  • ConfigurableApplicationContext.set(ConfigurableEnvironment)
  • 执行初始化的所有ApplicationContextInitializer
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer,\
org.springframework.boot.context.ContextIdApplicationContextInitializer,\
org.springframework.boot.context.config.DelegatingApplicationContextInitializer,\
org.springframework.boot.web.context.ServerPortInfoApplicationContextInitializer
  • 广播 ApplicationContextInitializedEvent 事件
  • 将 DefaultApplicationArguments对象放入ConfigurableApplicationContext 的beanFactory 中
  • 设置beanFactory的allowBeanDefinitionOverriding 属性
  • 广播 ApplicationPreparedEvent事件
  • 执行ConfigurableApplicationContext.refresh()刷新Spring上下文,此时Spring容器正式启动发挥作用,包括读Bean 还有AOP 等等
  • 广播 ApplicationStartedEvent 事件
  • 获取spring中所有 ApplicationRunner,CommandLineRunner 执行 run方法
  • 广播 ApplicationReadyEvent 事件

各种广播其实就是SpringBoot还有Spring的各种扩展点,可以在SpringBoot启动到相应的阶段做些自己想做的事情