全面解讀Spring Boot 中的Profile配置體系

在這裡插入圖片描述


Pre

配置體系是基於 Spring Boot 框架開發應用程序的基礎,而自動配置也是該框架的核心功能之一,梳理使用 Spring Boot 配置體系的系統方法.

接下來,我們為這個代碼工程添加一些支持 RESTful 風格的 HTTP 端點,在這裡我們同樣創建一個 CustomerController 類,如下所示

@RestController
@RequestMapping(value="customers")
public class CustomerController {

    @RequestMapping(value = "/{id}", method = RequestMethod.GET)

     public CustomerTicket getCustomerTicketById(@PathVariable Long id) {        

        CustomerTicket customerTicket = new CustomerTicket();
        customerTicket.setId(1L);
        customerTicket.setAccountId(100L);
        customerTicket.setOrderNumber("Order00001");
        customerTicket.setDescription("DemoOrder");
        customerTicket.setCreateTime(new Date());
        return customerTicket;

    }

}

請註意,這裡是為瞭演示方便,我們才使用瞭硬編碼完成瞭一個 HTTP GET 請求的響應處理。

現在 RESTful 端點已經開發完成,我們需要對這個應用程序進行打包。基於 Spring Boot 和 Maven,當我們使用 mvn package 命令構建整個應用程序時,將得到一個 customerservice-0.0.1-SNAPSHOT.jar 文件,而這個 jar 文件就是可以直接運行的可執行文件,內置瞭 Tomcat Web 服務器。也就是說,我們可以通過如下命令直接運行這個 Spring Boot 應用程序:

java –jar customerservice-0.0.1-SNAPSHOT.jar

通過 Postman 訪問“http://localhost:8083/customers/1”端點,可以得到如下圖所示的HTTP響應結果,說明整個服務已經啟動成功。

在這裡插入圖片描述


Spring Boot 中的配置體系

在 Spring Boot 中,其核心設計理念是對配置信息的管理采用約定優於配置。在這一理念下,則意味著開發人員所需要設置的配置信息數量比使用傳統 Spring 框架時還大大減少。

當然,今天我們關註的主要是如何理解並使用 Spring Boot 中的配置信息組織方式,這裡就需要引出一個核心的概念,即 Profile。


配置文件與 Profile

Profile 本質上代表一種用於組織配置信息的維度,在不同場景下可以代表不同的含義。例如,如果 Profile 代表的是一種狀態,我們可以使用 open、halfopen、close 等值來分別代表全開、半開和關閉等。再比如系統需要設置一系列的模板,每個模板中保存著一系列配置項,那麼也可以針對這些模板分別創建 Profile。這裡的狀態或模版的定義完全由開發人員自主設計,我們可以根據需要自定義各種 Profile,這就是 Profile 的基本含義。

為瞭達到集中化管理的目的,Spring Boot 對配置文件的命名也做瞭一定的約定,分別使用 label 和 profile 概念來指定配置信息的版本以及運行環境,其中 label 表示配置版本控制信息,而 profile 則用來指定該配置文件所對應的環境

在 Spring Boot 中,配置文件同時支持 .properties 和 .yml 兩種文件格式,結合 label 和 profile 概念,如下所示的配置文件命名都是常見和合法的:

/{application}.yml

/{application}-{profile}.yml

/{label}/{application}-{profile}.yml

/{application}-{profile}.properties

/{label}/{application}-{profile}.properties

Yaml 的語法和其他高級語言類似,並且可以非常直觀地表達各種列表、清單、標量等數據形態,特別適合用來表達或編輯數據結構和各種配置文件。在這裡,我們指定瞭如下所示的數據源配置,這裡使用瞭 . yml 文件,如下所示:

spring: 

  datasource:

    driver-class-name: com.mysql.cj.jdbc.Driver

    url: jdbc:mysql://127.0.0.1:3306/account

    username: root

	password: root

如果采用 .propertie 配置文件,那麼上述配置信息將表示為如下的形式:

spring.datasource.driverClassName=com.mysql.cj.jdbc.Driver

spring.datasource.url=jdbc:mysql://127.0.0.1:3306/account

spring.datasource.username=root 

spring.datasource.password=root

顯然,類似這樣的數據源通常會根據環境的不同而存在很多套配置。假設我們存在如下所示的配置文件集合:

在這裡插入圖片描述

註意,這裡有一個全局的 application.yml 配置文件以及多個局部的 profile 配置文件。

主 application.properties 中指定激活的Profile

那麼,如何指定當前所使用的那一套配置信息呢?

在 Spring Boot 中,我們可以在主 application.properties 中使用如下的配置方式來激活當前所使用的 Profile:

spring.profiles.active = test

上述配置項意味著系統當前會讀取 application-test.yml 配置文件中的配置內容。同樣,如果使用 .yml 文件,則可以使用如下所示的配置方法:

spring:

  profiles:

    active: test

事實上,我們也可以同時激活幾個 Profile,這完全取決於你對系統配置的需求和維度:

spring.profiles.active: prod, myprofile1, myprofile2

Profile 配置信息隻保存在一個文件

當然,如果你想把所有的 Profile 配置信息隻保存在一個文件中而不是分散在多個配置文件中, Spring Boot 也是支持的,需要做的事情隻是對這些信息按 Profile 進行組織、分段,如下所示:

spring: 

         profiles: test

         #test 環境相關配置信息

spring: 

         profiles: prod

         #prod 環境相關配置信息

推薦按多個配置文件的組織方法管理各個 Profile 配置信息,這樣才不容易混淆和出錯。


java –jar 激活Profile

最後,如果我們不希望在全局配置文件中指定所需要激活的 Profile,而是想把這個過程延遲到運行這個服務時,那麼我們可以直接在 java –jar 命令中添加“–spring.profiles.active”參數,如下所示

java –jar customerservice-0.0.1-SNAPSHOT.jar --spring.profiles.active=prod

這種實現方案在通過腳本進行自動化打包和部署的場景下非常有用。


代碼控制與Profile

在 Spring Boot 中,Profile 這一概念的應用場景還包括動態控制代碼執行流程。為此,我們需要使用 @Profile 註解,先來看一個簡單的示例。

@Configuration

public class DataSourceConfig {

 

    @Bean

    @Profile("dev")

    public DataSource devDataSource() {

        //創建 dev 環境下的 DataSource 

    }

 

    @Bean()

    @Profile("prod")

    public DataSource prodDataSource(){

        //創建 prod 環境下的 DataSource 

    }

}

可以看到,我們構建瞭一個 DataSourceConfig 配置類來專門管理各個環境所需的 DataSource。註意到這裡使用 @Profile 註解來指定具體所需要執行的 DataSource 創建代碼,通過這種方式,可以達到與使用配置文件相同的效果。

更進一步,能夠在代碼中控制 JavaBean 的創建過程為我們根據各種條件動態執行代碼流程提供瞭更大的可能性。

例如,在日常開發過程中,一個常見的需求是根據不同的運行環境初始化數據,常見的做法是獨立執行一段代碼或腳本。基於 @Profile 註解,我們就可以將這一過程包含在代碼中並做到自動化,如下所示:

@Profile("dev")

@Configuration

public class DevDataInitConfig {

 

@Bean

  public CommandLineRunner dataInit() { 

    return new CommandLineRunner() {

      @Override

      public void run(String... args) throws Exception {

        //執行 Dev 環境的數據初始化

    };  

}

這裡用到瞭 Spring Boot 所提供瞭啟動時任務接口 CommandLineRunner,實現瞭該接口的代碼會在 Spring Boot 應用程序啟動時自動進行執行 。

@Profile 註解的應用范圍很廣,我們可以將它添加到包含 @Configuration 和 @Component 註解的類及其方法,也就是說可以延伸到繼承瞭 @Component 註解的 @Service、@Controller、@Repository 等各種註解中。


常見配置場景和內容

下面來看幾個常見的配置示例 , 加深對 Spring Boot 中配置體系的理解。

對於一個 Web 應用程序而言,最常見的配置可能就是指定服務暴露的端口地址,如下所示:

server:

    port: 8080

同時,數據庫訪問也是 Web 應用程序的基本功能,因此,關於數據源的設置也是常見的一種配置場景,上一篇博文時給出瞭一個基本的示例。

這裡再以 JPA 為例,給出如下所示的一種配置方案:

spring:
  jpa:
    hibernate:
      ddl-auto: create
    show-sql: true

顯然,這裡使用瞭 Hibernate 作為 JPA 規范的實現框架,並設置瞭 show-sql 等相關屬性。然後,開發人員一般也需要設置日志級別和對象,如下所示的就是一個典型的配置示例:

logging.level.root=WARN
logging.level.com.springcss.customer=INFO

我們設置瞭系統的全局日志級別為 WARN,而針對自定義的 com.springcss.customer 包下的日志則將其級別調整到 INFO。

這裡需要註意的是,Spring Boot 基於 application.properties 或 application.yml 全局配置文件已經自動內置瞭很多默認配置。即使我們不設置上述配置內容,Spring Boot 仍然可以基於這些默認配置完成系統的初始化。

自動配置是 Spring Boot 中的一個核心概念,我們會在後續內容中給出詳細的實現原理分析。


如何在應用程序中嵌入系統配置信息

我們知道 Spring Boot 通過自動配置機制內置瞭很多默認的配置信息,而在這些配置信息中,有一部分系統配置信息也可以反過來作為配置項應用到我們的應用程序中。

例如,如果想要獲取當前應用程序的名稱並作為一個配置項進行管理,那麼很簡單,我們直接通過 ${spring.application.name} 占位符就可以做到這一點,如下所示:

myapplication.name : ${spring.application.name}

通過 ${} 占位符同樣可以引用配置文件中的其他配置項內容,如在下列配置項中,最終“system.description”配置項的值就是“The system springcss is used for health”。

system.name=springcss
system.domain=health
system.description=The system ${name} is used for ${domain}.

再來看一種場景,假設我們使用 Maven 來構建應用程序,那麼可以按如下所示的配置項來動態獲取與系統構建過程相關的信息:

info: 
  app:
    encoding: @project.build.sourceEncoding@
    java:
      source: @java.version@
      target: @java.version@

上述配置項的效果與如下所示的靜態配置是一樣的:

info:
  app:
    encoding: UTF-8
    java:
        source: 1.8.0_31
        target: 1.8.0_31

根據不同的需求,在應用程序中嵌入系統配置信息是很有用的,特別是在一些面向 DevOps 的應用場景中。


如何創建和使用自定義配置信息

在現實的開發過程中,面對紛繁復雜的應用場景,Spring Boot 所提供的內置配置信息並不一定能夠完全滿足開發的需求,這就需要開發人員創建並管理各種自定義的配置信息。

例如,對於一個電商類應用場景,為瞭鼓勵用戶完成下單操作,我們希望每完成一個訂單給就給到用戶一定數量的積分。從系統擴展性上講,這個積分應該是可以調整的,所以我們創建瞭一個自定義的配置項,如下所示:

springcss.order.point = 10

這裡,我們設置瞭每個訂單對應的積分為 10,那麼應用程序該如何獲取這個配置項的內容呢?通常有兩種方法。


使用 @Value 註解

使用 @Value 註解來註入配置項內容是一種傳統的實現方法。針對前面給出的自定義配置項,我們可以構建一個 SpringCssConfig 類,如下所示:

@Component
public class SpringCssConfig {

    @Value("${springcss.order.point}")
    private int point;

}

在 SpringCssConfig 類中,我們要做的就是在字段上添加 @Value 註解,並指向配置項的名稱即可。


使用 @ConfigurationProperties 註解

相較 @Value 註解,更為現代的一種做法是使用 @ConfigurationProperties 註解。在使用該註解時,我們通常會設置一個“prefix”屬性用來指定配置項的前綴,如下所示:

@Component
@ConfigurationProperties(prefix = "springcss.order")
public class SpringCsshConfig {

	private int point;

	//省略 getter/setter

}

相比 @Value 註解隻能用於指定具體某一個配置項,@ConfigurationProperties 可以用來批量提取配置內容。隻要指定 prefix,我們就可以把該 prefix 下的所有配置項按照名稱自動註入業務代碼中。

我們考慮一種更常見也更復雜的場景:假設用戶根據下單操作獲取的積分並不是固定的,而是根據每個不同類型的訂單會有不同的積分,那麼現在的配置項的內容,如果使用 Yaml 格式的話就應該是這樣:

springcss:
    points:
      orderType[1]: 10
      orderType[2]: 20
      orderType[3]: 30

如果想把這些配置項全部加載到業務代碼中,使用 @ConfigurationProperties 註解同樣也很容易實現。我們可以直接在配置類 SpringCssConfig 中定義一個 Map 對象,然後通過 Key-Value 對來保存這些配置數據,如下所示:

@Component
@ConfigurationProperties(prefix="springcss.points")
public class SpringCssConfig {


    private Map<String, Integer> orderType = new HashMap<>();

	//省略 getter/setter

}

可以看到這裡通過創建一個 HashMap 來保存這些 Key-Value 對。類似的,我們也可以實現常見的一些數據結構的自動嵌入。


為自定義配置項添加提示功能

如果你已經使用過 Spring Boot 中的配置文件,並添加瞭一些內置的配置項,你就會發現,當我們輸入某一個配置項的前綴時,諸如 IDEA、Eclipse 這樣的,IDE 就會自動彈出該前綴下的所有配置信息供你進行選擇,效果如下:

在這裡插入圖片描述

上圖的效果對於管理自定義的配置信息非常有用。如何實現這種效果呢?當我們在 application.yml 配置文件中添加一個自定義配置項時,會註意到 IDE 會出現一個提示,說明這個配置項無法被 IDE 所識別,如下所示:

在這裡插入圖片描述

遇到這種提示時,我們是可以忽略的,因為它不會影響到任何執行效果。

但為瞭達到自動提示效果,我們就需要生成配置元數據。生成元數據的方法也很簡單,直接通過 IDE 的“Create metadata for ‘springcss.order.point’”按鈕,就可以選擇創建配置元數據文件,這個文件的名稱為 additional-spring-configuration-metadata.json,文件內容如下所示:

{"properties": [{
  "name": "springcss.order.point",
  "type": "java.lang.String",
  "description": "A description for 'springcss.order.point'"
}]}

現在,假如我們在 application.properties 文件中輸入“springcss”,IDE 就會自動提示完整的配置項內容,效果如下所示:

在這裡插入圖片描述

另外,假設我們需要為 springcss.order.point 配置項指定一個默認值,可以通過在元數據中添加一個”defaultValue”項來實現,如下所示:

{"properties": [{
  "name": "springcss.order.point",
  "type": "java.lang.String",
  "description": "'springcss.order.point' is userd for setting the point when dealing with an order.",
  "defaultValue": 10
}]}

這時候,在 IDE 中設置這個配置項時,就會提出該配置項的默認值為 10,效果如下所示:

在這裡插入圖片描述


如何組織和整合配置信息

Profile 可以認為是管理配置信息中的一種有效手段。

下面,我們繼續介紹另一種組織和整合配置信息的方法,這種方法同樣依賴於前面介紹的 @ConfigurationProperties 註解。

使用 @PropertySources 註解

在使用 @ConfigurationProperties 註解時,我們可以和 @PropertySource 註解一起進行使用,從而指定從哪個具體的配置文件中獲取配置信息。

例如,在下面這個示例中,我們通過 @PropertySource 註解指定瞭 @ConfigurationProperties 註解中所使用的配置信息是從當前類路徑下的 application.properties 配置文件中進行讀取。

@Component
@ConfigurationProperties(prefix = "springcss.order")
@PropertySource(value = "classpath:application.properties")
public class SpringCssConfig {

}

既然我們可以通過 @PropertySource 註解來指定一個配置文件的引用地址,那麼顯然也可以引入多個配置文件,這時候用到的是 @PropertySources 註解,使用方式如下所示:

@PropertySources({
        @PropertySource("classpath:application.properties "),
        @PropertySource("classpath:redis.properties"),
        @PropertySource("classpath:mq.properties")
})

public class SpringCssConfig {

這裡,我們通過 @PropertySources 註解組合瞭多個 @PropertySource 註解中所指定的配置文件路徑。SpringCssConfig 類可以同時引用所有這些配置文件中的配置項。


spring.config.location 來改變配置文件的默認加載位置

另一方面,我們也可以通過配置 spring.config.location 來改變配置文件的默認加載位置,從而實現對多個配置文件的同時加載。例如,如下所示的執行腳本會在啟動 customerservice-0.0.1-SNAPSHOT.jar 時加載D盤下的 application.properties 文件,以及位於當前類路徑下 config 目錄中的所有配置文件:

java -jar customerservice-0.0.1-SNAPSHOT.jar --spring.config.location=file:///D:/application.properties, classpath:/config/

通過 spring.config.location 指定多個配置文件路徑也是組織和整合配置信息的一種有效的實現方式。


理解配置文件的加載順序

通過前面的示例,我們看到可以把配置文件保存在多個路徑,而這些路徑在加載配置文件時具有一定的順序。Spring Boot 在啟動時會掃描以下位置的 application.properties 或者 application.yml 文件作為全局配置文件:

–file:./config/

–file:./

–classpath:/config/

–classpath:/

以下是按照優先級從高到低的順序,如下所示:

在這裡插入圖片描述

Spring Boot 會全部掃描上圖中的這四個位置,掃描規則是高優先級配置內容會覆蓋低優先級配置內容。而如果高優先級的配置文件中存在與低優先級配置文件不沖突的屬性,則會形成一種互補配置,也就是說會整合所有不沖突的屬性。


如何覆寫內置的配置類

關於 Spring Boot 配置體系,最後值得介紹的就是如何覆寫它所提供的配置類。我們已經反復強調 Spring Boot 內置瞭大量的自動配置,如果我們不想使用這些配置,就需要對它們進行覆寫。

覆寫的方法有很多,我們可以使用配置文件、Groovy 腳本以及 Java 代碼。這裡,我們就以Java代碼為例來簡單演示覆寫配置類的實現方法。

以Spring Security為例

在 Spring Security 體系中,設置用戶認證信息所依賴的配置類是 WebSecurityConfigurer 類。顧名思義,這是一個設置 Web 安全的配置類。

Spring Security 提供瞭 WebSecurityConfigurerAdapter 這個適配器類來簡化該配置類的使用方式,我們可以繼承 WebSecurityConfigurerAdapter 類並且覆寫其中的 configure() 的方法來完成自定義的用戶認證配置工作。

典型的 WebSecurityConfigurerAdapter 子類及其代碼實現如下所示

@Configuration
public class SpringHCssWebSecurityConfigurer extends WebSecurityConfigurerAdapter {

    @Override
    @Bean
    public AuthenticationManager authenticationManagerBean() throws Exception {
       return super.authenticationManagerBean();
    }

 

    @Override
    @Bean
    public UserDetailsService userDetailsServiceBean() throws Exception {
        return super.userDetailsServiceBean();
    }

 

    @Override
    protected void configure(AuthenticationManagerBuilder builder) throws Exception {
   			  builder.inMemoryAuthentication().withUser("springcss_user").password("{noop}password1").roles("USER").and()
                .withUser("springcss_admin").password("{noop}password2").roles("USER", "ADMIN");

    }

}

這裡我們隻需要知道,在 Spring Boot 中,提供瞭一些類的內置配置類,而開發人員可以通過構建諸如上述所示的 SpringCssWebSecurityConfigurer 類來對這些內置配置類進行覆寫,從而實現自定義的配置信息。

以上就是Spring Boot 中的配置體系Profile全面解讀的詳細內容,更多關於Spring Boot配置體系Profile的資料請關註WalkonNet其它相關文章!

推薦閱讀: