SpringBoot:Spring容器的啟動過程


一、簡述

Spring的啟動過程就是IoC容器的啟動過程,本質上就是創建和初始化Bean的工廠(BeanFactory),BeanFactory是整個SpringIoC的核心,Spring使用BeanFactory來實例化、配置和管理Bean。

二、SpringBoot的啟動過程

在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 → 結束

  1. 通過 SpringFactoriesLoader 加載 META-INF/spring.factories 文件,獲取並創建 SpringApplicationRunListener 對象

  2. 然后由 SpringApplicationRunListener 來發出 starting 消息

  3. 創建參數,並配置當前 SpringBoot 應用將要使用的 Environment

  4. 完成之后,依然由 SpringApplicationRunListener 來發出 environmentPrepared 消息

  5. 根據 WebApplicationType 創建 ApplicationContext

  6. 初始化 ApplicationContext,並設置 Environment,加載相關配置等

  7. SpringApplicationRunListener 來發出 contextPrepared 消息,告知SpringBoot 應用使用的 ApplicationContext 已准備OK

  8. 將各種 beans 裝載入 ApplicationContext,繼續由 SpringApplicationRunListener 來發出 contextLoaded 消息,告知 SpringBoot 應用使用的 ApplicationContext 已裝填OK

  9. refresh ApplicationContext,完成IoC容器可用的最后一步

  10. SpringApplicationRunListener 來發出 started 消息

  11. 完成最終的程序的啟動

  12. SpringApplicationRunListener 來發出 running 消息,告知程序已運行起來了


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM