本文是经过查看SpringBoot源码整理出来的SpringBoot大体启动流程,总体大方向是以简单为出发点,不说太多复杂的东西,内部实现细节本文不深扣由于每一个人的思路、理解都不同。java
首先我将SpringBoot的启动流程整理成如下阶段:react
省去了一些不影响主流程的细节,在查看SpringBoot源码以前,不得不提一下spring.factories这个文件的使用和功能。web
spring.factories是一个properties文件,它位于classpath:/META-INF/目录里面,每一个jar包均可以有spring.factories的文件。Spring提供工具类SpringFactoriesLoader负责加载、解析文件,如spring-boot-2.2.0.RELEASE.jar里面的META-INF目录里面就有spring.factories文件:spring
# 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的概念后,继续来分析SpringBoot的启动。架构
Java程序的入口在main方法SpringBoot的一样能够经过main方法启动,只须要少许的代码加上@SpringBootApplication注解,很容易的就启动SpringBoot:app
@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的构造函数干了些啥:socket
而后再来逐个分析这些步骤。分布式
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程序,它有三个值:
简单的来讲该方法会经过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就会有ConfigurableWebApplicationContext和Servlet类,这样就决定了程序的ApplicationContext类型为WebApplicationType.SERVLET。
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包也有可能配置ApplicationContextInitializer来实现额外的初始化工做。
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,\ o
ApplicationListener的加载流程跟ApplicationContextInitializer相似都是经过SpringFactoriesLoader加载的。
完成初始化阶段后,能够知道如下信息:
初始化工做完成后SpringBoot会干不少事情来为运行程序作好准备,SpringBoot启动核心代码大部分都位于SpringApplication实例的run方法中,在环境初始化大体的启动流程包括:
固然还会有一些别的操做如:
这些不是重要的操做就不讲解了,能够看完文章再细细研究。
令行参数是由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中,提供统一的属性抽象。
建立环境的代码比较简单,根据以前提到过的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)大体由Profile和PropertyResolver组成:
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方法大体完成如下工做:
建立完环境后会为环境作一些简单的配置:
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)); }
篇幅有限省去一些不重要的代码,配置环境主要用于:
配置SpringApplicaton主要是将已有的属性链接到SpringApplicaton实例,如spring.main.banner-mode属性就对应于bannerMode实例属性,这一步的属性来源有三种(没有自定义的状况):
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); } }
总结下环境准备阶段所作的大体工做:
前面提到的一些步骤大部分都是为了准备ApplicationContext所作的工做,ApplicationContext提供加载Bean、加载资源、发送事件等功能,SpringBoot在启动过程当中建立、配置好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类型为:
建立完ApplicationContext完后须要初始化下它,设置环境、应用ApplicationContextInitializer、注册Source类等,SpringBoot的准备Context的流程能够概括以下:
准备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里面。
若是说SpringBoot的是个汽车,要启动这辆汽车那前面所作的操做都是开门、系安全带等基本操做了,刷新ApplicationContext就是点火了,没刷新ApplicationContext只是保存了一个Bean的定义、后处理器啥的没有真正跑起来。 刷新context的基本步骤:
刷新流程步骤比较多,关联的类库都相对比较复杂,建议先看完其余辅助类库再来看刷新源码,会事半功倍。
context刷新完成后Spring容器能够彻底使用了,接下来SpringBoot会执行ApplicationRunner和CommandLineRunner,这两接口功能类似都只有一个run方法只是接收的参数不一样而以。经过实现它们能够自定义启动模块,如启动dubbo、gRPC等,callRunners方法以下:
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的核心功能仍是自动配置。
看完SpringApplication的源码还有些问题值得思考:
-END-
架构文摘
ArchDigest
架构知识丨大型网站丨大数据丨机器学习若有收获,点个在看,诚挚感谢图片