上一篇文章講了 Config Server 的設定,這篇就來講怎麼在 client 端正確地拉和使用這些設定。有不少人在這邊踩過坑,主要都是因為搞不清楚 bootstrap.yml 和 application.yml 的區別。
bootstrap.yml vs application.yml
這是最關鍵的一點,千萬別搞反了。
- bootstrap.yml - 用來設定 Spring Cloud 相關的東西,比如 Config Server 的位置。它的載入優先序比 application.yml 更高
- application.yml - 普通的應用設定
如果你把 Config Server 的設定寫在 application.yml,Spring 會先載入 application.yml,但這時候還沒有連到 Config Server,所以設定會不全。一定要寫在 bootstrap.yml。
Config Client 的基本設定
在你的 Spring Boot 應用裡,先加依賴:
1 2 3 4
| <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-config-client</artifactId> </dependency>
|
然後在 bootstrap.yml 裡面設定:
1 2 3 4 5 6 7 8
| spring: application: name: user-service profiles: active: dev cloud: config: uri: http://localhost:8888
|
各個設定項目的含義
spring.application.name - 應用的名稱,Config Server 會用這個名稱去找對應的設定檔
spring.profiles.active - 啟用的 profile,比如 dev、prod
spring.cloud.config.uri - Config Server 的位址
如果你想要 Config Server 的路徑更複雜,可以這樣設定:
1 2 3 4 5 6 7
| spring: cloud: config: uri: http://config-server.example.com:8888 name: user-service profile: dev label: main
|
用 @ConfigurationProperties 綁定設定
與其用 @Value 逐個注入,不如用 @ConfigurationProperties 把相關的設定綁在一個 POJO class 上面。假設 Config Server 有這樣的設定:
1 2 3 4 5 6 7 8 9 10
| app: database: url: jdbc:mysql://localhost:3306/user_db username: root password: password cache: type: redis host: localhost port: 6379
|
你可以建立一個設定 class:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| @Configuration @ConfigurationProperties(prefix = "app") public class AppProperties { private DatabaseProperties database; private CacheProperties cache; public static class DatabaseProperties { private String url; private String username; private String password; } public static class CacheProperties { private String type; private String host; private int port; } }
|
然後在 service 裡面就可以注入:
1 2 3 4 5 6 7 8 9 10 11 12
| @Service public class UserService { @Autowired private AppProperties appProperties; public void connect() { String url = appProperties.getDatabase().getUrl(); String host = appProperties.getCache().getHost(); System.out.println("DB: " + url + ", Cache: " + host); } }
|
@ConfigurationProperties 和 @Value 的區別
| 特性 |
@ConfigurationProperties |
@Value |
| 適合用途 |
多個相關設定的綁定 |
單個設定值 |
| 前綴支援 |
支援,整個 prefix 下的都會綁定 |
不支援 |
| 複雜物件 |
支援巢狀物件 |
只能用 SpEL |
| 驗證 |
可以加 @Validated |
不支援 |
| 設定來源 |
environment + Config Server |
environment |
用 SpEL 的例子:
1 2 3 4 5 6 7 8 9
| @Service public class UserService { @Value("${app.database.url}") private String dbUrl; @Value("${app.cache.host:localhost}") private String cacheHost; }
|
動態更新設定(不重啟應用)
有時候你想要改設定不重啟應用。Spring Cloud Config 提供了一個功能——用 @RefreshScope + POST /actuator/refresh 來做這個。
首先加 actuator 依賴:
1 2 3 4
| <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency>
|
然後在 Config Client 的 application.yml 裡面開啟 refresh endpoint:
1 2 3 4 5
| management: endpoints: web: exposure: include: refresh
|
在你想要支援熱更新的 Bean 上加 @RefreshScope:
1 2 3 4 5 6 7 8 9 10 11
| @Component @RefreshScope public class FeatureFlagService { @Value("${features.new-ui:false}") private boolean newUiEnabled; public boolean isNewUiEnabled() { return newUiEnabled; } }
|
改完設定檔後,呼叫這個 endpoint 就能更新:
1
| curl -X POST http://localhost:8080/actuator/refresh
|
應用就會從 Config Server 重新拉設定,不用重啟。
用 @ConfigurationProperties + @RefreshScope
如果你用 @ConfigurationProperties 和 @RefreshScope 組合,會更方便:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| @Component @ConfigurationProperties(prefix = "features") @RefreshScope public class FeatureProperties { private boolean newUiEnabled; private boolean betaFeatureEnabled; }
@Service public class UserService { @Autowired private FeatureProperties featureProperties; public void doSomething() { if (featureProperties.isNewUiEnabled()) { } else { } } }
|
開發 vs 生產的設定切換
通常開發和生產會有不同的 Config Server。在 bootstrap.yml 裡面用環境變數切換:
1 2 3 4 5 6 7 8
| spring: application: name: user-service profiles: active: ${PROFILE:dev} cloud: config: uri: ${CONFIG_SERVER_URI:http://localhost:8888}
|
然後啟動應用時傳入環境變數:
1 2 3 4 5 6 7 8 9
| export PROFILE=dev export CONFIG_SERVER_URI=http://localhost:8888
export PROFILE=prod export CONFIG_SERVER_URI=http://config.production.com:8888
java -jar user-service.jar
|
設定載入的優先順序
Spring Cloud Config Client 的設定載入優先順序是這樣的(高到低):
- 環境變數
- Config Server 的設定
application.yml / application-{profile}.yml
bootstrap.yml / bootstrap-{profile}.yml
- 預設值
也就是說,如果你在環境變數裡設定了某個值,它會覆蓋 Config Server 的值。
常見的坑
1. 把設定寫在 application.yml 而不是 bootstrap.yml
1 2 3 4 5
| spring: cloud: config: uri: http://localhost:8888
|
Config Server 的設定一定要在 bootstrap.yml。
2. 忘記加 spring-cloud-config-client 依賴
沒有這個依賴,Spring 根本不會去連 Config Server。
3. @ConfigurationProperties 沒有指定 prefix
1 2 3 4 5 6 7 8 9
| @ConfigurationProperties public class AppProperties { }
@ConfigurationProperties(prefix = "app") public class AppProperties { }
|
4. 熱更新時忘記加 @RefreshScope
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| @Component public class FeatureService { @Value("${feature.enabled}") private boolean enabled; }
@Component @RefreshScope public class FeatureService { @Value("${feature.enabled}") private boolean enabled; }
|
完整例子
Config Server 的 user-service-dev.yml:
1 2 3 4 5 6 7 8 9
| server: port: 8001
app: name: user-service database: url: jdbc:mysql://localhost:3306/user_db username: root password: password
|
Client 的 bootstrap.yml:
1 2 3 4 5 6 7 8
| spring: application: name: user-service profiles: active: dev cloud: config: uri: http://localhost:8888
|
Client 的 application.yml:
1 2 3
| logging: level: root: INFO
|
Client 的設定 class:
1 2 3 4 5 6 7 8
| @Configuration @ConfigurationProperties(prefix = "app") public class AppConfig { private String name; private DatabaseConfig database; }
|
Client 啟動時,Spring 會:
- 讀 bootstrap.yml,知道要去連 Config Server
- 連到 Config Server,拉
user-service-dev.yml
- 合併兩份設定
- 初始化 Bean
重點整理
- 設定一定要在
bootstrap.yml,不是 application.yml
- 用
@ConfigurationProperties 綁定相關設定,更整潔
- 用
@RefreshScope + /actuator/refresh 可以不重啟更新設定
- Config Server 的設定優先序比本地設定高
- 環境變數優先序最高
搞清楚這些概念後,就能輕鬆管理各個環境的設定了。
你的鼓勵將被轉換為我明天繼續加班的動力(真的)。 ❤️