SpringBoot是怎样启动的?这篇文章通知你答案!
2019-11-18杂谈搜奇网38°c
A+ A-本文是经由历程检察SpringBoot源码整理出来的SpringBoot大抵启动流程,团体大方向是以简朴为起点,不说太多庞杂的东西,内部完成细节本文不深扣由于每一个人的思绪、明白都不一样,我个人看的明白跟人人看的一定不一样,到时候表达的出来的云里雾里也没啥用。
起首我将SpringBoot的启动流程整理成以下阶段:
- SpringApplicaiton初始化
- 检察ApplicationContext范例
- 加载ApplicationContextInitializer
- 加载ApplicationListener
- Environment初始化
- 剖析命令行参数
- 竖立Environment
- 设置Environment
- 设置SpringApplication
- ApplicationContext初始化
- 竖立ApplicationContext
- 设置ApplicationContext
- 革新ApplicationContext
- 运转顺序进口
省去了一些不影响主流程的细节,在检察SpringBoot源码之前,不得不提一下spring.factories
这个文件的运用和功用。
关于spring.factories
spring.factories
是一个properties文件,它位于classpath:/META-INF/
目次内里,每一个jar包都能够有spring.factories
的文件。Spring供应东西类SpringFactoriesLoader
担任加载、剖析文件,如spring-boot-2.2.0.RELEASE.jar
内里的META-INF
目次内里就有spring.factories
文件:
# 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
...
关于spring.factories
须要晓得些什么?
spring.factories
是一个properties文件spring.factories
里的键值对的value是以逗号分开的完整类名列表
spring.factories
里的键值对的key是完整接口称号
spring.factories
键值对的value是key的完成类spring.factories
是由SpringFactoriesLoader
东西类加载spring.factories
位于classpath:/META-INF/
目次SpringFactoriesLoader
会加载jar包内里的spring.factories
文件并举行兼并
晓得spring.factories
的观点后,继承来剖析SpringBoot的启动。
SpringApplicaiton初始化
Java顺序的进口在main
要领SpringBoot的一样能够经由历程main
要领启动,只须要少许的代码加上@SpringBootApplication
注解,很轻易的就启动SpringBoot:
@SpringBootApplication
@Slf4j
public class SpringEnvApplication {
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(SpringEnvApplication.class, args);
}
}
SpringApplicaiton初始化位于SpringApplication
的组织函数中:
public SpringApplication(Class<?>... primarySources) {
this(null, primarySources);
}
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
this.webApplicationType = WebApplicationType.deduceFromClasspath();
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
this.mainApplicationClass = deduceMainApplicationClass();
}
简朴的说下SpringApplication
的组织函数干了些啥:
- 基础变量赋值(resourceLoader、primarySources、...)
- 检察ApplicationContext范比方(Web、Reactive、Standard)
- 加载ApplicationContextInitializer
- 加载ApplicationListener
- 检察启动类(main要领的类)
然后再来逐一剖析这些步骤。
检察ApplicationContext范例
SpringBoot会在初始化阶段检察ApplicationContext
的范例,检察体式格局是经由历程罗列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;
}
WebApplicationType
罗列用于标记顺序是不是为Web顺序,它有三个值:
- NONE:不是web顺序
- SERVLET:基于Servlet的Web顺序
- REACTIVE:基于Reactive的Web顺序
简朴的来讲该要领会经由历程classpath来推断是不是Web顺序,要领中的常量是完整的class类名:
private static final String[] SERVLET_INDICATOR_CLASSES = { "javax.servlet.Servlet","org.springframework.web.context.ConfigurableWebApplicationContext" };
private static final String WEBMVC_INDICATOR_CLASS = "org.springframework.web.servlet.DispatcherServlet";
private static final String WEBFLUX_INDICATOR_CLASS = "org.springframework.web.reactive.DispatcherHandler";
private static final String JERSEY_INDICATOR_CLASS = "org.glassfish.jersey.servlet.ServletContainer";
private static final String SERVLET_APPLICATION_CONTEXT_CLASS = "org.springframework.web.context.WebApplicationContext";
private static final String REACTIVE_APPLICATION_CONTEXT_CLASS = "org.springframework.boot.web.reactive.context.ReactiveWebApplicationContext";
比方经由历程pom.xml
文件引入spring-boot-starter-web
那classpath就会有org.springframework.web.context.ConfigurableWebApplicationContext
和javax.servlet.Servlet
类,如许就决议了顺序的ApplicationContext
范例为WebApplicationType.SERVLET
。
加载ApplicationContextInitializer
ApplicationContextInitializer
会在革新context之前实行,平常用来做一些分外的初始化工程如:增加PropertySource
、设置ContextId
等事变它只要一个initialize
要领:
public interface ApplicationContextInitializer<C extends ConfigurableApplicationContext> {
void initialize(C applicationContext);
}
SpringBoot经由历程SpringFactoriesLoader
加载spring.factories
中的设置读取key为org.springframework.context.ApplicationContextInitializer
的value,前面提到过spring.factoies
中的设置的value都为key的完成类:
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer,\
org.springframework.boot.context.ContextIdApplicationContextInitializer,\
org.springframework.boot.context.config.DelegatingApplicationContextInitializer,\
org.springframework.boot.rsocket.context.RSocketPortInfoApplicationContextInitializer,\
org.springframework.boot.web.context.ServerPortInfoApplicationContextInitializer
上面列出的是spring-boot-2.2.0.RELEASE.jar
中包含的设置,其他jar包也有能够设置org.springframework.context.ApplicationContextInitializer
来完成分外的初始化事变。
加载ApplicationListener
ApplicationListener
用于监听ApplicationEvent
事宜,它的初始加载流程跟加载ApplicationContextInitializer
类似,在spring.factories
中也会设置一些优先级较高的ApplicationListener
:
# 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
ApplicationListener
的加载流程跟ApplicationContextInitializer
类似都是经由历程SpringFactoriesLoader
加载的。
小结
完成初始化阶段后,能够晓得以下信息:
- ApplicationContext是Web照样其他范例
- SpringApplication中有一些
ApplicationContextInitializer
完成类 - SpringApplication中有一些
ApplicationListener
的完成类
Environment初始化
初始化事变完成后SpringBoot会干许多事变来为运转顺序做好预备,SpringBoot启动中心代码大部分都位于SpringApplication实例的run
要领中,在环境初始化大抵的启动流程包含:
- 剖析命令行参数
- 预备环境(Environment)
- 设置环境
固然还会有一些别的操纵如:
- 实例化SpringApplicationRunListeners
- 打印Banner
- 设置非常报告
- ...
这些不是重要的操纵就不讲解了,能够看完文章再细细研讨。
剖析命令行参数
命令行参数是由main
要领的args
参数通报进来的,SpringBoot在预备阶段竖立一个DefaultApplicationArguments
类用来剖析、保留命令行参数。如--spring.profiles.active=dev
就会将SpringBoot的spring.profiles.active
属性设置为dev。
public ConfigurableApplicationContext run(String... args) {
...
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
...
}
SpringBoot还会将收到的命令行参数放入到Environment
中,供应一致的属性笼统。
竖立Environment
竖立环境的代码比较简朴,依据之前提到过的WebApplicationType
来实例化差别的环境:
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();
}
}
预备Environment
环境(Environment)大抵由Profile和PropertyResolver构成:
- Profile是BeanDefinition的逻辑分组,定义Bean时能够指定Profile使SpringBoot在运转时会依据Bean的Profile决议是不是注册Bean
- PropertyResolver是特地用来剖析属性的,SpringBoot会在启动时加载设置文件、体系变量等属性
SpringBoot在预备环境时会挪用SpringApplication
的prepareEnvironment
要领:
private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments) {
// Create and configure the environment
ConfigurableEnvironment environment = getOrCreateEnvironment();
configureEnvironment(environment, applicationArguments.getSourceArgs());
ConfigurationPropertySources.attach(environment);
listeners.environmentPrepared(environment);
bindToSpringApplication(environment);
...
return environment;
}
prepareEnvironment
要领大抵完成以下事变:
- 竖立一个环境
- 设置环境
- 设置SpringApplication的属性
设置Environment
竖立完环境后会为环境做一些简朴的设置:
protected void configureEnvironment(ConfigurableEnvironment environment, String[] args) {
if (this.addConversionService) {
ConversionService conversionService = ApplicationConversionService.getSharedInstance();
environment.setConversionService((ConfigurableConversionService) conversionService);
}
configurePropertySources(environment, args);
configureProfiles(environment, args);
}
protected void configurePropertySources(ConfigurableEnvironment environment, String[] args) {
if (this.addCommandLineProperties && args.length > 0) {
...
sources.addFirst(new SimpleCommandLinePropertySource(args));
...
}
}
protected void configureProfiles(ConfigurableEnvironment environment, String[] args) {
Set<String> profiles = new LinkedHashSet<>(this.additionalProfiles);
profiles.addAll(Arrays.asList(environment.getActiveProfiles()));
environment.setActiveProfiles(StringUtils.toStringArray(profiles));
}
篇幅有限省去一些不重要的代码,设置环境重要用于:
- 设置ConversionService: 用于属性转换
- 将命令行参数增加到环境中
- 增加分外的ActiveProfiles
SpringApplicaton属性设置
设置SpringApplicaton
重如果将已有的属性连接到SpringApplicaton
实例,如spring.main.banner-mode
属性就对应于bannerMode
实例属性,这一步的属性泉源有三种(没有自定义的状况):
- 环境变量
- 命令行参数
- JVM体系属性
SpringBoot会将前缀为spring.main
的属性绑定到SpringApplicaton
实例:
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初始化小结
总结下环境预备阶段所做的大抵事变:
- 依据
WebApplicationType
罗列竖立环境 - 设置
ConversionService
用于转换属性变量 - 将命令行参数
args
增加到环境 - 将外部设置的Profiles增加到环境
- 绑定SprinngApplicaiton属性
- 发送环境
Prepared
事宜
ApplicationContext初始化
前面提到的一些步骤大部分都是为了预备ApplicationContext
所做的事变,ApplicationContext
供应加载Bean、加载资本、发送事宜等功用,SpringBoot在启动历程当中竖立、设置好ApplicationContext
不须要开辟都作分外的事变(太轻易啦~~)。
本文不盘算深切ApplicationContext
中,由于与ApplicationContext
相干的类许多,不是一两篇文章写的完的,发起按模块来看,末了再整合起来看ApplicationContext
源码。
竖立ApplicationContext
竖立ApplicationContext
的历程与竖立环境基础模类似,依据WebApplicationType
推断顺序范例竖立差别的ApplicationContext
:
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);
}
前面提到过WebApplicationType
有三个成员(SERVLET,REACTIVE,NONE),离别对应差别的context范例为:
- SERVLET: AnnotationConfigServletWebServerApplicationContext
- REACTIVE: AnnotationConfigReactiveWebServerApplicationContext
- NONE: AnnotationConfigApplicationContext
预备ApplicationContext
竖立完ApplicationContext
完后须要初始化下它,设置环境、运用ApplicationContextInitializer、注册Source类等,SpringBoot的预备Context的流程能够归纳以下:
- 为
ApplicationContext
设置环境(之前竖立的环境) - 基础设置操纵设置BeanNameGenerator、ResourceLoader、ConversionService等
- 实行
ApplicationContextInitializer
的initialize
要领(ApplicationContextInitializer是在初始化阶段猎取的) - 注册命令行参数(springApplicationArguments)
- 注册Banner(springBootBanner)
- 注册sources(由@Configuration注解的类)
预备ApplicationContext
的代码以下所示:
private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment,
SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {
context.setEnvironment(environment);
postProcessApplicationContext(context);
applyInitializers(context);
listeners.contextPrepared(context);
if (this.logStartupInfo) {
logStartupInfo(context.getParent() == null);
logStartupProfileInfo(context);
}
// Add boot specific singleton beans
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
if (printedBanner != null) {
beanFactory.registerSingleton("springBootBanner", printedBanner);
}
if (beanFactory instanceof DefaultListableBeanFactory) {
((DefaultListableBeanFactory) beanFactory)
.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
}
if (this.lazyInitialization) {
context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());
}
// Load the sources
Set<Object> sources = getAllSources();
Assert.notEmpty(sources, "Sources must not be empty");
load(context, sources.toArray(new Object[0]));
listeners.contextLoaded(context);
}
注重注册sources
这一步,sources是@Configuration注解的类SpringBoot依据供应的sources注册Bean,基础道理是经由历程剖析注解元数据,然后竖立BeanDefinition然后将它注册进ApplicationContext
内里。
革新ApplicationContext
如果说SpringBoot的是个汽车,那前面所做的操纵都是开门、系安全带等基础操纵了,革新ApplicationContext就是点火了,没革新ApplicationContext只是保留了一个Bean的定义、后处理器啥的没有真正跑起来。革新ApplicationContext这个内容很重要,要明白ApplicationContext照样要看革新操纵的源码,
这里先简朴列一下基础步骤:
- 预备革新(考证属性、设置监听器)
- 初始化BeanFactory
- 实行BeanFactoryPostProcessor
- 注册BeanPostProcessor
- 初始化MessageSource
- 初始化事宜播送
- 注册ApplicationListener
- ...
革新流程步骤比较多,关联的类库都相对比较庞杂,发起先看完其他辅佐类库再来看革新源码,会事半功倍。
运转顺序进口
context革新完成后Spring容器能够完整运用了,接下来SpringBoot会实行ApplicationRunner
和CommandLineRunner
,这两接口功用类似都只要一个run
要领只是吸收的参数差别而以。经由历程完成它们能够自定义启动模块,如启动dubbo
、gRPC
等。
ApplicationRunner
和CommandLineRunner
的挪用代码以下:
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);
}
}
}
callRunners
实行完后,SpringBoot的启动流程就完成了。
总结
经由历程检察SpringApplication的源码,发明SpringBoot的启动源码还好明白,重要照样为ApplicationContext供应一个初始化的进口,免除开辟人员设置ApplicationContext的事变。SpringBoot的中心功用照样自动设置,下次剖析下SpringBoot Autoconfig的源码,要充足明白SpringBoot看源码是少了的。
看完SpringApplication的源码还有些题目值得思索:
- SpringBoot是启动Tomcat的流程
- SpringBoot自动设置道理
- SpringBoot Starter自定义
- BeanFactoryPostProcessor和BeanPostProcessor完成道理
- ...
《架构文摘》天天一篇架构范畴重磅好文,触及一线互联网公司运用架构(高可用、高性 能、高稳固)、大数据、机械进修等各个热点范畴。