本文是經過查看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
架構知識丨大型網站丨大數據丨機器學習
若有收穫,點個在看,誠摯感謝圖片