Spring中的Environment外部化配置管理詳解

跟著Mic學架構發表於2022-02-21

Environment的中文意思是環境,它表示整個spring應用執行時的環境資訊,它包含兩個關鍵因素

  • profiles
  • properties

profiles

profiles這個概念相信大家都已經理解了,最常見的就是不同環境下,決定當前spring容器中的不同配置上下文的解決方案。比如針對開發環境、測試環境、生產環境,構建不同的application.properties配置項,這個時候我們可以通過profiles這個屬性來決定當前spring應用上下文中生效的配置項。

實際上,通過profiles可以針對bean的配置進行邏輯分組。 簡單來說,我們可以通過profiles來針對不同的bean進行邏輯分組,這個分組和bean本身的定義沒有任何關係,無論是xml還是註解方式,都可以配置bean屬於哪一個profile分組。

當存在多個profile分組時,我們可以指定哪一個profile生效,當然如果不指定,spring會根據預設的profile去執行。我們來通過一個程式碼演示一下。

ProfileService

建立一個普通的類,程式碼如下

public class ProfileService {
    private String profile;

    public ProfileService(String profile) {

        this.profile = profile;
    }

    @Override
    public String toString() {
        return "ProfileService{" +
                "profile='" + profile + '\'' +
                '}';
    }
}

宣告一個配置類

在配置類中,構建兩個bean,配置不同的profile。

@Configuration
public class ProfileConfiguration {

    @Bean
    @Profile("dev")
    public ProfileService profileServiceDev(){
        return new ProfileService("dev");
    }

    @Bean
    @Profile("prod")
    public ProfileService profileServiceProd(){
        return new ProfileService("prod");
    }
}

定義測試方法

public class ProfileMain {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext applicationContext=new AnnotationConfigApplicationContext();
//        applicationContext.getEnvironment().setActiveProfiles("prod");
        applicationContext.register(ProfileConfiguration.class);
        applicationContext.refresh();
        System.out.println(applicationContext.getBean(ProfileService.class));
    }
}

可以通過很多種方式來啟用配置,預設情況下不新增applicationContext.getEnvironment().setActiveProfiles("prod");時,會發現bean沒有被裝載。新增了之後,會根據當前啟用的profiles來決定裝載哪個bean。

除此之外,我們還可以在啟動引數中增加-Dspring.profiles.active=prod來決定當前啟用哪個profile。該屬性可以配置在系統環境變數、JVM系統屬性、等。

注意配置檔案不是單選;可能會同時啟用多個配置檔案,程式設計式的使用方法setActiveProfiles(),該方法接收String陣列引數,也就是多個配置檔名
applicationContext.getEnvironment().setActiveProfiles("prod","dev");

如果沒有任何profile配置被啟用,預設的profile將會啟用。
預設profile配置檔案可以更改,通過環境變數的setDefaultProfiles方法,或者是宣告的spring.profiles.default屬性值

profiles總結

簡單總結一下profiles,通過profiles可以最一組bean進行邏輯分組,這些邏輯分組的bean會根據Environment上下文中配置的啟用的profile來進行載入,也就是Environment對於profiles配置來說,它能決定當前啟用的是哪個profile配置。

  • 一個profile就是一組Bean定義的邏輯分組。
  • 這個分組,也就 這個profile,被賦予一個命名,就是這個profile名字。
  • 只有當一個profile處於active狀態時,它對應的邏輯上組織在一起的這些Bean定義才會被註冊到容器中。
  • Bean新增到profile可以通過XML定義方式或者annotation註解方式。
  • Environment對於profile所扮演的角色是用來指定哪些profile是當前活躍的預設。

Properties

properties的作用就是用來存放屬性的,它可以幫我們管理各種配置資訊。這個配置的來源可以是properties檔案、JVM properties、系統環境變數、或者專門的Properties物件等。

我們來看一下Environment這個介面,它繼承了PropertyResolver,這個介面和屬性的操作有關,也就是我們可以通過Environment來設定和獲得相關屬性。

public interface Environment extends PropertyResolver {
    String[] getActiveProfiles();

    String[] getDefaultProfiles();

    /** @deprecated */
    @Deprecated
    boolean acceptsProfiles(String... var1);

    boolean acceptsProfiles(Profiles var1);
}

至此,我們可以可以簡單的總結Environment的作用,Environment提供了不同的profile配置,而PropertyResolver提供了配置的操作,由此我們可以知道,Spring 容器可以根據不同的profile來獲取不同的配置資訊,從而實現Spring容器中執行時環境的處理。

environment的應用

  • 在spring boot應用中,修改application.properties配置

    env=default
  • 建立一個Controller進行測試

    @RestController
    public class EnvironementController {
    
        @Autowired
        Environment environment;
    
        @GetMapping("/env")
        public String env(){
            return environment.getProperty("env");
        }
    }

指定profile屬性

在前面的內容中我們介紹了profile和property這兩個概念,現在我們來結合使用加深對這兩者的理解。

在spring boot應用中,預設的外部化配置是application.properties檔案,事實上,除了這個預設的配置檔案之外,我們還可以使用springboot中的約定命名格式來實現不同環境的配置

application-profile.properties

當前spring boot應用選擇使用哪個properties檔案作為上下文環境配置,取決與當前啟用的profile。同樣,我們可以通過很多種方式來啟用,比如在application.properties中增加spring.profiles.active=dev這種方式,也可以在JVM引數中增加該配置來指定生效的配置。

在不指定的情況下,則使用預設的配置檔案,簡單來說,如果沒有顯式啟用某一個配置檔案,那麼應用程式就將載入application-default.properties中的屬性。

這個功能非常實用,一般的公司裡面都會有幾套執行環境,比如開發、測試、生產環境,這些環境中會有一些配置資訊是不同的,比如伺服器地址。那我們需要針對不同的環境使用指定的配置資訊,通過這種方式就可以很方便的去解決。

@Value註解的使用

在properties檔案中定義的屬性,除了可以通過environment的getProperty方法獲取之外,spring還提供了@Value註解,

@RestController
public class EnvironementController {

    @Value("${env}")
    private String env;

    @GetMapping("/env")
    public String env(){
        return env;
    }
}

spring容器在載入一個bean時,當發現這個Bean中有@Value註解時,那麼它可以從Environment中將屬性值進行注入,如果Environment中沒有這個屬性,則會報錯。

Spring Environment原理設計

結合前面我們們講過的內容,我們來推測一下Environment的實現原理。

簡單演示一下Environment中的配置來源
  • @Value("${java.version}") 獲取System.getProperties , 獲取系統屬性
  • 配置command的jvm引數, -Denvtest=command

基於現有的內容的推導,我們可以畫出下面這樣一個圖。

image-20211216204620415

  • 第一部分是屬性定義,這個屬性定義可以來自於很多地方,比如application.properties、或者系統環境變數等。
  • 然後根據約定的方式去指定路徑或者指定範圍去載入這些配置,儲存到記憶體中。
  • 最後,我們可以根據指定的key從快取中去查詢這個值。

下面這個是表示Environment的類關係圖,這個類關係圖還是非常清晰的體現了Environment的原理。

image-20211216210040646

上述類圖的核心API說明如下

  1. Environment介面,繼承了PropertyResolver。 PropertyResolver,它主要有兩個作用。

    • 通過propertyName屬性名獲取與之對應的propertValue屬性值(getProperty)。
    • ${propertyName:defaultValue}格式的屬性佔位符,替換為實際的值(resolvePlaceholders)。
  2. PropertyResolver的具體實現類是PropertySourcesPropertyResolver,屬性源的解決方案。該類是體系中唯一的完整實現類。它以PropertySources屬性源集合(內部持有屬性源列表List<PropertySource>)為屬性值的來源,按序遍歷每個PropertySource,獲取到一個非null的屬性值則返回。

其中,PropertySourcesPropertyResolver中的List<PropertySource>,表示不同屬性源的來源,它的類關係圖如下,表示針對不同資料來源的儲存。

image-20211216211933822

版權宣告:本部落格所有文章除特別宣告外,均採用 CC BY-NC-SA 4.0 許可協議。轉載請註明來自 Mic帶你學架構
如果本篇文章對您有幫助,還請幫忙點個關注和贊,您的堅持是我不斷創作的動力。歡迎關注同名微信公眾號獲取更多技術乾貨!

相關文章