SpringApplication類用於引導和啓動一個Spring應用程序(即SpringBoot開發的應用)。一般用SpringBoot開發一個應用程序時,在主類的main函數中能夠經過以下代碼啓動一個Spring應用:html
@SpringBootApplication public class App { public static void main(String[] args) { SpringApplication.run(App.class, args); } }
SpringApplication的靜態方法run(Class<?> primarySource, String... args))
的第一個參數接受一個Spring容器配置類(用Java代碼對Spring容器進行配置)。第二個參數是命令行參數。將命令行參數轉發給SpringApplication類,就能夠在用java命令啓動應用時,經過命令行參數對Spring應用作一些配置。
SpringApplication類會作以下事情啓動應用:java
下面咱們經過SpringApplication的源碼詳細描述上述這些過程。react
下面是SpringApplication類靜態run方法的源碼。能夠看到,當咱們調用這個靜態run方法時,實際上會構造一個SpringApplication實例,而後再調用實例的run方法完成spring應用的啓動。web
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) { return new SpringApplication(primarySources).run(args); }
下面是SpringApplication的構造函數,它主要完成下面初始化工做:spring
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(); }
下面對這些初始化過程進行一一說明:app
SpringApplication可以從各類不一樣的配置源讀取bean的定義。Spring Boot建議採用Java註解配置的方式提供一個全局惟一的配置類。可是,你能夠同時使用多種不一樣的配置源。若是是Java註解的配置方式,會使用AnnotatedBeanDefinitionReader
加載配置(經過全類名)。若是是XML的配置方式,則會使用XmlBeanDefinitionReader
加載配置(經過XML文件地址)。
若是除了primarySources配置類之外,還須要其它的ApplicationContext配置源,則能夠調用SpringApplication#setSources(Set<String> sources)
方法進行設置,該方法的參數既能夠接受一個配置類的全類名,也能夠是一個XML配置文件的地址。less
SpringApplication默認的應用類型只有三種:函數
public enum WebApplicationType { /** * 非web類應用,無需內嵌web server */ NONE, /** * servlet類型的web應用,須要啓動內嵌的web server */ SERVLET, /** * reactive類型的web應用,須要啓動內嵌的reactive web server * 啥是reactive類型的web應用?目前還不知道^_^ */ REACTIVE;
判斷的邏輯也很是簡單,就是檢查classpath下是否存在對應的類。spring-boot
應用類型直接決定了要建立的ApplicationContext類型,下表整理了三種應用類型和所建立的ApplicationContext間的對應關係:ui
應用類型 | ApplicationContext類型 |
---|---|
NONE | AnnotationConfigApplicationContext |
SERVLET | AnnotationConfigServletWebServerApplicationContext |
REACTIVE | AnnotationConfigReactiveWebServerApplicationContext |
初始化ApplicationContextInitializer和ApplicationListener的過程比較類似,都是藉助於SpringFactoriesLoader的方式完成初始化的,因此放到一塊兒講述。
SpringFactoriesLoader會讀取META-INF/spring.factories文件中的配置。一個工程項目中能夠同時有多個META-INF/spring.factories文件(每一個jar中都能有一個)。
例如在spring-boot-autoconfigure和spring-boot兩個jar的META-INF/spring.factories文件中,均有針對ApplicationContextInitializer的配置:
# spring-boot-autoconfigure jar中的META-INF/spring.factories # Initializers org.springframework.context.ApplicationContextInitializer=\ org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\ org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener # spring-boot jar中的META-INF/spring.factories # 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
SpringApplication的initializers就由這兩部分配置的類共同組成,一共有6個ApplicationContextInitializer的實現類。
同理下面是關於listeners的配置,一共有10個ApplicationListener實現類。
# spring-boot-autoconfigure jar中的META-INF/spring.factories # Application Listeners org.springframework.context.ApplicationListener=\ org.springframework.boot.autoconfigure.BackgroundPreinitializer # spring-boot jar中的META-INF/spring.factories # 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
此處介紹一個IDEA使用的小技巧,咱們能夠藉助IDEA的find usage功能啓動找到哪裏對ApplicationListener進行了配置,以下圖所示:
推斷主類過程的實現方式很巧妙,經過遍歷異常堆棧找到方法名稱是main的類,將其做爲主類
private Class<?> deduceMainApplicationClass() { try { StackTraceElement[] stackTrace = new RuntimeException().getStackTrace(); for (StackTraceElement stackTraceElement : stackTrace) { if ("main".equals(stackTraceElement.getMethodName())) { return Class.forName(stackTraceElement.getClassName()); } } } catch (ClassNotFoundException ex) { // Swallow and continue } return null; }
在完成SpringApplication對象的構建和初始化以後,就開始執行run方法的邏輯了。具體代碼以下,下面將介紹其執行流程。
public ConfigurableApplicationContext run(String... args) { StopWatch stopWatch = new StopWatch(); stopWatch.start(); ConfigurableApplicationContext context = null; Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>(); configureHeadlessProperty(); SpringApplicationRunListeners listeners = getRunListeners(args); listeners.starting(); try { ApplicationArguments applicationArguments = new DefaultApplicationArguments( args); ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments); configureIgnoreBeanInfo(environment); Banner printedBanner = printBanner(environment); context = createApplicationContext(); exceptionReporters = getSpringFactoriesInstances( SpringBootExceptionReporter.class, new Class[] { ConfigurableApplicationContext.class }, context); prepareContext(context, environment, listeners, applicationArguments, printedBanner); refreshContext(context); afterRefresh(context, applicationArguments); stopWatch.stop(); if (this.logStartupInfo) { new StartupInfoLogger(this.mainApplicationClass) .logStarted(getApplicationLog(), stopWatch); } listeners.started(context); callRunners(context, applicationArguments); } catch (Throwable ex) { handleRunFailure(context, ex, exceptionReporters, listeners); throw new IllegalStateException(ex); } try { listeners.running(context); } catch (Throwable ex) { handleRunFailure(context, ex, exceptionReporters, null); throw new IllegalStateException(ex); } return context; }
/** - Load beans into the application context. - @param context the context to load beans into - @param sources the sources to load */ protected void load(ApplicationContext context, Object[] sources) { if (logger.isDebugEnabled()) { logger.debug( "Loading source " + StringUtils.arrayToCommaDelimitedString(sources)); } BeanDefinitionLoader loader = createBeanDefinitionLoader( getBeanDefinitionRegistry(context), sources); if (this.beanNameGenerator != null) { loader.setBeanNameGenerator(this.beanNameGenerator); } if (this.resourceLoader != null) { loader.setResourceLoader(this.resourceLoader); } if (this.environment != null) { loader.setEnvironment(this.environment); } loader.load(); }
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); } } }
至此,整個run方法執行結束,Spring應用也完成了整個啓動流程。
SpringApplication類將一個SpringBoot應用的啓動流程模板化,並在啓動過程當中提供了一些擴展點讓咱們能夠根據具體需求進行擴展(經過SpringApplicationRunListener機制)。SpringBoot提供的就是這樣一個標準化的平臺,在這個平臺上既能夠運行普通java應用,也能夠運行web應用。SpringBoot平臺會根據classpath自動判斷應用類型,建立對應類型的ApplicationContext和加載恰當的配置。固然,除了平臺的能力外,SpringBoot還提供了不少XXXAutoConfiguration支持自動裝配。我想,正是平臺的能力和自動裝配的能力,才讓SpringBoot變得強大吧。