1. 程式人生 > >springboot原始碼分析7-環境屬性構造過程(上)

springboot原始碼分析7-環境屬性構造過程(上)

使用springboot的目的就是在專案開發中,快速出東西,因此springboot對於配置檔案的格式支援是非常豐富的,最常見的配置檔案字尾有如下四種:properties、xml、yml、yaml,比如我們在springboot專案根目錄中配置了一個application.properties檔案,則springboot專案啟動的時候就會自動將該檔案的內容解析並設定到環境中,這樣後續需要使用該檔案中配置的屬性的時候,只需要使用@value即可。同理application.xml、application.yml、application.yaml檔案也會自動被載入並最終設定到環境中。

上面我們提到了環境,那麼環境到底是個什麼玩意呢?在這裡提前說一下:我們這裡關注的是原始碼層面的事情。並非講解

api如何使用。

大家首先思考一下,springboot專案如何啟動,這個到很簡單,無外乎引入springboot依賴包,設定專案啟動的main方法如下所示:

@EnableAutoConfiguration

public class Application {

    private static Logger logger = LoggerFactory.getLogger(Application.class);

    public static void main(String[] args) {

        long startTime = System.currentTimeMillis();

        SpringApplication.run(Application.class,args);

        logger.info("程式啟動花費時間為:" + (System.currentTimeMillis() - startTime) / 1000 + "秒");

    }

}

上述的程式碼非常的簡單,但是springboot做了非常多的事情,因為springboot程式碼體系非常龐大,所以後續的文章是我們講解那些原始碼就直接看那些原始碼,把不需要了解的暫時放到一邊。因此在這裡暫時先關注環境的建立原始碼,我們快速定位到SpringApplication類中的public ConfigurableApplicationContext run(String... args)方法,該方法關於環境的準備程式碼如下所示:

...

ApplicationArguments applicationArguments = new DefaultApplicationArguments(

args);

ConfigurableEnvironment environment = prepareEnvironment(listeners,applicationArguments);

...

prepareEnvironment方法從名字就可以看出來是準備環境(Environment),prepareEnvironment程式碼如下:

private ConfigurableEnvironment prepareEnvironment(

SpringApplicationRunListeners listeners,

    ApplicationArguments applicationArguments) {

    //獲取或者建立環境

    ConfigurableEnvironment environment = getOrCreateEnvironment();

    //配置環境的資訊

    configureEnvironment(environment, applicationArguments.getSourceArgs());

    //通知所有的觀察者,環境已經準備好了。

    listeners.environmentPrepared(environment);

bindToSpringApplication(environment);

if (this.webApplicationType == WebApplicationType.NONE) {

environment = new EnvironmentConverter(getClassLoader())

.convertToStandardEnvironmentIfNecessary(environment);

}

ConfigurationPropertySources.attach(environment);

    return environment;

}

接下來,我們一步步的分析。

1.1. 1.獲取或者建立環境

getOrCreateEnvironment()方法如下所示:

if (this.environment != null) {

return this.environment;

}

if (this.webApplicationType == WebApplicationType.SERVLET) {

return new StandardServletEnvironment();

}

return new StandardEnvironment();

上述程式碼邏輯如下:

1.如果environment不為空則直接返回。

2.如果是web環境則直接例項化StandardServletEnvironment類。

3.如果不是web環境則直接例項化StandardEnvironment類。

1.2. WebApplicationType型別

Springboot2版本開始增加了WebApplicationType的型別,其定義如下:

public enum WebApplicationType {

/**

 * 不需要再web容器的環境下執行,也就是普通的工程

 */

NONE,

/**

    基於servletWeb專案

 */

SERVLET,

/**

響應式web應用==reactive web Spring5版本的新特性

 */

REACTIVE

}

1.3. ConfigurableEnvironment

environment 為ConfigurableEnvironment型別。我們不妨看一下該類的層次圖如下所示:

 

   Environment介面是Spring對當前程式執行期間的環境的封裝(spring)。主要提供了兩大功能:profile和property(頂級介面PropertyResolver提供)。目前主要有StandardEnvironment、StandardServletEnvironment和MockEnvironmentStandardReactiveWebEnvironment4種實現,分別代表普通程式、Web程式測試程式的環境、響應式web環境。通過上述的getOrCreateEnvironment方法處理邏輯也是可以總結出來的。

StandardReactiveWebEnvironmentSpringboot2新引入的,之前的版本沒有這個類。關於這一個後續的章節會單獨的詳細講解。

2.環境的裝載

在上面的程式碼中例項化了StandardServletEnvironment類(我自己的環境是web),例項化該類的時候肯定會例項化其父類AbstractEnvironment,AbstractEnvironment類的建構函式如下:

public AbstractEnvironment() {

    customizePropertySources(this.propertySources);

 }

需要注意一點,因為例項化的是StandardServletEnvironment類,jvm會自動觸發其父類中的建構函式,但是當前程式的this指標依然是StandardServletEnvironment。

this.propertySources屬性如下所示:

AbstractEnvironment.java

private final MutablePropertySources propertySources = new MutablePropertySources(this.logger);

我們繼續跟蹤customizePropertySources方法,如下所示:

AbstractEnvironment.java

protected void customizePropertySources(MutablePropertySources propertySources) {

 }

好吧,customizePropertySources方法竟然是個空的實現,但是注意一點,當前程式this是StandardServletEnvironment例項,我們不妨看一下StandardServletEnvironment類中是否重寫了該方法。果不其然,StandardServletEnvironment類重寫了customizePropertySources方法,詳細程式碼如下所示:

StandardServletEnvironment.java

protected void customizePropertySources(MutablePropertySources propertySources) {

 //servletConfigInitParams

propertySources.addLast(new StubPropertySource(SERVLET_CONFIG_PROPERTY_SOURCE_NAME));

//servletContextInitParams

propertySources.addLast(new StubPropertySource(SERVLET_CONTEXT_PROPERTY_SOURCE_NAME));

 //jndiProperties

 if (JndiLocatorDelegate.isDefaultJndiEnvironmentAvailable()) {

     propertySources.addLast(new JndiPropertySource(JNDI_PROPERTY_SOURCE_NAME));

    }

    super.customizePropertySources(propertySources);

    }

上述的程式碼中,propertySources為AbstractEnvironment.java中的propertySources欄位,因為他是個引用型別,所以可以拿到指標即可修改其值。

1.4. propertySources

雖然我們暫時還不知道propertySources要幹啥,但是我們還是先看明白PropertySources到底要幹啥。PropertySources類可以參考springboot原始碼分析6-springboot之PropertySource類初探一文。

我們再次看一下customizePropertySources方法的實現:

首先新增servletConfigInitParams,然後新增servletContextInitParams,其次判斷是否是jndi環境,如果是則新增jndiProperties,最後呼叫父類的customizePropertySources(propertySources)。

在跟進父類的customizePropertySources(propertySources)方法之前,我們總結一下MutablePropertySources類中propertySourceList已經存在的屬性為servletConfigInitParams、servletContextInitParams、jndiProperties(如果存在)。

StandardEnvironment類為StandardServletEnvironment類的父類,該類的customizePropertySources方法如下:

protected void customizePropertySources(MutablePropertySources propertySources) {

    propertySources.addLast(new MapPropertySource(SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME, getSystemProperties()));

    propertySources.addLast(new SystemEnvironmentPropertySource(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, getSystemEnvironment()));

  }

1、新增systemProperties

2、新增systemEnvironment。

上述的方法邏輯執行完畢之後,MutablePropertySources類中propertySourceList已經存在的屬性為servletConfigInitParams、servletContextInitParams、jndiProperties(如果存在)、systemProperties、systemEnvironment。

經過一系列的跟蹤getOrCreateEnvironment方法所做的事情已經分析完畢了。我們不妨繼往下看。

3.配置環境資訊

configureEnvironment(environment, applicationArguments.getSourceArgs())方法詳細實現如下所示:

protected void configureEnvironment(ConfigurableEnvironment environment,

    String[] args) {

    configurePropertySources(environment, args);

    configureProfiles(environment, args);

  }

3.1配置屬性源

configurePropertySources(environment, args)方法的核心實現如下:

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(

           name + "-" + args.hashCode(), args));

           composite.addPropertySource(source);

       sources.replace(name, composite);

       }

    else {

           sources.addFirst(new SimpleCommandLinePropertySource(args));

    }

    }

     }

1、如果defaultProperties不為空,則繼續新增defaultProperties。思考一個問題defaultProperties怎麼設定?

2、如果addCommandLineProperties為true並且有命令引數,分兩步驟走:第一步存在commandLineArgs則繼續設定屬性;第二步commandLineArgs不存在則在頭部新增commandLineArgs。

上述的程式碼執行完畢之後,MutablePropertySources類中propertySourceList已經存在的屬性為commandLineArgs、servletConfigInitParams、servletContextInitParams、jndiProperties(如果存在)、systemProperties、systemEnvironment、defaultProperties(如果存在)。

3.2配置Profiles

這個後續我們用到了再來講解。

本文我們暫時講解到這裡,後續的文章中,我們繼續跟蹤屬性檔案的載入規則以及載入過程。提前曝光一點:

commandLineArgs、servletConfigInitParams、servletContextInitParams、jndiProperties(如果存在)、systemProperties、systemEnvironment、defaultProperties(如果存在)中的屬性優先順序從前到後依次降低。在最前面的使用優先順序最高。

比如commandLineArgs中存在一個屬性a=1; systemProperties中存在一個屬性a=2,則我們程式使用的時候a=1,因為越靠前的優先順序越高。通過上述的優先順序我們可以發現一個規律,命令列的優先順序最高、其次是程式中的、然後是系統的環境變數以及屬性、最後是預設的。

propertySources介面我們下一節課重點進行分析。

歡迎關注我的微信公眾號,第一時間獲得部落格更新提醒,以及更多成體系的Java相關原創技術乾貨。 
掃一掃下方二維碼或者長按識別二維碼,即可關注。