SpringBoot運行源碼分析:Spring應(yīng)用上下文準(zhǔn)備
Spring應(yīng)用上下文的準(zhǔn)備
我們在上一節(jié)完成了應(yīng)用上下文的創(chuàng)建工作,SpringApplication 繼續(xù)通過 prepareContext方法來進行應(yīng)用上下文的準(zhǔn)備工作。首先,通過圖 4-4 來整體了解一下 prepareContext 的核心功能及流程。

配合流程圖,看一下 SpringApplication 中 prepareContext 方法源代碼及功能注解。
- private void prepareContext(ConfigurableApplicationContext context,ConfigurableEnvironment
- environment,
- SpringApplicationRunL
- isteners
- listeners ,
- Applicat ionArguments applicat ionArguments, Bann
- er printedBanner) {
- //沒置上下文的配置環(huán)境
- context . setEnvironment (environment);
- //應(yīng)用上下文后置處理
- postProcessApplicationContext( context);//在 context 刷新之前,Appl icat ionContext Init
- ial izer 初始化 context
- applyInitializers(context);
- //通知監(jiān)聽器 context 準(zhǔn)備完成,該方法以 上為上下文準(zhǔn)備階段,以下為上下文加載階段
- listeners . contextPrepared(context);//打印日志,啟動 Profile
- if (this . logStartupInfo)-
- logStartupInfo(context . getParent() == nu1l);
- logStartupProfileInfo( context);
- }
- //獲得 ConfigurableL istableBeanFactory 并炷冊單例對象
- ConfigurableL istableBeanFactory beanFactory = context . getBeanFactory();
- beanFactory. registerSingleton("springApplicat ionArguments", applicationAr
- guments);
- if (printedBanner != null) {
- //注冊打印日志對象
- beanF actory. registerSingleton("springBootBanner", printedBanner);
- if (beanFactory instanceof DefaultlistableBeanFactory) {
- //沒置是否允許覆蓋炷冊
- ((DefaultListableBeanFactory) beanFactory)
- . setAllowBeanDefinitionOverriding(this . allowBeanDefinitionOverriding);
- //獲取全部配置源,其中包含 primarySources 和 sources
- Set<0bject> sources = getAllSources();
- Assert . notEmpty(sources, "Sources must not be empty");
- //將 sources 中的 Bean 加載到 context 中
- load(context, sources . toArray(new 0bject[0]));
- //遁知監(jiān)聽器 context 加載完成
- listeners . contextLoaded(context);
- }
通過流程圖和具體代碼可以看出,在該方法內(nèi)完成了兩步操作:應(yīng)用上下文的準(zhǔn)備和加載。
下面我們針對具體的源代碼進行詳細(xì)講解。
應(yīng)用上下文準(zhǔn)備階段
在上下文準(zhǔn)備階段,主要有 3 步操作:對 context 設(shè) 置 environment、應(yīng)用上下文后置處理和 ApplicationContextlnitializer 初始化 context 操作。
首先是對 context 設(shè)置 environment,代碼和業(yè)務(wù)操作都很簡單。
- public void setEnvironment (ConfigurableEnvironment environment) {
- //設(shè)置 context 的 environment
- super. setEnvi ronment( environment);
- //設(shè)置 context 的 reader 屬性的 conditionEvaluator 屬性 this.reade
- er. settEnvironment(environment) ;
- //設(shè)置 context 的 scanner 屬性的 environment 屬性
- this. scanner. setEnvi ronment ( envi ronment);
- }
隨 后 , 便 是 進 行 Spring 應(yīng) 用 上 下 文 的 后置處理 , 這 一 步 是 通 過postProcessApplicationContext 方法來完成的。
- protected void postProcessApplicat ionContext (ConfigurableApplicat ionConEext
- context){
- f (this. beanNameGenerator != null) {
- // 如果 beanNameGenerator 為 null, 則將當(dāng)前的 beanNameGenerator 按照默認(rèn)名字進
- 行注冊
- context . getBeanFactory(). regi sterSingleton(
- Annotat ionConfigUtils .CONF IGURATION BEAN NAME GENERATOR,
- this . beanNameGenerator);
- esourceLoader 為 null 時, 則根據(jù) context 的類型分別進行 Resourceloader 和 CL
- assLoader 的設(shè)置
- if (this .resourceLoader != null) {
- F (context instanceof GenericApplicationContext) {
- ((GenericApplicationContext) context) . setResourcel oader(this . resource
- Loader);
- if (context instanceof DefaultResourceLoader) {
- ( (DefaultResourceLoader) context)
- . setClassLoader(this.resourceLoader. getClassLoader());
- //如果為 true 則獲取并沒置轉(zhuǎn)換服務(wù)
- f (this .addConversionService) {
- context . getBeanFactory(). setConversionService(
- ApplicationConversionService . getSharedInstance());
- }
postProcessApplicationContext 方 法 主 要 完 成 上 下 文 的 后 置 操 作 , 默 認(rèn) 包 含beanNameGeneratorResourceL oader.ClassL oader 和 ConversionService 的設(shè)置。該方法可由子類覆蓋實現(xiàn),以添加更多的操作。
而在此階段,beanNameGenerator 和 resourceL oader 都為 null,因此只操作了最后-一步的設(shè)置轉(zhuǎn)換服務(wù)。
最后,在通知監(jiān)聽器 context 準(zhǔn)備完成之前,通過 applylnitializers 方法對上下文進行初始化。
所使用的 ApplicationContextInitializer 正是我們在 SpringApplication 初始化階段設(shè)置在itializers 變量中的值,只不過在通過 getlnitializers 方法獲取時進行了去重和排序。
- protected void applyInitializers(ConfigurableApplicat ionContext context) {
- /獲取 Appl icat ionContextInitializer 集合并遍歷
- for (ApplicationContextInitializer initializer : getInitializers()) {
- //解析當(dāng)前 initial izer.實現(xiàn)的 Appl icat ionContextInitializer 的泛型參數(shù)
- Class<?> requiredType = GenericTypeResolver . resolveTypeArgument(
- initializer . getClass(), ApplicationContextInitializer.class);
- 1 斷言判斷所需類似是否與 context 類型匹配
- Assert. isInstanceOf(requiredType, context, "Unable to call initialize
- r.");
- // 初始化 context
- initializer. initialize(context);
- }
- }
完成以上操作之后,程序便調(diào)用 SpringApplicationRunListeners 的 contextPrepared 方法通知監(jiān)聽器,至此第一階段的準(zhǔn)備操作完成。
應(yīng)用上下文加載階段
應(yīng)用上下文加載階段包含以下步驟:打印日志和 Profile 的設(shè)置、設(shè)置是否允許覆蓋注冊、獲取全部配置源、將配置源加載入上下文、通知監(jiān)控器 contex 加載完成。
首先進入應(yīng)用上下文加載階段的操作為打印日志和 Profile 的設(shè)置,對此不展開講解。隨后,便是獲得 ConfigurableL istableBeanFactory 并注冊單例對象,注冊的單例對象包含:
ApplicationArguments 和 Banner。 當(dāng) BeanFactory 為 DefaultL istableBeanFactory 時,進入設(shè)置是否允許覆蓋注冊的處理邏輯。
此處需注意的是,當(dāng)進行了 ApplicationArguments 類單例對象的注冊之后,也就意味著我們在使用 Spring 應(yīng)用上下文的過程中可以通過依賴注入來使用該對象。
- @Resource
- private ApplicationArguments applicat ionArguments;
完成以.上操作后,便進入配置源信息的處理階段,這一步通過 getAllSources 方法來對配置源信息進行合并操作。
- public Set<Object> getAllSources() {
- Set<0bject> allSources = new LinkedHashSet<>();
- if (!CollectionUtils.isEmpty(this . primarySources)) {
- allSources.addAll(this.primarySources);
- if (!CollectionUtils . isEmpty(this. sources)) {
- allSources. addAll(this.sources);
- }
- }
return Collections . unmodifiableSet(allSources); }以上操作邏輯很簡單,如果 Set 集合中不存在 primarySources 配置源或 sources 配置源,則將其添加入 Set 中,同時將 Set 設(shè)置為不可修改,并返回。
前面章節(jié)已經(jīng)提到,變量 primarySources 的值 來自 SpringApplication 的構(gòu)造參數(shù),變量sources 的值來自 setResources 方法。
當(dāng)獲得所有的配置源信息之后,通過 load 方法將配置源信息加載到上下文中,代碼如下。
- protected void load(ApplicationContext context, Object[] sources) {
- /日志打印
- BeanDefinitionLoader loader = createBeanDefinitionLoader(
- getBeanDefinitionRegistry(context), sources);
- f (this. beanNameGenerator != nu1l).
- loader. setBeanNameGenerator(this . beanNameGenerator);
- if (this.resourceLoader != nu1l) {
- loader . setResourceLoader(this . resourceLoader);
- if (this. environment != null) {
- loader . setEnvironment (this . environment) ;
- loader. load();
- }
該方法主要通過 BeanDefinitionL oader 來完成配置資源的加載操作。我們進一步查看方法createBeanDefinitionL oader 的源代碼,會發(fā)現(xiàn)它最終調(diào)用了 BeanDefinitionL oader 的構(gòu)造方法,并進行初始化操作。
- BeanDefinitionLoader(BeanDefinitionRegistry registry, Object... sources) {
- this. sources = sources;
- this . annotatedReader = new AnnotatedBeanDefinitionReader(registry);
- this . xmlReader = new XmlBeanDefinitionReader(registry);
- if (isGroovyPresent())
- this. groovyReader = new GroovyBeanDefinitionReader(registry);
- }
通過 BeanDefinitionLoader 的構(gòu)造方法我們可以看到 BeanDefinitionLoader 支持基于AnnotatedBeanDefinitionReaderXmlBeanDefinitionReader、GroovyBeanDefinitionReader等 多種類型的加載操作。
在執(zhí)行完 BeanDefinitionL oader 的創(chuàng)建及基本屬性設(shè)置之后,調(diào)用其 load方法,該方法最終執(zhí)行以下代碼。
- private int load(0bject source) {
- Assert. notNull(source, "Source must not be null");
- if (source instanceof Class<?>) {
- return load((Class<?>) source);
- }if (source instanceof Resource)
- return load( (Resource) source);
- }
- if (source instanceof Package) {
- return load( (Package) source);
- }
- if (source instanceof CharSequence) {
- return load( (CharSequence) source);
- throw new IllegalArgumentException("Invalid source type ”+ source. getC
- lass());}
從以上代碼可以看出,BeanDefinitionLoader 加載支持的范圍包括:
Class、Resource、 Package 和 CharSequence 四種。 前面我們已經(jīng)提到變量 sources的來源有 primarySources 配置源和 sources 配置源。變量 primarySources 在初始化時接收的類型為 Class,而變量 sources 通過 set(Set )方法接收的參數(shù)為 String 集合。
因此,在實際使用的過程中,Resource 和 Package 的判斷分支始終無法進入執(zhí)行階段。
完成以上操作后,接下來執(zhí)行 SpringApplicationRunListeners 的 contextL oaded 方法通知監(jiān)聽器上下文加載完成,至此整個 Spring 應(yīng)用上下文的準(zhǔn)備階段完成。