SpringBoot應用--01-啟動流程解析
阿新 • • 發佈:2021-07-02
1. 一個簡單SpringBoot專案主啟動類配置
@SpringBootApplication public class SpringBoot05MongodbApplication { public static void main(String[] args) { SpringApplication.run(SpringBoot05MongodbApplication.class, args); } }
2. SpringApplication#run方法總體流程
public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {return run(new Class<?>[] { primarySource }, args); } public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) { return new SpringApplication(primarySources).run(args); }
2.1 SpringApplication構造方法
public SpringApplication(Class<?>... primarySources) {this(null, primarySources); }
@SuppressWarnings({ "unchecked", "rawtypes" }) 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();
// 從spring.factories檔案中獲取應用初始化器 通過反射獲取並注入當前Boot應用中 setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
// 從spring.factories檔案中獲取應用監聽器 通過反射獲取並注入當前Boot應用中 setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
// 推導主啟動類 this.mainApplicationClass = deduceMainApplicationClass(); }
SpringApplication#getSpringFactoriesInstances方法追蹤:
/** 獲取定義在spring.factories檔案中的元件 */ private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) { return getSpringFactoriesInstances(type, new Class<?>[] {}); } private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) { ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); // Use names and ensure unique to protect against duplicates Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader)); List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names); AnnotationAwareOrderComparator.sort(instances); return instances; } /* 使用反射機制生成spring.factories檔案中的元件 */ @SuppressWarnings("unchecked") private <T> List<T> createSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, ClassLoader classLoader, Object[] args, Set<String> names) { List<T> instances = new ArrayList<>(names.size()); for (String name : names) { try { Class<?> instanceClass = ClassUtils.forName(name, classLoader); Assert.isAssignable(type, instanceClass); Constructor<?> constructor = instanceClass.getDeclaredConstructor(parameterTypes); T instance = (T) BeanUtils.instantiateClass(constructor, args); instances.add(instance); } catch (Throwable ex) { throw new IllegalArgumentException("Cannot instantiate " + type + " : " + name, ex); } } return instances; }
SpringFactoriesLoader.loadFactoryNames方法詳情:解析spring.factories檔案中資料讀入Properties中
SpringApplication#deduceMainApplicationClass方法追蹤:獲取當前執行java方法棧中名稱為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; }
PS: spring.factories檔案一般由spring-boot-autoconfigure 子模組中的WEB-INF目錄下,是一種SPI機制的實現方式,spring-boot-autoconfigure是SpringBoot實現自動裝配的基礎,同時也是spring-boot-starter的依賴,間接屬於各個starter的簡介依賴
2.2 SpringApplication#run方法總覽
/** * Run the Spring application, creating and refreshing a new {@link ApplicationContext}. * -- 啟動Spring應用,建立並且重新整理ApplicationContext * @param args the application arguments (usually passed from a Java main method) * @return a running {@link ApplicationContext} */ public ConfigurableApplicationContext run(String... args) { /** 1. 建立一個 StopWatch,允許計時的一些任務, 公開每個命名任務的總執行時間和執行時間啟動任務計時器 */ StopWatch stopWatch = new StopWatch(); stopWatch.start(); ConfigurableApplicationContext context = null; // 存放異常報告類 Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>(); /** * 2. 配置java.awt.headless為true -- 模式切換 * 怎麼從正常模式切換到headline模式呢, 其實很簡單,java提供了幾種方式: * 1、在main函式入口通過System.setProperties("java.awt.headless", true); * 2、通過java的啟動引數指定 JAVA_OPTS="-Djava.awt.headless=true" * 3、在環境變數中定義改屬性,並且賦值位true。 export java.awt.headless=true * 使用以上3種方法都可以讓jvm在啟動的時候,把工作模式修改位headline,這樣就不會在建立使用awt資源的 * 時候去載入window互動系統提供的具體實現了 */ configureHeadlessProperty(); /** * 3. 從當前classpath下獲取所有的META-INF/spring.factories檔案獲取所有的SpringApplicationRunListener——Spring執行監聽器 * # Run Listeners * org.springframework.boot.SpringApplicationRunListener=\ * org.springframework.boot.context.event.EventPublishingRunListener * 同時將執行監聽器啟動(預設配置使用EventPublishingRunListener),該方法釋出一個ApplicationStartingEvent事件 */ SpringApplicationRunListeners listeners = getRunListeners(args); listeners.starting(); try { /*4. 建立一個預設的為SpringBoot應用啟動提供獲取引數的功能元件*/ ApplicationArguments applicationArguments = new DefaultApplicationArguments(args); /** * 5. 根據當前SpringBoot的web環境狀態建立一個符合的Environment * Servlet 的Web環境下使用StandardServletEnvironment * REACTIVE 的Web環境下使用StandardReactiveWebEnvironment * 非Web環境下使用StandardEnvironment * 同時使用applicationArguments獲取資源資訊 */ ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments); // 6. 根據執行時環境資料spring.beaninfo.ignore值 配置是否需要忽略某些Bean configureIgnoreBeanInfo(environment); // 7. 列印Banner展示 Banner printedBanner = printBanner(environment); /** * 8. 建立Spring ApplicationContext上下文 * SERVLET使用 org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext * REACTIVE使用 org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext * 預設使用 org.springframework.context.annotation.AnnotationConfigApplicationContext * 使用反射機制生成類對應的物件 */ context = createApplicationContext(); /** * 9. 從當前classpath下獲取所有的META-INF/spring.factories檔案 * 從檔案獲取所有的SpringBootExceptionReporter Spring執行異常報告器 *# Error Reporters * org.springframework.boot.SpringBootExceptionReporter=\ * org.springframework.boot.diagnostics.FailureAnalyzers */ exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class, new Class[] { ConfigurableApplicationContext.class }, context); /* 10. 預準備Spring ApplicationContext上下文 */ prepareContext(context, environment, listeners, applicationArguments, printedBanner); /* 11. 核心方法 ApplicationContext#refresh方法相關*/ refreshContext(context); /* 12. IOC容器refresh之後的操作,目前該方法是空實現 */ afterRefresh(context, applicationArguments); // 13. 停止監控器 stopWatch.stop(); if (this.logStartupInfo) { new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch); } /* 14. 釋出IOC容器已經成功啟動的事件 */ listeners.started(context); /*15. 指定自定義的CommandLineRunner ApplicationRunner*/ callRunners(context, applicationArguments); } catch (Throwable ex) { /*16. 處理異常資訊 */ 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; }
2.3 SpringApplication#run方法拆解
1. 建立一個 StopWatch,允許計時的一些任務, 公開每個命名任務的總執行時間和執行時間啟動任務計時器
public StopWatch() { this(""); } public StopWatch(String id) { this.keepTaskList = true; this.taskList = new LinkedList(); this.id = id; } public void start() throws IllegalStateException { this.start(""); } public void start(String taskName) throws IllegalStateException { if (this.currentTaskName != null) { throw new IllegalStateException("Can't start StopWatch: it's already running"); } else { this.currentTaskName = taskName; this.startTimeMillis = System.currentTimeMillis(); } }
2. 配置java.awt.headless為true -- 模式切換
/* 配置java.awt.headless,預設為true */ private void configureHeadlessProperty() { System.setProperty(SYSTEM_PROPERTY_JAVA_AWT_HEADLESS,System.getProperty(SYSTEM_PROPERTY_JAVA_AWT_HEADLESS, Boolean.toString(this.headless))); }
3. 從當前classpath下獲取所有的META-INF/spring.factories檔案獲取所有的SpringApplicationRunListener——Spring執行監聽器
/* 獲取啟動監聽器 */ private SpringApplicationRunListeners getRunListeners(String[] args) { Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class }; return new SpringApplicationRunListeners(logger,getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args)); }
4. 建立一個預設的為SpringBoot應用啟動提供獲取引數的功能元件
public DefaultApplicationArguments(String[] args) { Assert.notNull(args, "Args must not be null"); this.source = new Source(args); this.args = args; }
5. 根據當前SpringBoot的web環境狀態建立一個符合的Environment
/* 預準備SpringBoot執行環境 */ private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments) { // Create and configure the environment ConfigurableEnvironment environment = getOrCreateEnvironment(); // 配置執行時環境 configureEnvironment(environment, applicationArguments.getSourceArgs()); // 執行監聽器中注入執行環境 listeners.environmentPrepared(environment); // 將執行環境繫結在SpringBoot上下文中 bindToSpringApplication(environment); if (!this.isCustomEnvironment) { environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment, deduceEnvironmentClass()); } // 從執行環境中獲取配置資源附件 ConfigurationPropertySources.attach(environment); return environment; }
getOrCreateEnvironment方法追蹤
private ConfigurableEnvironment getOrCreateEnvironment() { if (this.environment != null) { return this.environment; } switch (this.webApplicationType) { case SERVLET: // Servlet 的Web環境下使用StandardServletEnvironment return new StandardServletEnvironment(); case REACTIVE: // REACTIVE 的Web環境下使用StandardReactiveWebEnvironment return new StandardReactiveWebEnvironment(); default: // 非Web環境下使用StandardEnvironment return new StandardEnvironment(); } }
configureEnvironment方法追蹤
protected void configureEnvironment(ConfigurableEnvironment environment, String[] args) { configurePropertySources(environment, args); configureProfiles(environment, args); } /** * Add, remove or re-order any {@link PropertySource}s in this application's * environment. * -- 在此應用程式中新增、刪除或重新排序任何{@link PropertySource} 環境 * @param environment this application's environment * @param args arguments passed to the {@code run} method * @see #configureEnvironment(ConfigurableEnvironment, String[]) */ protected void configurePropertySources(ConfigurableEnvironment environment, String[] args) { // 執行時環境中宣告的屬性資源資訊 MutablePropertySources sources = environment.getPropertySources(); if (this.defaultProperties != null && !this.defaultProperties.isEmpty()) { sources.addLast(new MapPropertySource("defaultProperties", this.defaultProperties)); } if (this.addCommandLineProperties && args.length > 0) { String name = CommandLinePropertySource.COMMAND_LINE_PROPERTY_SOURCE_NAME; if (sources.contains(name)) { PropertySource<?> source = sources.get(name); CompositePropertySource composite = new CompositePropertySource(name); composite.addPropertySource( new SimpleCommandLinePropertySource("springApplicationCommandLineArgs", args)); composite.addPropertySource(source); sources.replace(name, composite); } else { // 建立一個從命令列獲取配置屬性的處理器並新增在第一位 sources.addFirst(new SimpleCommandLinePropertySource(args)); } } } /** * Configure which profiles are active (or active by default) for this application * environment. Additional profiles may be activated during configuration file * processing via the {@code spring.profiles.active} property. * @param environment this application's environment * @param args arguments passed to the {@code run} method * @see #configureEnvironment(ConfigurableEnvironment, String[]) * @see org.springframework.boot.context.config.ConfigFileApplicationListener */ protected void configureProfiles(ConfigurableEnvironment environment, String[] args) { environment.getActiveProfiles(); // ensure they are initialized 獲取當前執行獲取所有處於活躍狀態的Profile // But these ones should go first (last wins in a property key clash) 進行去重操作 Set<String> profiles = new LinkedHashSet<>(this.additionalProfiles); profiles.addAll(Arrays.asList(environment.getActiveProfiles())); environment.setActiveProfiles(StringUtils.toStringArray(profiles)); }
bindToSpringApplication方法追蹤
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); } }
ConfigurationPropertySources.attach方法追蹤
public static void attach(Environment environment) { Assert.isInstanceOf(ConfigurableEnvironment.class, environment); MutablePropertySources sources = ((ConfigurableEnvironment) environment).getPropertySources(); PropertySource<?> attached = sources.get(ATTACHED_PROPERTY_SOURCE_NAME); if (attached != null && attached.getSource() != sources) { sources.remove(ATTACHED_PROPERTY_SOURCE_NAME); attached = null; } if (attached == null) { sources.addFirst(new ConfigurationPropertySourcesPropertySource(ATTACHED_PROPERTY_SOURCE_NAME, new SpringConfigurationPropertySources(sources))); } }
6. 根據執行時環境資料spring.beaninfo.ignore值 配置是否需要忽略某些Bean
private void configureIgnoreBeanInfo(ConfigurableEnvironment environment) { if (System.getProperty(CachedIntrospectionResults.IGNORE_BEANINFO_PROPERTY_NAME) == null) { Boolean ignore = environment.getProperty("spring.beaninfo.ignore", Boolean.class, Boolean.TRUE); System.setProperty(CachedIntrospectionResults.IGNORE_BEANINFO_PROPERTY_NAME, ignore.toString()); } }
7. 列印Banner展示
private Banner printBanner(ConfigurableEnvironment environment) { // 如果Banner模式使用關閉則不需要列印 if (this.bannerMode == Banner.Mode.OFF) { return null; } ResourceLoader resourceLoader = (this.resourceLoader != null) ? this.resourceLoader : new DefaultResourceLoader(getClassLoader()); SpringApplicationBannerPrinter bannerPrinter = new SpringApplicationBannerPrinter(resourceLoader, this.banner); // 使用日誌模式則輸出在日誌中 if (this.bannerMode == Mode.LOG) { return bannerPrinter.print(environment, this.mainApplicationClass, logger); } // 預設列印輸出在console控制檯中 return bannerPrinter.print(environment, this.mainApplicationClass, System.out); }
8. 建立Spring ApplicationContext上下文
protected ConfigurableApplicationContext createApplicationContext() { Class<?> contextClass = this.applicationContextClass; if (contextClass == null) { try { switch (this.webApplicationType) { case SERVLET: // 預設使用 org.springframework.context.annotation.AnnotationConfigApplicationContext // SERVLET使用 org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext // REACTIVE使用 org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext 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); }
9. 從當前classpath下獲取所有的META-INF/spring.factories檔案,從檔案獲取所有的SpringBootExceptionReporter Spring執行異常報告器
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) { ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); // Use names and ensure unique to protect against duplicates Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader)); List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names); AnnotationAwareOrderComparator.sort(instances); return instances; }
10. 預準備Spring ApplicationContext上下文
private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment, SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) { /** * 為當前ApplicationContext注入 * environment ---- 執行時環境 * beanNameGenerator ---- Bean名稱生成器 * resourceLoader ---- 資源載入器 * classLoader ---- 類載入器 */ context.setEnvironment(environment); postProcessApplicationContext(context); /** * 構造器方法從當前classpath下獲取所有的META-INF/spring.factories檔案,獲取ApplicationContextInitializer對應資料解析成 this.initializers * # 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 * 此處執行該this.initializers中的所有初始化方法 * 執行時監控器釋出上下文已準備事件 */ applyInitializers(context); listeners.contextPrepared(context); // 如果記錄啟動日誌的話記錄啟動+當前活躍Profile資訊 if (this.logStartupInfo) { logStartupInfo(context.getParent() == null); logStartupProfileInfo(context); } // Add boot specific singleton beans 新增引導特定的單例bean context.getBeanFactory().registerSingleton("springApplicationArguments", applicationArguments); if (printedBanner != null) { context.getBeanFactory().registerSingleton("springBootBanner", printedBanner); } /** * Load the sources 獲取所有資源配置 * 將所有Bean定義載入到ApplicationContext上下文中 * 執行時監控器釋出上下文已載入事件 */ Set<Object> sources = getAllSources(); Assert.notEmpty(sources, "Sources must not be empty"); load(context, sources.toArray(new Object[0])); listeners.contextLoaded(context); }
11. 核心方法 ApplicationContext#refresh方法相關
private void refreshContext(ConfigurableApplicationContext context) { refresh(context); if (this.registerShutdownHook) { try { context.registerShutdownHook(); } catch (AccessControlException ex) { // Not allowed in some environments. } } }
protected void refresh(ApplicationContext applicationContext) { Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext); ((AbstractApplicationContext) applicationContext).refresh(); }
AbstractApplicationContext.refresh()單獨解析流程
12. IOC容器refresh之後的操作,目前該方法是空實現
protected void afterRefresh(ConfigurableApplicationContext context, ApplicationArguments args) { }
13. 停止監控器
public void stop() throws IllegalStateException { if (this.currentTaskName == null) { throw new IllegalStateException("Can't stop StopWatch: it's not running"); } else { long lastTime = System.currentTimeMillis() - this.startTimeMillis; this.totalTimeMillis += lastTime; this.lastTaskInfo = new StopWatch.TaskInfo(this.currentTaskName, lastTime); if (this.keepTaskList) { this.taskList.add(this.lastTaskInfo); } ++this.taskCount; this.currentTaskName = null; } }
14. 釋出IOC容器成功啟動的事件
15.指定自定義的CommandLineRunner ApplicationRunner
/* 呼叫所有自定義的ApplicationRunner+CommandLineRunner 執行對應的業務方法*/ 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); } } } private void callRunner(ApplicationRunner runner, ApplicationArguments args) { try { (runner).run(args); } catch (Exception ex) { throw new IllegalStateException("Failed to execute ApplicationRunner", ex); } } private void callRunner(CommandLineRunner runner, ApplicationArguments args) { try { (runner).run(args.getSourceArgs()); } catch (Exception ex) { throw new IllegalStateException("Failed to execute CommandLineRunner", ex); } }
16. 啟動異常處理
private void handleRunFailure(ConfigurableApplicationContext context, Throwable exception, Collection<SpringBootExceptionReporter> exceptionReporters, SpringApplicationRunListeners listeners) { try { try { /* 處理退出碼 */ handleExitCode(context, exception); if (listeners != null) { listeners.failed(context, exception); } } finally { /*報告錯誤 */ reportFailure(exceptionReporters, exception); if (context != null) { context.close(); } } } catch (Exception ex) { logger.warn("Unable to close ApplicationContext", ex); } ReflectionUtils.rethrowRuntimeException(exception); } /* 記錄異常錯誤資訊*/ private void reportFailure(Collection<SpringBootExceptionReporter> exceptionReporters, Throwable failure) { try { for (SpringBootExceptionReporter reporter : exceptionReporters) { if (reporter.reportException(failure)) { registerLoggedException(failure); return; } } } catch (Throwable ex) { // Continue with normal handling of the original failure } if (logger.isErrorEnabled()) { logger.error("Application run failed", failure); registerLoggedException(failure); } } protected void registerLoggedException(Throwable exception) { SpringBootExceptionHandler handler = getSpringBootExceptionHandler(); if (handler != null) { handler.registerLoggedException(exception); } } /* 根據應用退出碼處理 */ private void handleExitCode(ConfigurableApplicationContext context, Throwable exception) { int exitCode = getExitCodeFromException(context, exception); if (exitCode != 0) { if (context != null) { context.publishEvent(new ExitCodeEvent(context, exitCode)); } SpringBootExceptionHandler handler = getSpringBootExceptionHandler(); if (handler != null) { handler.registerExitCode(exitCode); } } }