springboot 2.0.3 源碼分析(二)SpringApplication的run方法之prepareEnvironment

    prepareEnvironment按字面意思就是準備環境,其源代碼如下

 

private ConfigurableEnvironment prepareEnvironment(
			SpringApplicationRunListeners listeners,
			ApplicationArguments applicationArguments) {
		// Create and configure the environment獲取或創建環境
		ConfigurableEnvironment environment = getOrCreateEnvironment();
                //配置環境:配置PropertySources和activeProfiles
		configureEnvironment(environment, applicationArguments.getSourceArgs());
                // listeners環境準備(就是廣播ApplicationEnvironmentPreparedEvent事件)。這個listeners就是上一章所講的內容
		listeners.environmentPrepared(environment);
                //將環境綁定到SpringApplication
		bindToSpringApplication(environment);
                //如果是非web環境,將環境轉換成StandardEnvironment,是否是web環境配置也在SpringApplicaiton的構造函數裏講過
		if (this.webApplicationType == WebApplicationType.NONE) {
			environment = new EnvironmentConverter(getClassLoader())
					.convertToStandardEnvironmentIfNecessary(environment);
		}
                //配置PropertySources對它自己的遞歸依賴
		ConfigurationPropertySources.attach(environment);
		return environment;
	}

從第一個方法看起,getOrCreateEnvironment,先上源碼

private ConfigurableEnvironment getOrCreateEnvironment() {
		if (this.environment != null) {
			return this.environment;
		}
                // 根據webApplicationType創建對應的Environment
		if (this.webApplicationType == WebApplicationType.SERVLET) {
                        //標準Servlet環境,也就是我們說的web環境
			return new StandardServletEnvironment();
		}
                //標準環境,非web環境
		return new StandardEnvironment();
	}

從字面上看,這個方法的作用就是獲取或創建環境,應該就是存在就直接返回,不存在則創建一個並返回。

StandardServletEnvironment類圖

StandardServletEnvironment繼承自StandardEnvironment,也就是web環境是特殊的非web環境,有點類似正方形是特殊的長方形一樣。AbstractEnvironment的構造方法調用了customizePropertySources方法,也就說StandardServletEnvironment在實例化的時候,他的customizePropertySources會被調用,customizePropertySources源代碼如下

 

@Override
	protected void customizePropertySources(MutablePropertySources propertySources) {
		propertySources.addLast(new StubPropertySource(SERVLET_CONFIG_PROPERTY_SOURCE_NAME));
		propertySources.addLast(new StubPropertySource(SERVLET_CONTEXT_PROPERTY_SOURCE_NAME));
		if (JndiLocatorDelegate.isDefaultJndiEnvironmentAvailable()) {
			propertySources.addLast(new JndiPropertySource(JNDI_PROPERTY_SOURCE_NAME));
		}
		super.customizePropertySources(propertySources);
	}

從上圖中可以看出StandardServletEnvironment的customizePropertySources方法只是往propertySources中添加了兩個名字叫servletConfigInitParams、servletContextInitParams的StubPropertySource對象,沒更多的操作;而StandardEnvironment的customizePropertySources方法則往propertySources中添加了兩個包含java系統屬性和操作系統環境變量的兩個對象:MapPropertySource和SystemEnvironmentPropertySource。

總結下,getOrCreateEnvironment方法創建並返回了一個環境:StandardServletEnvironment,該環境目前包含的內容如下

接着在看configureEnvironment

protected void configureEnvironment(ConfigurableEnvironment environment,
    String[] args) {
    // 配置PropertySources
    configurePropertySources(environment, args);
    // 配置Profiles
    configureProfiles(environment, args);
}

 

從源碼看,將配置任務按順序委託給configurePropertySources和configureProfiles,那麼我們來看看這兩個方法

configurePropertySources

protected void configurePropertySources(ConfigurableEnvironment environment,
        String[] args) {
    MutablePropertySources sources = environment.getPropertySources();
    // 此時defaultProperties還是null
    if (this.defaultProperties != null && !this.defaultProperties.isEmpty()) {
        // 存在的話將其放到最後位置
        sources.addLast(
                new MapPropertySource("defaultProperties", this.defaultProperties));
    }
    // 存在命令行參數,則解析它並封裝進SimpleCommandLinePropertySource對象,同時將此對象放到sources的第一位置(優先級最高)
    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));
        }
    }
}

 註釋說明是增加、移除或者重排序應用環境中的PropertySource。就目前而言,如果有命令行參數則新增封裝命令行參數的PropertySource,並將它放到sources的第一位置。

configureProfiles

protected void configureProfiles(ConfigurableEnvironment environment, String[] args) {
    // 保證environment的activeProfiles屬性被初始化了。從PropertySources中查找spring.profiles.active屬性
    // 存在則將其值添加activeProfiles集合中。我們可以通過命令行參數指定該參數,但我們沒有指定
    environment.getActiveProfiles(); // ensure they are initialized
    // But these ones should go first (last wins in a property key clash)
    // 如果存在其他的Profiles,則將這些Profiles放到第一的位置。此時沒有,後面有沒有後面再說
    Set<String> profiles = new LinkedHashSet<>(this.additionalProfiles);
    profiles.addAll(Arrays.asList(environment.getActiveProfiles()));
    environment.setActiveProfiles(StringUtils.toStringArray(profiles));
}

 

配置應用環境中的哪些配置文件處於激活狀態(或默認激活)。可以通過spring.profiles.active屬性在配置文件處理期間激活其他配置文件。說的簡單點就是設置哪些Profiles是激活的。

    這3個方法都是protected,也就說鼓勵被重寫。重寫configureEnvironment可以完全控制自定義環境,或者重寫configurePropertySources或configureProfiles,進行進行更細粒度控制。

listeners.environmentPrepared(environment)



public void environmentPrepared(ConfigurableEnvironment environment) {
    for (SpringApplicationRunListener listener : this.listeners) {
        listener.environmentPrepared(environment);
    }
}

這個代碼有沒有很熟悉?參照listeners.starting()。上次廣播的是ApplicationStartingEvent事件,而這次廣播的是ApplicationEnvironmentPreparedEvent事件。這裏就不和大家一起跟源代碼了,大家自行去跟哦。我在這總結下:

    過濾出的與ApplicationEnvironmentPreparedEvent相匹配的監聽器列表如下,他們的onApplicationEvent會被調用,大致做了以下事情:  

      ConfigFileApplicationListener
        1、加載EnvironmentPostProcessor列表,仍然是從META-INF/spring.factories中加載(在SpringApplication實例化的時候已經加載了,這次是從緩存中讀取),然後實例化;
        2、將自己也加入EnvironmentPostProcessor列表;ConfigFileApplicationListener實現了EnvironmentPostProcessor接口,可以看它的類圖。
        3、對EnvironmentPostProcessor列表進行排序;排序之後,EnvironmentPostProcessor列表圖如下:
        4、遍歷EnvironmentPostProcessor列表,調用每個EnvironmentPostProcessor的postProcessEnvironment方法

          SystemEnvironmentPropertySourceEnvironmentPostProcessor

            將propertySourceList中名爲systemEnvironment的SystemEnvironmentPropertySource對象替換成OriginAwareSystemEnvironmentPropertySource對象,source未變,還是SystemEnvironmentPropertySource對象的source;OriginAwareSystemEnvironmentPropertySource是SystemEnvironmentPropertySourceEnvironmentPostProcessor的靜態內部類,且繼承自SystemEnvironmentPropertySource。具體這麼替換出於什麼目的,便於原點查找?暫時還未知。

          SpringApplicationJsonEnvironmentPostProcessor

            spring.application.json(或SPRING_APPLICATION_JSON)是設置在系統屬性或系統環境中;

            如果spring.application.json(或SPRING_APPLICATION_JSON)有配置,那麼給environment的propertySourceList增加JsonPropertySource,並將JsonPropertySource放到名叫systemProperties的PropertySource前;目前沒有配置,那麼此環境後處理器相當於什麼也沒做。

          CloudFoundryVcapEnvironmentPostProcessor

            雲平臺是否激活,激活了則給environment的propertySourceList增加名爲vcap的PropertiesPropertySource對象,並將此對象放到命令行參數PropertySource(名叫commandLineArgs)後。很顯然,我們沒有激活雲平臺,那麼此環境後處理器相當於什麼也沒做。

          ConfigFileApplicationListener

            添加名叫random的RandomValuePropertySource到名叫systemEnvironment的PropertySource後;

            並初始化Profiles;初始化PropertiesPropertySourceLoader和YamlPropertySourceLoader這兩個加載器從file:./config/,file:./,classpath:/config/,classpath:/路徑下加載配置文件,PropertiesPropertySourceLoader加載配置文件application.xml和application.properties,YamlPropertySourceLoader加載配置文件application.yml和application.yaml。目前我們之後classpath:/路徑下有個application.yml配置文件,將其屬性配置封裝進了一個名叫applicationConfig:[]的OriginTrackedMapPropertySource中,並將此對象放到了propertySourceList的最後。

      AnsiOutputApplicationListener

        設置ansi輸出,將AnsiOutput的屬性enabled設置成ALWAYS,即允許ANSI-colored輸出

      LoggingApplicationListener

        初始化日誌系統
      ClasspathLoggingApplicationListener:沒開啓調試,所以什麼也沒做
      BackgroundPreinitializer:此時什麼也沒做
      DelegatingApplicationListener:此時什麼也沒做,因爲環境中沒有配置context.listener.classes屬性
      FileEncodingApplicationListener:此時什麼也沒做,環境中沒有spring.mandatory-file-encoding屬性

      EnableEncryptablePropertiesBeanFactoryPostProcessor:此時什麼也沒有做

    environmentPrepared方法會觸發所有監聽了ApplicationEnvironmentPreparedEvent事件的監聽器,這些監聽器目前主要新增了兩個PropertySource:RandomValuePropertySource和OriginTrackedMapPropertySource,這個OriginTrackedMapPropertySource一般就是我們應用的配置文件application.yml(application.properties)。

bindToSpringApplication(environment)

/**
 * Bind the environment to the {@link SpringApplication}.
 * @param environment the environment to bind
 */
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);
    }
}

代碼比較簡單,應該就是將environment綁定到SpringApplication,可我跟進去發現沒有將environment綁定到SpringApplication,執行完bindToSpringApplication方法後,SpringApplication的屬性environment仍是null,這我就有點懵圈了,那這個方法到底有什麼用,有知道的朋友嗎?

ConfigurationPropertySources.attach(environment)

public static void attach(Environment environment) {
    // 判斷environment是否是ConfigurableEnvironment的實例
    Assert.isInstanceOf(ConfigurableEnvironment.class, environment);
    // 從environment獲取PropertySources
    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封裝成ConfigurationPropertySourcesPropertySource對象,並把這個對象放到sources的第一位置
        sources.addFirst(new ConfigurationPropertySourcesPropertySource(
                ATTACHED_PROPERTY_SOURCE_NAME,
                new SpringConfigurationPropertySources(sources)));
    }
}

將sources封裝成了一個名叫configurationProperties的ConfigurationPropertySourcesPropertySource對象,並把這個對象放到了sources的第一個位置。SpringConfigurationPropertySources是一個將MutablePropertySources轉換成ConfigurationPropertySources的適配器。這就相當於sources的第一個元素是它自己,形成了一個自己對自己的遞歸依賴,這麼做的目的是什麼,暫時還不得而知,也許後面會有所體現,這裏先當做一個疑問留着。

  prepareEnvironment執行完後,此時environment中的內容如下:(重點看下propertySourceList)

 

總結

  1、profile

    直譯的意思總感覺不對(其作用就是指定激活的配置文件,可以區分環境來加載不同的配置),所以文中沒有對其進行翻譯,直接採用的原單詞。有更好理解的小夥伴可以在評論區提供翻譯。

  2、資源文件

    加載外部化配置的資源到environment,Spring Boot設計了一個非常特別的PropertySource順序,以允許對屬性值進行合理的覆蓋。具體有哪些外部化配置,以及他們的優先級情況可以參考《Spring Boot Reference Guide》的第24章節

  3、prepareEnvironment方法到底做了什麼

    加載外部化配置資源到environment,包括命令行參數、servletConfigInitParams、servletContextInitParams、systemProperties、sytemEnvironment、random、application.yml(.yaml/.xml/.properties)等;

    初始化日誌系統。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章