在SpringBoot中,SpringApplication封裝了一套Spring應用的啟動流程,對用戶完全是透明的,這個類原本在Spring中是沒有的。
一般來說,默認的SpringApplication執行流程可以滿足大部分需求,若是想要干預這過程,可以通過SpringApplication在流程的某些地方開啟擴展點來對流程進行擴展,典型的擴展方案就是setXXX方法.
1 @SpringBootApplication 2 public class CodeSheepApplication { 3 public static void main( String[] args ) { 4 // SpringApplication.run( DdsApplication.class, args ); 5 SpringApplication app = new SpringApplication( DdsApplication.class ); 6 app.setXXX( ... ); // 用戶自定的擴展在此 !!! 7 app.run( args ); 8 } 9 }
SpringBoot應用中,首先要了解的就是SpringApplication這個類了。
SpringApplication的實例化,在上面這段代碼中,使用了自定義SpringApplication,通過一行代碼啟動SpringBoot應用,也可以自定義SpringApplication的一些擴展。
1 @SuppressWarnings({ "unchecked", "rawtypes" }) 2 public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) { 3 this.resourceLoader = resourceLoader; 4 Assert.notNull(primarySources, "PrimarySources must not be null"); 5 this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources)); 6 this.webApplicationType = WebApplicationType.deduceFromClasspath(); 7 setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class)); 8 setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class)); 9 this.mainApplicationClass = deduceMainApplicationClass(); 10 }
1.WebApplicationType是一個枚舉Web Application的類型,其類型定義有三種:NONE(不應作為Web應用程序運行,也不應啟動嵌入式Web服務器)、SERVLET(基於servlet的Web應用程序)、REACTIVE(響應式Web應用程序)。
NONE:org.springframework.context.annotation.AnnotationConfigApplicationContext
SERVLET:org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext
REACTIVE:org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext
WebApplicationType#deduceFromClasspath()的意思是根據Classpath的內容推斷WebApplication類型。
1 static WebApplicationType deduceFromClasspath() { 2 if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null) && !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null) 3 && !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) { 4 return WebApplicationType.REACTIVE; 5 } 6 for (String className : SERVLET_INDICATOR_CLASSES) { 7 if (!ClassUtils.isPresent(className, null)) { 8 return WebApplicationType.NONE; 9 } 10 } 11 return WebApplicationType.SERVLET; 12 }
ClassUtils是Spring框架所提供關於類級別的工具類,主要供框架內部使用。ClassUtils#isPresent是判斷當前class loader中是否存在對應的類型。代碼里面關於判斷中的方法,為什么所提供的classLoader參數都為空,再進去方法里面看看,發現它是調用了ClassUtils#forName
1 public static boolean isPresent(String className, @Nullable ClassLoader classLoader) { 2 try { 3 forName(className, classLoader); 4 return true; 5 } 6 catch (IllegalAccessError err) { 7 throw new IllegalStateException("Readability mismatch in inheritance hierarchy of class [" + 8 className + "]: " + err.getMessage(), err); 9 } 10 catch (Throwable ex) { 11 // Typically ClassNotFoundException or NoClassDefFoundError... 12 return false; 13 } 14 }
再看看ClassUtils#forName,方法的源碼很長,它替換Class.forName(),還返回原始類型(例如“int”)和數組類名稱(例如“String []”)的Class實例。 此外,它還能夠以Java源代碼樣式解析內部類名(例如“java.lang.Thread.State”而不是“java.lang.Thread $ State”)。
里面調用了ClassUtils#resolvePrimitiveClassName來把給定的類作為基本類(如果適用的話);如果不是基本類型則在緩存找,如果是基本類型則返回;然后接着判斷給出的類名是不是一維或多維的整型或字符串數組;接着判斷方法傳入的classLoader,為空則ClassUtils#getDefaultClassLoader來從當前System中獲取默認的classLoader,再使用給定的類加載器返回與具有給定字符串名稱的類或接口關聯的Class對象→Class.forName(類名, false, 當前默認的classLoader),當找不到Class就會報異常。
1 public static Class<?> forName(String name, @Nullable ClassLoader classLoader) 2 throws ClassNotFoundException, LinkageError { 3 4 Assert.notNull(name, "Name must not be null"); 5 6 Class<?> clazz = resolvePrimitiveClassName(name); 7 if (clazz == null) { 8 clazz = commonClassCache.get(name); 9 } 10 if (clazz != null) { 11 return clazz; 12 } 13 14 // "java.lang.String[]" style arrays 15 if (name.endsWith(ARRAY_SUFFIX)) { 16 String elementClassName = name.substring(0, name.length() - ARRAY_SUFFIX.length()); 17 Class<?> elementClass = forName(elementClassName, classLoader); 18 return Array.newInstance(elementClass, 0).getClass(); 19 } 20 21 // "[Ljava.lang.String;" style arrays 22 if (name.startsWith(NON_PRIMITIVE_ARRAY_PREFIX) && name.endsWith(";")) { 23 String elementName = name.substring(NON_PRIMITIVE_ARRAY_PREFIX.length(), name.length() - 1); 24 Class<?> elementClass = forName(elementName, classLoader); 25 return Array.newInstance(elementClass, 0).getClass(); 26 } 27 28 // "[[I" or "[[Ljava.lang.String;" style arrays 29 if (name.startsWith(INTERNAL_ARRAY_PREFIX)) { 30 String elementName = name.substring(INTERNAL_ARRAY_PREFIX.length()); 31 Class<?> elementClass = forName(elementName, classLoader); 32 return Array.newInstance(elementClass, 0).getClass(); 33 } 34 35 ClassLoader clToUse = classLoader; 36 if (clToUse == null) { 37 clToUse = getDefaultClassLoader(); 38 } 39 try { 40 return Class.forName(name, false, clToUse); 41 } 42 catch (ClassNotFoundException ex) { 43 int lastDotIndex = name.lastIndexOf(PACKAGE_SEPARATOR); 44 if (lastDotIndex != -1) { 45 String innerClassName = 46 name.substring(0, lastDotIndex) + INNER_CLASS_SEPARATOR + name.substring(lastDotIndex + 1); 47 try { 48 return Class.forName(innerClassName, false, clToUse); 49 } 50 catch (ClassNotFoundException ex2) { 51 // Swallow - let original exception get through 52 } 53 } 54 throw ex; 55 } 56 }
總結WebApplicationType#deduceFromClasspath(),它通過ClassPath來推斷WebApplication的類型,從當前系統默認的ClassLoader獲取WebApplication類型相對應類的映射,從而判斷WebApplication的類型。
1 setInitializers((Collection) 2 getSpringFactoriesInstances(ApplicationContextInitializer.class));
2.setInitializers(...)
使用 SpringFactoriesLoader 查找並加載 classpath下 META-INF/spring.factories
文件中所有可用的 ApplicationContextInitializer ---- 用於在ConfigurableApplicationContext#refresh() 之前初始化Spring ConfigurableApplicationContext的回調接口。
1 # PropertySource Loaders 2 org.springframework.boot.env.PropertySourceLoader=\ 3 org.springframework.boot.env.PropertiesPropertySourceLoader,\ 4 org.springframework.boot.env.YamlPropertySourceLoader 5 6 # Run Listeners 7 org.springframework.boot.SpringApplicationRunListener=\ 8 org.springframework.boot.context.event.EventPublishingRunListener 9 10 # Error Reporters 11 org.springframework.boot.SpringBootExceptionReporter=\ 12 org.springframework.boot.diagnostics.FailureAnalyzers 13 14 # Application Context Initializers 15 org.springframework.context.ApplicationContextInitializer=\ 16 org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer,\ 17 org.springframework.boot.context.ContextIdApplicationContextInitializer,\ 18 org.springframework.boot.context.config.DelegatingApplicationContextInitializer,\ 19 org.springframework.boot.web.context.ServerPortInfoApplicationContextInitializer 20 21 # Application Listeners 22 org.springframework.context.ApplicationListener=\ 23 org.springframework.boot.ClearCachesApplicationListener,\ 24 org.springframework.boot.builder.ParentContextCloserApplicationListener,\ 25 org.springframework.boot.context.FileEncodingApplicationListener,\ 26 org.springframework.boot.context.config.AnsiOutputApplicationListener,\ 27 org.springframework.boot.context.config.ConfigFileApplicationListener,\ 28 org.springframework.boot.context.config.DelegatingApplicationListener,\ 29 org.springframework.boot.context.logging.ClasspathLoggingApplicationListener,\ 30 org.springframework.boot.context.logging.LoggingApplicationListener,\ 31 org.springframework.boot.liquibase.LiquibaseServiceLocatorApplicationListener 32 33 # Environment Post Processors 34 org.springframework.boot.env.EnvironmentPostProcessor=\ 35 org.springframework.boot.cloud.CloudFoundryVcapEnvironmentPostProcessor,\ 36 org.springframework.boot.env.SpringApplicationJsonEnvironmentPostProcessor,\ 37 org.springframework.boot.env.SystemEnvironmentPropertySourceEnvironmentPostProcessor 38 39 # Failure Analyzers 40 org.springframework.boot.diagnostics.FailureAnalyzer=\ 41 org.springframework.boot.diagnostics.analyzer.BeanCurrentlyInCreationFailureAnalyzer,\ 42 org.springframework.boot.diagnostics.analyzer.BeanDefinitionOverrideFailureAnalyzer,\ 43 org.springframework.boot.diagnostics.analyzer.BeanNotOfRequiredTypeFailureAnalyzer,\ 44 org.springframework.boot.diagnostics.analyzer.BindFailureAnalyzer,\ 45 org.springframework.boot.diagnostics.analyzer.BindValidationFailureAnalyzer,\ 46 org.springframework.boot.diagnostics.analyzer.UnboundConfigurationPropertyFailureAnalyzer,\ 47 org.springframework.boot.diagnostics.analyzer.ConnectorStartFailureAnalyzer,\ 48 org.springframework.boot.diagnostics.analyzer.NoSuchMethodFailureAnalyzer,\ 49 org.springframework.boot.diagnostics.analyzer.NoUniqueBeanDefinitionFailureAnalyzer,\ 50 org.springframework.boot.diagnostics.analyzer.PortInUseFailureAnalyzer,\ 51 org.springframework.boot.diagnostics.analyzer.ValidationExceptionFailureAnalyzer,\ 52 org.springframework.boot.diagnostics.analyzer.InvalidConfigurationPropertyNameFailureAnalyzer,\ 53 org.springframework.boot.diagnostics.analyzer.InvalidConfigurationPropertyValueFailureAnalyzer 54 55 # FailureAnalysisReporters 56 org.springframework.boot.diagnostics.FailureAnalysisReporter=\ 57 org.springframework.boot.diagnostics.LoggingFailureAnalysisReporter
3.setListeners(...)
使用SpringFactoriesLoader查找並加載 classpath下 META-INF/spring.factories
文件中的所有可用的 ApplicationListener ---- 應用程序事件偵聽器實現的接口, 基於Observer設計模式的標准java.util.EventListener 接口。
4.deduceMainApplicationClass()
推斷並設置main方法的定義類,通過Throwable#getStackTrace()方法返回堆棧中的元素,找出方法名為main的堆棧元素,再根據類名返回對應 的反射類
1 private Class<?> deduceMainApplicationClass() { 2 try { 3 StackTraceElement[] stackTrace = new RuntimeException().getStackTrace(); 4 for (StackTraceElement stackTraceElement : stackTrace) { 5 if ("main".equals(stackTraceElement.getMethodName())) { 6 return Class.forName(stackTraceElement.getClassName()); 7 } 8 } 9 } 10 catch (ClassNotFoundException ex) { 11 // Swallow and continue 12 } 13 return null; 14 }
再來看看SpringBoot應用運行方法 SpringApplication#run
1 public ConfigurableApplicationContext run(String... args) { 2 StopWatch stopWatch = new StopWatch(); 3 stopWatch.start(); 4 ConfigurableApplicationContext context = null; 5 Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>(); 6 configureHeadlessProperty(); 7 SpringApplicationRunListeners listeners = getRunListeners(args); 8 listeners.starting(); 9 try { 10 ApplicationArguments applicationArguments = new DefaultApplicationArguments(args); 11 ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments); 12 configureIgnoreBeanInfo(environment); 13 Banner printedBanner = printBanner(environment); 14 context = createApplicationContext(); 15 exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class, 16 new Class[] { ConfigurableApplicationContext.class }, context); 17 prepareContext(context, environment, listeners, applicationArguments, printedBanner); 18 refreshContext(context); 19 afterRefresh(context, applicationArguments); 20 stopWatch.stop(); 21 if (this.logStartupInfo) { 22 new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch); 23 } 24 listeners.started(context); 25 callRunners(context, applicationArguments); 26 } 27 catch (Throwable ex) { 28 handleRunFailure(context, ex, exceptionReporters, listeners); 29 throw new IllegalStateException(ex); 30 } 31 32 try { 33 listeners.running(context); 34 } 35 catch (Throwable ex) { 36 handleRunFailure(context, ex, exceptionReporters, null); 37 throw new IllegalStateException(ex); 38 } 39 return context; 40 }
三、總結
獲取SpringApplicationListener → 通知Listeners start → 創建參數,配置Environment → 根據WebApplicationType創建ApplicationContext → 初始化ApplicationContext,設置Environment加載相關配置等 → 通知EnvironmentPrepared,contextLoaded → refresh ApplicationContext → 通知Listeners start context → 完成啟動→ 通知runner → 結束
-
通過
SpringFactoriesLoader
加載META-INF/spring.factories
文件,獲取並創建SpringApplicationRunListener
對象 -
然后由
SpringApplicationRunListener
來發出 starting 消息 -
創建參數,並配置當前 SpringBoot 應用將要使用的
Environment
-
完成之后,依然由
SpringApplicationRunListener
來發出environmentPrepared
消息 -
根據
WebApplicationType
創建ApplicationContext
-
初始化
ApplicationContext
,並設置Environment
,加載相關配置等 -
由
SpringApplicationRunListener
來發出contextPrepared
消息,告知SpringBoot 應用使用的ApplicationContext
已准備OK -
將各種 beans 裝載入
ApplicationContext
,繼續由SpringApplicationRunListener
來發出 contextLoaded 消息,告知 SpringBoot 應用使用的ApplicationContext
已裝填OK -
refresh ApplicationContext,完成IoC容器可用的最后一步
-
由
SpringApplicationRunListener
來發出 started 消息 -
完成最終的程序的啟動
-
由
SpringApplicationRunListener
來發出 running 消息,告知程序已運行起來了