SpringBoot是如何启动的?
source link: http://www.cnblogs.com/xwgblog/p/11798560.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
本文是通过查看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
加载的。
小结
完成初始化阶段后,可以知道以下信息:
ApplicationContextInitializer 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 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实现原理
- ...
《架构文摘》每天一篇架构领域重磅好文,涉及一线互联网公司应用架构(高可用、高性 能、高稳定)、大数据、机器学习等各个热门领域。
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK