SpringBoot2 | SpringBoot啓動流程源碼分析(二)

微信公衆號:吉姆餐廳ak 學習更多源碼知識,歡迎關注。 java

在這裏插入圖片描述


SpringBoot2 | SpringBoot啓動流程源碼分析(一)web

SpringBoot2 | SpringBoot啓動流程源碼分析(二)spring

SpringBoot2 | @SpringBootApplication註解 自動化配置流程源碼分析(三)設計模式

SpringBoot2 | SpringBoot Environment源碼分析(四)bash

SpringBoot2 | SpringBoot自定義AutoConfiguration | SpringBoot自定義starter(五)微信

SpringBoot2 | SpringBoot監聽器源碼分析 | 自定義ApplicationListener(六)app

SpringBoot2 | 條件註解@ConditionalOnBean原理源碼深度解析(七)源碼分析

SpringBoot2 | Spring AOP 原理源碼深度剖析(八)post

SpringBoot2 | SpingBoot FilterRegistrationBean 註冊組件 | FilterChain 責任鏈源碼分析(九)性能

SpringBoot2 | BeanDefinition 註冊核心類 ImportBeanDefinitionRegistrar (十)

SpringBoot2 | Spring 核心擴展接口 | 核心擴展方法總結(十一)


繼續上一篇博客的啓動流程分析。


在上一篇SpringBoot | SpringBoot2 | SpringBoot2啓動流程源碼分析(一)中咱們提到springBoot啓動流程大體有如下7點:

  • 第一步:獲取並啓動監聽器
  • 第二步:構造容器環境
  • 第三步:建立容器
  • 第四步:實例化SpringBootExceptionReporter.class,用來支持報告關於啓動的錯誤
  • 第五步:準備容器
  • 第六步:刷新容器
  • 第七步:刷新容器後的擴展接口

上一篇博客中分析了前面兩點,今天繼續分析後面四點。


第三步:建立容器

context = createApplicationContext();
複製代碼

繼續跟進該方法:

protected ConfigurableApplicationContext createApplicationContext() {
		Class<?> contextClass = this.applicationContextClass;
		if (contextClass == null) {
			try {
				switch (this.webApplicationType) {
				case SERVLET:
					contextClass = Class.forName(DEFAULT_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類型,因此會經過反射裝載對應的字節碼,以下:

public static final String DEFAULT_WEB_CONTEXT_CLASS = "org.springframework.boot."
			+ "web.servlet.context.AnnotationConfigServletWebServerApplicationContext";
複製代碼

該對象是springBoot2建立的容器,後續全部的操做都會基於該容器。

注意:在 springBoot2版本之前,該容器的名稱爲 AnnotationConfigServletWebServerApplicationContext,在最新的版本中才更名爲 AnnotationConfigServletWebServerApplicationContext。 下面是該類的結構圖:

這裏寫圖片描述
具體做用後面會詳細介紹。


第四步:報告錯誤信息

exceptionReporters = getSpringFactoriesInstances(
					SpringBootExceptionReporter.class,
					new Class[] { ConfigurableApplicationContext.class }, context);
複製代碼

這裏仍是以一樣的方式獲取 spring.factories文件中的指定類:

exceptionReporters = getSpringFactoriesInstances(
					SpringBootExceptionReporter.class,
					new Class[] { ConfigurableApplicationContext.class }, context);
複製代碼

該類主要是在項目啓動失敗以後,打印log:

private void reportFailure(Collection<SpringBootExceptionReporter> exceptionReporters,
			Throwable failure) {
		try {
			for (SpringBootExceptionReporter reporter : exceptionReporters) {
				if (reporter.reportException(failure)) {
					//上報錯誤log
					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);
		}
	}
複製代碼

第五步:準備容器

這一步主要是在容器刷新以前的準備動做。包含一個很是關鍵的操做:將啓動類注入容器,爲後續開啓自動化配置奠基基礎。

prepareContext(context, environment, listeners, applicationArguments,
					printedBanner);
複製代碼

繼續跟進該方法:

private void prepareContext(ConfigurableApplicationContext context,
			ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
			ApplicationArguments applicationArguments, Banner printedBanner) {
		//設置容器環境,包括各類變量
		context.setEnvironment(environment);
		//執行容器後置處理
		postProcessApplicationContext(context);
		//執行容器中的ApplicationContextInitializer(包括 spring.factories和自定義的實例)
		applyInitializers(context);
		//發送容器已經準備好的事件,通知各監聽器
		listeners.contextPrepared(context);
		//打印log
		if (this.logStartupInfo) {
			logStartupInfo(context.getParent() == null);
			logStartupProfileInfo(context);
		}
		// Add boot specific singleton beans
		//註冊啓動參數bean,這裏將容器指定的參數封裝成bean,注入容器
		context.getBeanFactory().registerSingleton("springApplicationArguments",
				applicationArguments);
		//設置banner
		if (printedBanner != null) {
			context.getBeanFactory().registerSingleton("springBootBanner", printedBanner);
		}
		// 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);
	}
複製代碼

來看一下上面的幾個核心處理。

1)容器的後置處理:

protected void postProcessApplicationContext(ConfigurableApplicationContext context) {
		if (this.beanNameGenerator != null) {
			context.getBeanFactory().registerSingleton(
					AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR,
					this.beanNameGenerator);
		}
		if (this.resourceLoader != null) {
			if (context instanceof GenericApplicationContext) {
				((GenericApplicationContext) context)
						.setResourceLoader(this.resourceLoader);
			}
			if (context instanceof DefaultResourceLoader) {
				((DefaultResourceLoader) context)
						.setClassLoader(this.resourceLoader.getClassLoader());
			}
		}
	}
複製代碼

這裏默認不執行任何邏輯,由於beanNameGeneratorresourceLoader默認爲空。之因此這樣作,是springBoot留給咱們的擴展處理方式,相似於這樣的擴展,spring中也有不少。


2)加載啓動指定類(重點) 這裏會將咱們的啓動類加載spring容器beanDefinitionMap中,爲後續springBoot 自動化配置奠基基礎,springBoot爲咱們提供的各類註解配置也與此有關。

load(context, sources.toArray(new Object[0]));
複製代碼
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();
	}
複製代碼

這裏參數即爲咱們項目啓動時傳遞的參數:SpringApplication.run(SpringBootApplication.class, args); 因爲咱們指定了啓動類,因此上面也就是加載啓動類到容器。

須要注意的是,springBoot2會優先選擇groovy加載方式,找不到再選用java方式。或許groovy動態加載class文件的性能更勝一籌

private int load(Class<?> source) {
		if (isGroovyPresent()
				&& GroovyBeanDefinitionSource.class.isAssignableFrom(source)) {
			// Any GroovyLoaders added in beans{} DSL can contribute beans here
			GroovyBeanDefinitionSource loader = BeanUtils.instantiateClass(source,
					GroovyBeanDefinitionSource.class);
			load(loader);
		}
		if (isComponent(source)) {
			//以註解的方式,將啓動類bean信息存入beanDefinitionMap
			this.annotatedReader.register(source);
			return 1;
		}
		return 0;
	}
複製代碼

上面代碼中啓動類被加載到 beanDefinitionMap中,後續該啓動類將做爲開啓自動化配置的入口,後面一篇文章我會詳細的分析,啓動類是如何加載,以及自動化配置開啓的詳細流程。


3)通知監聽器,容器已準備就緒

listeners.contextLoaded(context);
複製代碼

主仍是針對一些日誌等監聽器的響應處理。


第六步:刷新容器

執行到這裏,springBoot相關的處理工做已經結束,接下的工做就交給了spring。

synchronized (this.startupShutdownMonitor) {
			// Prepare this context for refreshing.
			/**
			 * 刷新上下文環境
			 * 初始化上下文環境,對系統的環境變量或者系統屬性進行準備和校驗
			 * 如環境變量中必須設置某個值才能運行,不然不能運行,這個時候能夠在這裏加這個校驗,
			 * 重寫initPropertySources方法就行了
			 */
			prepareRefresh();
 
			// Tell the subclass to refresh the internal bean factory.
			/**
			 * 初始化BeanFactory,解析XML,至關於以前的XmlBeanFactory的操做,
			 */
			ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
 
			// Prepare the bean factory for use in this context.
			/**
			 * 爲上下文準備BeanFactory,即對BeanFactory的各類功能進行填充,如經常使用的註解@Autowired @Qualifier等
			 * 設置SPEL表達式#{key}的解析器
			 * 設置資源編輯註冊器,如PerpertyEditorSupper的支持
			 * 添加ApplicationContextAwareProcessor處理器
			 * 在依賴注入忽略實現*Aware的接口,如EnvironmentAware、ApplicationEventPublisherAware等
			 * 註冊依賴,如一個bean的屬性中含有ApplicationEventPublisher(beanFactory),則會將beanFactory的實例注入進去
			 */
			prepareBeanFactory(beanFactory);
 
			try {
				// Allows post-processing of the bean factory in context subclasses.
				/**
				 * 提供子類覆蓋的額外處理,即子類處理自定義的BeanFactoryPostProcess
				 */
				postProcessBeanFactory(beanFactory);
 
				// Invoke factory processors registered as beans in the context.
				/**
				 * 激活各類BeanFactory處理器,包括BeanDefinitionRegistryBeanFactoryPostProcessor和普通的BeanFactoryPostProcessor
				 * 執行對應的postProcessBeanDefinitionRegistry方法 和  postProcessBeanFactory方法
				 */
				invokeBeanFactoryPostProcessors(beanFactory);
 
				// Register bean processors that intercept bean creation.
				/**
				 * 註冊攔截Bean建立的Bean處理器,即註冊BeanPostProcessor,不是BeanFactoryPostProcessor,注意二者的區別
				 * 注意,這裏僅僅是註冊,並不會執行對應的方法,將在bean的實例化時執行對應的方法
				 */
				registerBeanPostProcessors(beanFactory);
 
				// Initialize message source for this context.
				/**
				 * 初始化上下文中的資源文件,如國際化文件的處理等
				 */
				initMessageSource();
 
				// Initialize event multicaster for this context.
				/**
				 * 初始化上下文事件廣播器,並放入applicatioEventMulticaster,如ApplicationEventPublisher
				 */
				initApplicationEventMulticaster();
 
				// Initialize other special beans in specific context subclasses.
				/**
				 * 給子類擴展初始化其餘Bean
				 */
				onRefresh();
 
				// Check for listener beans and register them.
				/**
				 * 在全部bean中查找listener bean,而後註冊到廣播器中
				 */
				registerListeners();
 
				// Instantiate all remaining (non-lazy-init) singletons.
				/**
				 * 設置轉換器
				 * 註冊一個默認的屬性值解析器
				 * 凍結全部的bean定義,說明註冊的bean定義將不能被修改或進一步的處理
				 * 初始化剩餘的非惰性的bean,即初始化非延遲加載的bean
				 */
				finishBeanFactoryInitialization(beanFactory);
 
				// Last step: publish corresponding event.
				/**
				 * 初始化生命週期處理器DefaultLifecycleProcessor,DefaultLifecycleProcessor含有start方法和stop方法,spring啓動的時候調用start方法開始生命週期,
				 * spring關閉的時候調用stop方法來結束生命週期,一般用來配置後臺程序,啓動有一直運行,如一直輪詢kafka
				 * 啓動全部實現了Lifecycle接口的類
				 * 經過spring的事件發佈機制發佈ContextRefreshedEvent事件,以保證對應的監聽器作進一步的處理,即對那種在spring啓動後須要處理的一些類,這些類實現了
				 * ApplicationListener<ContextRefreshedEvent> ,這裏就是要觸發這些類的執行(執行onApplicationEvent方法)另外,spring的內置Event有ContextClosedEvent、ContextRefreshedEvent、ContextStartedEvent、ContextStoppedEvent、RequestHandleEvent
				 * 完成初始化,通知生命週期處理器lifeCycleProcessor刷新過程,同時發出ContextRefreshEvent通知其餘人
				 */
				finishRefresh();
			}
 
			catch (BeansException ex) {
				if (logger.isWarnEnabled()) {
					logger.warn("Exception encountered during context initialization - " +
							"cancelling refresh attempt: " + ex);
				}
 
				// Destroy already created singletons to avoid dangling resources.
				destroyBeans();
				// Reset 'active' flag.
				cancelRefresh(ex);
				// Propagate exception to caller.
				throw ex;
			}
 
			finally {
				// Reset common introspection caches in Spring's core, since we // might not ever need metadata for singleton beans anymore... resetCommonCaches(); } } 複製代碼

refresh方法在spring整個源碼體系中舉足輕重,是實現 ioc 和 aop的關鍵。上述流程,不是一篇博文可以展現清楚的,因此這裏暫時不作展開。後續會有詳細的介紹。


第七步:刷新容器後的擴展接口

protected void afterRefresh(ConfigurableApplicationContext context,
			ApplicationArguments args) {
	}
複製代碼

擴展接口,設計模式中的模板方法,默認爲空實現。若是有自定義需求,能夠重寫該方法。好比打印一些啓動結束log,或者一些其它後置處理。


springBoot2啓動流程到這裏就結束了。後續會對springBoot2的經常使用註解,及一些核心類進行介紹。


SpringBoot2 | SpringBoot啓動流程源碼分析(一)

SpringBoot2 | SpringBoot啓動流程源碼分析(二)

SpringBoot2 | @SpringBootApplication註解 自動化配置流程源碼分析(三)

SpringBoot2 | SpringBoot Environment源碼分析(四)

SpringBoot2 | SpringBoot自定義AutoConfiguration | SpringBoot自定義starter(五)

SpringBoot2 | SpringBoot監聽器源碼分析 | 自定義ApplicationListener(六)

SpringBoot2 | 條件註解@ConditionalOnBean原理源碼深度解析(七)

相關文章
相關標籤/搜索