一、准備工作
1.1 環境要求
- Java: 1.7+
- Guava: 15.0+
- Apollo客戶端默認會引用Guava 19,如果你的項目引用了其它版本,請確保版本號大於等於15.0
注:對於Apollo客戶端,如果有需要的話,可以做少量代碼修改來降級到Java 1.6,詳細信息可以參考Issue 483
1.2 必選設置
Apollo客戶端依賴於AppId,Apollo Meta Server等環境信息來工作,所以請確保閱讀下面的說明並且做正確的配置:
1.2.1 AppId
AppId是應用的身份信息,是從服務端獲取配置的一個重要信息。
有以下幾種方式設置,按照優先級從高到低分別為:
- System Property
Apollo 0.7.0+支持通過System Property傳入app.id信息,如
-Dapp.id=YOUR-APP-ID
- 操作系統的System Environment
Apollo 1.4.0+支持通過操作系統的System Environment APP_ID來傳入app.id信息,如
APP_ID=YOUR-APP-ID
- Spring Boot application.properties
Apollo 1.0.0+支持通過Spring Boot的application.properties文件配置,如
app.id=YOUR-APP-ID
該配置方式不適用於多個war包部署在同一個tomcat的使用場景
- app.properties
確保classpath:/META-INF/app.properties文件存在,並且其中內容形如:
app.id=YOUR-APP-ID
文件位置參考如下:

注:app.id是用來標識應用身份的唯一id,格式為string。
1.2.2 Apollo Meta Server
Apollo支持應用在不同的環境有不同的配置,所以需要在運行提供給Apollo客戶端當前環境的Apollo Meta Server信息。默認情況下,meta server和config service是部署在同一個JVM進程,所以meta server的地址就是config service的地址。
為了實現meta server的高可用,推薦通過SLB(Software Load Balancer)做動態負載均衡。Meta server地址也可以填入IP,如http://1.1.1.1:8080,http://2.2.2.2:8080,不過生產環境還是建議使用域名(走slb),因為機器擴容、縮容等都可能導致IP列表的變化。
1.0.0版本開始支持以下方式配置apollo meta server信息,按照優先級從高到低分別為:
- 通過Java System Property
apollo.meta- 可以通過Java的System Property
apollo.meta來指定 - 在Java程序啟動腳本中,可以指定
-Dapollo.meta=http://config-service-url- 如果是運行jar文件,需要注意格式是
java -Dapollo.meta=http://config-service-url -jar xxx.jar
- 如果是運行jar文件,需要注意格式是
- 也可以通過程序指定,如
System.setProperty("apollo.meta", "http://config-service-url");
- 可以通過Java的System Property
- 通過Spring Boot的配置文件
- 可以在Spring Boot的
application.properties或bootstrap.properties中指定apollo.meta=http://config-service-url
- 可以在Spring Boot的
該配置方式不適用於多個war包部署在同一個tomcat的使用場景
- 通過操作系統的System Environment
APOLLO_META- 可以通過操作系統的System Environment
APOLLO_META來指定 - 注意key為全大寫,且中間是
_分隔
- 可以通過操作系統的System Environment
- 通過
server.properties配置文件- 可以在
server.properties配置文件中指定apollo.meta=http://config-service-url - 對於Mac/Linux,文件位置為
/opt/settings/server.properties - 對於Windows,文件位置為
C:\opt\settings\server.properties
- 可以在
- 通過
app.properties配置文件- 可以在
classpath:/META-INF/app.properties指定apollo.meta=http://config-service-url
- 可以在
- 通過Java system property
${env}_meta- 如果當前env是
dev,那么用戶可以配置-Ddev_meta=http://config-service-url - 使用該配置方式,那么就必須要正確配置Environment,詳見1.2.4.1 Environment
- 如果當前env是
- 通過操作系統的System Environment
${ENV}_META(1.2.0版本開始支持)- 如果當前env是
dev,那么用戶可以配置操作系統的System EnvironmentDEV_META=http://config-service-url - 注意key為全大寫
- 使用該配置方式,那么就必須要正確配置Environment,詳見1.2.4.1 Environment
- 如果當前env是
- 通過
apollo-env.properties文件- 用戶也可以創建一個
apollo-env.properties,放在程序的classpath下,或者放在spring boot應用的config目錄下 - 使用該配置方式,那么就必須要正確配置Environment,詳見1.2.4.1 Environment
- 文件內容形如:
- 用戶也可以創建一個
dev.meta=http://1.1.1.1:8080
fat.meta=http://apollo.fat.xxx.com uat.meta=http://apollo.uat.xxx.com pro.meta=http://apollo.xxx.com
如果通過以上各種手段都無法獲取到Meta Server地址,Apollo最終會fallback到
http://apollo.meta作為Meta Server地址
1.2.2.1 自定義Apollo Meta Server地址定位邏輯
在1.0.0版本中,Apollo提供了MetaServerProvider SPI,用戶可以注入自己的MetaServerProvider來自定義Meta Server地址定位邏輯。
由於我們使用典型的Java Service Loader模式,所以實現起來還是比較簡單的。
有一點需要注意的是,apollo會在運行時按照順序遍歷所有的MetaServerProvider,直到某一個MetaServerProvider提供了一個非空的Meta Server地址,因此用戶需要格外注意自定義MetaServerProvider的Order。規則是較小的Order具有較高的優先級,因此Order=0的MetaServerProvider會排在Order=1的MetaServerProvider的前面。
如果你的公司有很多應用需要接入Apollo,建議封裝一個jar包,然后提供自定義的Apollo Meta Server定位邏輯,從而可以讓接入Apollo的應用零配置使用。比如自己寫一個xx-company-apollo-client,該jar包依賴apollo-client,在該jar包中通過spi方式定義自定義的MetaServerProvider實現,然后應用直接依賴xx-company-apollo-client即可。
MetaServerProvider的實現可以參考LegacyMetaServerProvider和DefaultMetaServerProvider。
1.2.2.2 跳過Apollo Meta Server服務發現
適用於apollo-client 0.11.0及以上版本
一般情況下都建議使用Apollo的Meta Server機制來實現Config Service的服務發現,從而可以實現Config Service的高可用。不過apollo-client也支持跳過Meta Server服務發現,主要用於以下場景:
- Config Service部署在公有雲上,注冊到Meta Server的是內網地址,本地開發環境無法直接連接
- Config Service部署在docker環境中,注冊到Meta Server的是docker內網地址,本地開發環境無法直接連接
- Config Service部署在kubernetes中,希望使用kubernetes自帶的服務發現能力(Service)
針對以上場景,可以通過直接指定Config Service地址的方式來跳過Meta Server服務發現,按照優先級從高到低分別為:
- 通過Java System Property
apollo.configService- 可以通過Java的System Property
apollo.configService來指定 - 在Java程序啟動腳本中,可以指定
-Dapollo.configService=http://config-service-url:port- 如果是運行jar文件,需要注意格式是
java -Dapollo.configService=http://config-service-url:port -jar xxx.jar
- 如果是運行jar文件,需要注意格式是
- 也可以通過程序指定,如
System.setProperty("apollo.configService", "http://config-service-url:port");
- 可以通過Java的System Property
- 通過操作系統的System Environment
APOLLO_CONFIGSERVICE- 可以通過操作系統的System Environment
APOLLO_CONFIGSERVICE來指定 - 注意key為全大寫,且中間是
_分隔
- 可以通過操作系統的System Environment
- 通過
server.properties配置文件- 可以在
server.properties配置文件中指定apollo.configService=http://config-service-url:port - 對於Mac/Linux,文件位置為
/opt/settings/server.properties - 對於Windows,文件位置為
C:\opt\settings\server.properties
- 可以在
1.2.3 本地緩存路徑
Apollo客戶端會把從服務端獲取到的配置在本地文件系統緩存一份,用於在遇到服務不可用,或網絡不通的時候,依然能從本地恢復配置,不影響應用正常運行。
本地緩存路徑默認位於以下路徑,所以請確保/opt/data或C:\opt\data\目錄存在,且應用有讀寫權限。
- Mac/Linux: /opt/data/{appId}/config-cache
- Windows: C:\opt\data\{appId}\config-cache
本地配置文件會以下面的文件名格式放置於本地緩存路徑下:
{appId}+{cluster}+{namespace}.properties
- appId就是應用自己的appId,如100004458
- cluster就是應用使用的集群,一般在本地模式下沒有做過配置的話,就是default
- namespace就是應用使用的配置namespace,一般是application

文件內容以properties格式存儲,比如如果有兩個key,一個是request.timeout,另一個是batch,那么文件內容就是如下格式:
request.timeout=2000
batch=2000
1.2.3.1 自定義緩存路徑
1.0.0版本開始支持以下方式自定義緩存路徑,按照優先級從高到低分別為:
- 通過Java System Property
apollo.cacheDir- 可以通過Java的System Property
apollo.cacheDir來指定 - 在Java程序啟動腳本中,可以指定
-Dapollo.cacheDir=/opt/data/some-cache-dir- 如果是運行jar文件,需要注意格式是
java -Dapollo.cacheDir=/opt/data/some-cache-dir -jar xxx.jar
- 如果是運行jar文件,需要注意格式是
- 也可以通過程序指定,如
System.setProperty("apollo.cacheDir", "/opt/data/some-cache-dir");
- 可以通過Java的System Property
- 通過Spring Boot的配置文件
- 可以在Spring Boot的
application.properties或bootstrap.properties中指定apollo.cacheDir=/opt/data/some-cache-dir
- 可以在Spring Boot的
- 通過操作系統的System Environment
APOLLO_CACHEDIR- 可以通過操作系統的System Environment
APOLLO_CACHEDIR來指定 - 注意key為全大寫,且中間是
_分隔
- 可以通過操作系統的System Environment
- 通過
server.properties配置文件- 可以在
server.properties配置文件中指定apollo.cacheDir=/opt/data/some-cache-dir - 對於Mac/Linux,文件位置為
/opt/settings/server.properties - 對於Windows,文件位置為
C:\opt\settings\server.properties
- 可以在
注:本地緩存路徑也可用於容災目錄,如果應用在所有config service都掛掉的情況下需要擴容,那么也可以先把配置從已有機器上的緩存路徑復制到新機器上的相同緩存路徑
1.2.4 可選設置
1.2.4.1 Environment
Environment可以通過以下3種方式的任意一個配置:
-
通過Java System Property
- 可以通過Java的System Property
env來指定環境 - 在Java程序啟動腳本中,可以指定
-Denv=YOUR-ENVIRONMENT- 如果是運行jar文件,需要注意格式是
java -Denv=YOUR-ENVIRONMENT -jar xxx.jar
- 如果是運行jar文件,需要注意格式是
- 注意key為全小寫
- 可以通過Java的System Property
-
通過操作系統的System Environment
- 還可以通過操作系統的System Environment
ENV來指定 - 注意key為全大寫
- 還可以通過操作系統的System Environment
-
通過配置文件
- 最后一個推薦的方式是通過配置文件來指定
env=YOUR-ENVIRONMENT - 對於Mac/Linux,文件位置為
/opt/settings/server.properties - 對於Windows,文件位置為
C:\opt\settings\server.properties
- 最后一個推薦的方式是通過配置文件來指定
文件內容形如:
env=DEV
目前,env支持以下幾個值(大小寫不敏感):
- DEV
- Development environment
- FAT
- Feature Acceptance Test environment
- UAT
- User Acceptance Test environment
- PRO
- Production environment
更多環境定義,可以參考Env.java
1.2.4.2 Cluster(集群)
Apollo支持配置按照集群划分,也就是說對於一個appId和一個環境,對不同的集群可以有不同的配置。
1.0.0版本開始支持以下方式集群,按照優先級從高到低分別為:
- 通過Java System Property
apollo.cluster- 可以通過Java的System Property
apollo.cluster來指定 - 在Java程序啟動腳本中,可以指定
-Dapollo.cluster=SomeCluster- 如果是運行jar文件,需要注意格式是
java -Dapollo.cluster=SomeCluster -jar xxx.jar
- 如果是運行jar文件,需要注意格式是
- 也可以通過程序指定,如
System.setProperty("apollo.cluster", "SomeCluster");
- 可以通過Java的System Property
- 通過Spring Boot的配置文件
- 可以在Spring Boot的
application.properties或bootstrap.properties中指定apollo.cluster=SomeCluster
- 可以在Spring Boot的
- 通過Java System Property
- 可以通過Java的System Property
idc來指定環境 - 在Java程序啟動腳本中,可以指定
-Didc=xxx- 如果是運行jar文件,需要注意格式是
java -Didc=xxx -jar xxx.jar
- 如果是運行jar文件,需要注意格式是
- 注意key為全小寫
- 可以通過Java的System Property
- 通過操作系統的System Environment
- 還可以通過操作系統的System Environment
IDC來指定 - 注意key為全大寫
- 還可以通過操作系統的System Environment
- 通過
server.properties配置文件- 可以在
server.properties配置文件中指定idc=xxx - 對於Mac/Linux,文件位置為
/opt/settings/server.properties - 對於Windows,文件位置為
C:\opt\settings\server.properties
- 可以在
Cluster Precedence(集群順序)
-
如果
apollo.cluster和idc同時指定:- 我們會首先嘗試從
apollo.cluster指定的集群加載配置 - 如果沒找到任何配置,會嘗試從
idc指定的集群加載配置 - 如果還是沒找到,會從默認的集群(
default)加載
- 我們會首先嘗試從
-
如果只指定了
apollo.cluster:- 我們會首先嘗試從
apollo.cluster指定的集群加載配置 - 如果沒找到,會從默認的集群(
default)加載
- 我們會首先嘗試從
-
如果只指定了
idc:- 我們會首先嘗試從
idc指定的集群加載配置 - 如果沒找到,會從默認的集群(
default)加載
- 我們會首先嘗試從
-
如果
apollo.cluster和idc都沒有指定:- 我們會從默認的集群(
default)加載配置
- 我們會從默認的集群(
1.2.4.3 設置內存中的配置項是否保持和頁面上的順序一致
適用於1.6.0及以上版本
默認情況下,apollo client內存中的配置存放在Properties中(底下是Hashtable),不會刻意保持和頁面上看到的順序一致,對絕大部分的場景是沒有影響的。不過有些場景會強依賴配置項的順序(如spring cloud zuul的路由規則),針對這種情況,可以開啟OrderedProperties特性來使得內存中的配置順序和頁面上看到的一致。
配置方式按照優先級從高到低分別為:
- 通過Java System Property
apollo.property.order.enable- 可以通過Java的System Property
apollo.property.order.enable來指定 - 在Java程序啟動腳本中,可以指定
-Dapollo.property.order.enable=true- 如果是運行jar文件,需要注意格式是
java -Dapollo.property.order.enable=true -jar xxx.jar
- 如果是運行jar文件,需要注意格式是
- 也可以通過程序指定,如
System.setProperty("apollo.property.order.enable", "true");
- 可以通過Java的System Property
- 通過Spring Boot的配置文件
- 可以在Spring Boot的
application.properties或bootstrap.properties中指定apollo.property.order.enable=true
- 可以在Spring Boot的
- 通過
app.properties配置文件- 可以在
classpath:/META-INF/app.properties指定apollo.property.order.enable=true
- 可以在
二、Maven Dependency
Apollo的客戶端jar包已經上傳到中央倉庫,應用在實際使用時只需要按照如下方式引入即可。
<dependency>
<groupId>com.ctrip.framework.apollo</groupId> <artifactId>apollo-client</artifactId> <version>1.1.0</version> </dependency>
三、客戶端用法
Apollo支持API方式和Spring整合方式,該怎么選擇用哪一種方式?
- API方式靈活,功能完備,配置值實時更新(熱發布),支持所有Java環境。
- Spring方式接入簡單,結合Spring有N種酷炫的玩法,如
- Placeholder方式:
- 代碼中直接使用,如:
@Value("${someKeyFromApollo:someDefaultValue}") - 配置文件中使用替換placeholder,如:
spring.datasource.url: ${someKeyFromApollo:someDefaultValue} - 直接托管spring的配置,如在apollo中直接配置
spring.datasource.url=jdbc:mysql://localhost:3306/somedb?characterEncoding=utf8
- 代碼中直接使用,如:
- Spring boot的@ConfigurationProperties方式
- 從v0.10.0開始的版本支持placeholder在運行時自動更新,具體參見PR #972。(v0.10.0之前的版本在配置變化后不會重新注入,需要重啟才會更新,如果需要配置值實時更新,可以參考后續3.2.2 Spring Placeholder的使用的說明)
- Placeholder方式:
- Spring方式也可以結合API方式使用,如注入Apollo的Config對象,就可以照常通過API方式獲取配置了:
@ApolloConfig private Config config; //inject config for namespace application - 更多有意思的實際使用場景和示例代碼,請參考apollo-use-cases
3.1 API使用方式
API方式是最簡單、高效使用Apollo配置的方式,不依賴Spring框架即可使用。
3.1.1 獲取默認namespace的配置(application)
Config config = ConfigService.getAppConfig(); //config instance is singleton for each namespace and is never null String someKey = "someKeyFromDefaultNamespace"; String someDefaultValue = "someDefaultValueForTheKey"; String value = config.getProperty(someKey, someDefaultValue);
通過上述的config.getProperty可以獲取到someKey對應的實時最新的配置值。
另外,配置值從內存中獲取,所以不需要應用自己做緩存。
3.1.2 監聽配置變化事件
監聽配置變化事件只在應用真的關心配置變化,需要在配置變化時得到通知時使用,比如:數據庫連接串變化后需要重建連接等。
如果只是希望每次都取到最新的配置的話,只需要按照上面的例子,調用config.getProperty即可。
Config config = ConfigService.getAppConfig(); //config instance is singleton for each namespace and is never null config.addChangeListener(new ConfigChangeListener() { @Override public void onChange(ConfigChangeEvent changeEvent) { System.out.println("Changes for namespace " + changeEvent.getNamespace()); for (String key : changeEvent.changedKeys()) { ConfigChange change = changeEvent.getChange(key); System.out.println(String.format("Found change - key: %s, oldValue: %s, newValue: %s, changeType: %s", change.getPropertyName(), change.getOldValue(), change.getNewValue(), change.getChangeType())); } } });
3.1.3 獲取公共Namespace的配置
String somePublicNamespace = "CAT"; Config config = ConfigService.getConfig(somePublicNamespace); //config instance is singleton for each namespace and is never null String someKey = "someKeyFromPublicNamespace"; String someDefaultValue = "someDefaultValueForTheKey"; String value = config.getProperty(someKey, someDefaultValue);
3.1.4 獲取非properties格式namespace的配置
3.1.4.1 yaml/yml格式的namespace
apollo-client 1.3.0版本開始對yaml/yml做了更好的支持,使用起來和properties格式一致。
Config config = ConfigService.getConfig("application.yml"); String someKey = "someKeyFromYmlNamespace"; String someDefaultValue = "someDefaultValueForTheKey"; String value = config.getProperty(someKey, someDefaultValue);
3.1.4.2 非yaml/yml格式的namespace
獲取時需要使用ConfigService.getConfigFile接口並指定Format,如ConfigFileFormat.XML。
String someNamespace = "test"; ConfigFile configFile = ConfigService.getConfigFile("test", ConfigFileFormat.XML); String content = configFile.getContent();
3.2 Spring整合方式
3.2.1 配置
Apollo也支持和Spring整合(Spring 3.1.1+),只需要做一些簡單的配置就可以了。
Apollo目前既支持比較傳統的基於XML的配置,也支持目前比較流行的基於Java(推薦)的配置。
如果是Spring Boot環境,建議參照3.2.1.3 Spring Boot集成方式(推薦)配置。
需要注意的是,如果之前有使用org.springframework.beans.factory.config.PropertyPlaceholderConfigurer的,請替換成org.springframework.context.support.PropertySourcesPlaceholderConfigurer。Spring 3.1以后就不建議使用PropertyPlaceholderConfigurer了,要改用PropertySourcesPlaceholderConfigurer。
如果之前有使用<context:property-placeholder>,請注意xml中引入的spring-context.xsd版本需要是3.1以上(一般只要沒有指定版本會自動升級的),建議使用不帶版本號的形式引入,如:http://www.springframework.org/schema/context/spring-context.xsd
注1:yaml/yml格式的namespace從1.3.0版本開始支持和Spring整合,注入時需要填寫帶后綴的完整名字,比如application.yml
注2:非properties、非yaml/yml格式(如xml,json等)的namespace暫不支持和Spring整合。
3.2.1.1 基於XML的配置
注:需要把apollo相關的xml namespace加到配置文件頭上,不然會報xml語法錯誤。
1.注入默認namespace的配置到Spring中
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:apollo="http://www.ctrip.com/schema/apollo" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.ctrip.com/schema/apollo http://www.ctrip.com/schema/apollo.xsd"> <!-- 這個是最簡單的配置形式,一般應用用這種形式就可以了,用來指示Apollo注入application namespace的配置到Spring環境中 --> <apollo:config/> <bean class="com.ctrip.framework.apollo.spring.TestXmlBean"> <property name="timeout" value="${timeout:100}"/> <property name="batch" value="${batch:200}"/> </bean> </beans>
2.注入多個namespace的配置到Spring中
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:apollo="http://www.ctrip.com/schema/apollo" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.ctrip.com/schema/apollo http://www.ctrip.com/schema/apollo.xsd"> <!-- 這個是最簡單的配置形式,一般應用用這種形式就可以了,用來指示Apollo注入application namespace的配置到Spring環境中 --> <apollo:config/> <!-- 這個是稍微復雜一些的配置形式,指示Apollo注入FX.apollo和application.yml namespace的配置到Spring環境中 --> <apollo:config namespaces="FX.apollo,application.yml"/> <bean class="com.ctrip.framework.apollo.spring.TestXmlBean"> <property name="timeout" value="${timeout:100}"/> <property name="batch" value="${batch:200}"/> </bean> </beans>
3.注入多個namespace,並且指定順序
Spring的配置是有順序的,如果多個property source都有同一個key,那么最終是順序在前的配置生效。
apollo:config如果不指定order,那么默認是最低優先級。
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:apollo="http://www.ctrip.com/schema/apollo" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.ctrip.com/schema/apollo http://www.ctrip.com/schema/apollo.xsd"> <apollo:config order="2"/> <!-- 這個是最復雜的配置形式,指示Apollo注入FX.apollo和application.yml namespace的配置到Spring環境中,並且順序在application前面 --> <apollo:config namespaces="FX.apollo,application.yml" order="1"/> <bean class="com.ctrip.framework.apollo.spring.TestXmlBean"> <property name="timeout" value="${timeout:100}"/> <property name="batch" value="${batch:200}"/> </bean> </beans>
3.2.1.2 基於Java的配置(推薦)
相對於基於XML的配置,基於Java的配置是目前比較流行的方式。
注意@EnableApolloConfig要和@Configuration一起使用,不然不會生效。
1.注入默認namespace的配置到Spring中
//這個是最簡單的配置形式,一般應用用這種形式就可以了,用來指示Apollo注入application namespace的配置到Spring環境中 @Configuration @EnableApolloConfig public class AppConfig { @Bean public TestJavaConfigBean javaConfigBean() { return new TestJavaConfigBean(); } }
2.注入多個namespace的配置到Spring中
@Configuration
@EnableApolloConfig public class SomeAppConfig { @Bean public TestJavaConfigBean javaConfigBean() { return new TestJavaConfigBean(); } } //這個是稍微復雜一些的配置形式,指示Apollo注入FX.apollo和application.yml namespace的配置到Spring環境中 @Configuration @EnableApolloConfig({"FX.apollo", "application.yml"}) public class AnotherAppConfig {}
3.注入多個namespace,並且指定順序
//這個是最復雜的配置形式,指示Apollo注入FX.apollo和application.yml namespace的配置到Spring環境中,並且順序在application前面 @Configuration @EnableApolloConfig(order = 2) public class SomeAppConfig { @Bean public TestJavaConfigBean javaConfigBean() { return new TestJavaConfigBean(); } } @Configuration @EnableApolloConfig(value = {"FX.apollo", "application.yml"}, order = 1) public class AnotherAppConfig {}
3.2.1.3 Spring Boot集成方式(推薦)
Spring Boot除了支持上述兩種集成方式以外,還支持通過application.properties/bootstrap.properties來配置,該方式能使配置在更早的階段注入,比如使用@ConditionalOnProperty的場景或者是有一些spring-boot-starter在啟動階段就需要讀取配置做一些事情(如dubbo-spring-boot-project),所以對於Spring Boot環境建議通過以下方式來接入Apollo(需要0.10.0及以上版本)。
使用方式很簡單,只需要在application.properties/bootstrap.properties中按照如下樣例配置即可。
- 注入默認
applicationnamespace的配置示例
# will inject 'application' namespace in bootstrap phase apollo.bootstrap.enabled = true
- 注入非默認
applicationnamespace或多個namespace的配置示例
apollo.bootstrap.enabled = true
# will inject 'application', 'FX.apollo' and 'application.yml' namespaces in bootstrap phase apollo.bootstrap.namespaces = application,FX.apollo,application.yml
- 將Apollo配置加載提到初始化日志系統之前(1.2.0+)
從1.2.0版本開始,如果希望把日志相關的配置(如logging.level.root=info或logback-spring.xml中的參數)也放在Apollo管理,那么可以額外配置apollo.bootstrap.eagerLoad.enabled=true來使Apollo的加載順序放到日志系統加載之前,不過這會導致Apollo的啟動過程無法通過日志的方式輸出(因為執行Apollo加載的時候,日志系統壓根沒有准備好呢!所以在Apollo代碼中使用Slf4j的日志輸出便沒有任何內容),更多信息可以參考PR 1614。參考配置示例如下:
# will inject 'application' namespace in bootstrap phase apollo.bootstrap.enabled = true # put apollo initialization before logging system initialization apollo.bootstrap.eagerLoad.enabled=true
3.2.2 Spring Placeholder的使用
Spring應用通常會使用Placeholder來注入配置,使用的格式形如${someKey:someDefaultValue},如${timeout:100}。冒號前面的是key,冒號后面的是默認值。
建議在實際使用時盡量給出默認值,以免由於key沒有定義導致運行時錯誤。
從v0.10.0開始的版本支持placeholder在運行時自動更新,具體參見PR #972。
如果需要關閉placeholder在運行時自動更新功能,可以通過以下兩種方式關閉:
-
通過設置System Property
apollo.autoUpdateInjectedSpringProperties,如啟動時傳入-Dapollo.autoUpdateInjectedSpringProperties=false -
通過設置META-INF/app.properties中的
apollo.autoUpdateInjectedSpringProperties屬性,如
app.id=SampleApp
apollo.autoUpdateInjectedSpringProperties=false
3.2.2.1 XML使用方式
假設我有一個TestXmlBean,它有兩個配置項需要注入:
public class TestXmlBean { private int timeout; private int batch; public void setTimeout(int timeout) { this.timeout = timeout; } public void setBatch(int batch) { this.batch = batch; } public int getTimeout() { return timeout; } public int getBatch() { return batch; } }
那么,我在XML中會使用如下方式來定義(假設應用默認的application namespace中有timeout和batch的配置項):
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:apollo="http://www.ctrip.com/schema/apollo" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.ctrip.com/schema/apollo http://www.ctrip.com/schema/apollo.xsd"> <apollo:config/> <bean class="com.ctrip.framework.apollo.spring.TestXmlBean"> <property name="timeout" value="${timeout:100}"/> <property name="batch" value="${batch:200}"/> </bean> </beans>
3.2.2.2 Java Config使用方式
假設我有一個TestJavaConfigBean,通過Java Config的方式還可以使用@Value的方式注入:
public class TestJavaConfigBean { @Value("${timeout:100}") private int timeout; private int batch; @Value("${batch:200}") public void setBatch(int batch) { this.batch = batch; } public int getTimeout() { return timeout; } public int getBatch() { return batch; } }
在Configuration類中按照下面的方式使用(假設應用默認的application namespace中有timeout和batch的配置項):
@Configuration
@EnableApolloConfig public class AppConfig { @Bean public TestJavaConfigBean javaConfigBean() { return new TestJavaConfigBean(); } }
3.2.2.3 ConfigurationProperties使用方式
Spring Boot提供了@ConfigurationProperties把配置注入到bean對象中。
Apollo也支持這種方式,下面的例子會把redis.cache.expireSeconds和redis.cache.commandTimeout分別注入到SampleRedisConfig的expireSeconds和commandTimeout字段中。
@ConfigurationProperties(prefix = "redis.cache") public class SampleRedisConfig { private int expireSeconds; private int commandTimeout; public void setExpireSeconds(int expireSeconds) { this.expireSeconds = expireSeconds; } public void setCommandTimeout(int commandTimeout) { this.commandTimeout = commandTimeout; } }
在Configuration類中按照下面的方式使用(假設應用默認的application namespace中有redis.cache.expireSeconds和redis.cache.commandTimeout的配置項):
@Configuration
@EnableApolloConfig public class AppConfig { @Bean public SampleRedisConfig sampleRedisConfig() { return new SampleRedisConfig(); } }
需要注意的是,@ConfigurationProperties如果需要在Apollo配置變化時自動更新注入的值,需要配合使用EnvironmentChangeEvent或RefreshScope。相關代碼實現,可以參考apollo-use-cases項目中的ZuulPropertiesRefresher.java和apollo-demo項目中的SampleRedisConfig.java以及SpringBootApolloRefreshConfig.java
3.2.3 Spring Annotation支持
Apollo同時還增加了幾個新的Annotation來簡化在Spring環境中的使用。
- @ApolloConfig
- 用來自動注入Config對象
- @ApolloConfigChangeListener
- 用來自動注冊ConfigChangeListener
- @ApolloJsonValue
- 用來把配置的json字符串自動注入為對象
使用樣例如下:
public class TestApolloAnnotationBean { @ApolloConfig private Config config; //inject config for namespace application @ApolloConfig("application") private Config anotherConfig; //inject config for namespace application @ApolloConfig("FX.apollo") private Config yetAnotherConfig; //inject config for namespace FX.apollo @ApolloConfig("application.yml") private Config ymlConfig; //inject config for namespace application.yml /** * ApolloJsonValue annotated on fields example, the default value is specified as empty list - [] * <br /> * jsonBeanProperty=[{"someString":"hello","someInt":100},{"someString":"world!","someInt":200}] */ @ApolloJsonValue("${jsonBeanProperty:[]}") private List<JsonBean> anotherJsonBeans; @Value("${batch:100}") private int batch; //config change listener for namespace application @ApolloConfigChangeListener private void someOnChange(ConfigChangeEvent changeEvent) { //update injected value of batch if it is changed in Apollo if (changeEvent.isChanged("batch")) { batch = config.getIntProperty("batch", 100); } } //config change listener for namespace application @ApolloConfigChangeListener("application") private void anotherOnChange(ConfigChangeEvent changeEvent) { //do something } //config change listener for namespaces application, FX.apollo and application.yml @ApolloConfigChangeListener({"application", "FX.apollo", "application.yml"}) private void yetAnotherOnChange(ConfigChangeEvent changeEvent) { //do something } //example of getting config from Apollo directly //this will always return the latest value of timeout public int getTimeout() { return config.getIntProperty("timeout", 200); } //example of getting config from injected value //the program needs to update the injected value when batch is changed in Apollo using @ApolloConfigChangeListener shown above public int getBatch() { return this.batch; } private static class JsonBean{ private String someString; private int someInt; } }
在Configuration類中按照下面的方式使用:
@Configuration
@EnableApolloConfig public class AppConfig { @Bean public TestApolloAnnotationBean testApolloAnnotationBean() { return new TestApolloAnnotationBean(); } }
3.2.4 已有配置遷移
很多情況下,應用可能已經有不少配置了,比如Spring Boot的應用,就會有bootstrap.properties/yml, application.properties/yml等配置。
在應用接入Apollo之后,這些配置是可以非常方便的遷移到Apollo的,具體步驟如下:
- 在Apollo為應用新建項目
- 在應用中配置好META-INF/app.properties
- 建議把原先配置先轉為properties格式,然后通過Apollo提供的文本編輯模式全部粘帖到應用的application namespace,發布配置
- 如果原來格式是yml,可以使用YamlPropertiesFactoryBean.getObject轉成properties格式
- 如果原來是yml,想繼續使用yml來編輯配置,那么可以創建私有的application.yml namespace,把原來的配置全部粘貼進去,發布配置
- 需要apollo-client是1.3.0及以上版本
- 把原先的配置文件如bootstrap.properties/yml, application.properties/yml從項目中刪除
- 如果需要保留本地配置文件,需要注意部分配置如
server.port必須確保本地文件已經刪除該配置項
- 如果需要保留本地配置文件,需要注意部分配置如
如:
spring.application.name = reservation-service
server.port = 8080 logging.level = ERROR eureka.client.serviceUrl.defaultZone = http://127.0.0.1:8761/eureka/ eureka.client.healthcheck.enabled = true eureka.client.registerWithEureka = true eureka.client.fetchRegistry = true eureka.client.eurekaServiceUrlPollIntervalSeconds = 60 eureka.instance.preferIpAddress = true

3.3 Demo
項目中有一個樣例客戶端的項目:apollo-demo,具體信息可以參考Apollo開發指南中的2.3 Java樣例客戶端啟動部分。
更多使用案例Demo可以參考Apollo使用場景和示例代碼。
四、客戶端設計

上圖簡要描述了Apollo客戶端的實現原理:
- 客戶端和服務端保持了一個長連接,從而能第一時間獲得配置更新的推送。(通過Http Long Polling實現)
- 客戶端還會定時從Apollo配置中心服務端拉取應用的最新配置。
- 這是一個fallback機制,為了防止推送機制失效導致配置不更新
- 客戶端定時拉取會上報本地版本,所以一般情況下,對於定時拉取的操作,服務端都會返回304 - Not Modified
- 定時頻率默認為每5分鍾拉取一次,客戶端也可以通過在運行時指定System Property:
apollo.refreshInterval來覆蓋,單位為分鍾。
- 客戶端從Apollo配置中心服務端獲取到應用的最新配置后,會保存在內存中
- 客戶端會把從服務端獲取到的配置在本地文件系統緩存一份
- 在遇到服務不可用,或網絡不通的時候,依然能從本地恢復配置
- 應用程序可以從Apollo客戶端獲取最新的配置、訂閱配置更新通知
五、本地開發模式
Apollo客戶端還支持本地開發模式,這個主要用於當開發環境無法連接Apollo服務器的時候,比如在郵輪、飛機上做相關功能開發。
在本地開發模式下,Apollo只會從本地文件讀取配置信息,不會從Apollo服務器讀取配置。
可以通過下面的步驟開啟Apollo本地開發模式。
5.1 修改環境
修改/opt/settings/server.properties(Mac/Linux)或C:\opt\settings\server.properties(Windows)文件,設置env為Local:
env=Local
更多配置環境的方式請參考1.2.2 Environment
5.2 准備本地配置文件
在本地開發模式下,Apollo客戶端會從本地讀取文件,所以我們需要事先准備好配置文件。
5.2.1 本地配置目錄
本地配置目錄位於:
- Mac/Linux: /opt/data/{appId}/config-cache
- Windows: C:\opt\data\{appId}\config-cache
appId就是應用的appId,如100004458。
請確保該目錄存在,且應用程序對該目錄有讀權限。
【小技巧】 推薦的方式是先在普通模式下使用Apollo,這樣Apollo會自動創建該目錄並在目錄下生成配置文件。
5.2.2 本地配置文件
本地配置文件需要按照一定的文件名格式放置於本地配置目錄下,文件名格式如下:
{appId}+{cluster}+{namespace}.properties
- appId就是應用自己的appId,如100004458
- cluster就是應用使用的集群,一般在本地模式下沒有做過配置的話,就是default
- namespace就是應用使用的配置namespace,一般是application

文件內容以properties格式存儲,比如如果有兩個key,一個是request.timeout,另一個是batch,那么文件內容就是如下格式:
request.timeout=2000
batch=2000
5.3 修改配置
在本地開發模式下,Apollo不會實時監測文件內容是否有變化,所以如果修改了配置,需要重啟應用生效。
六、測試模式
1.1.0版本開始增加了apollo-mockserver,從而可以很好地支持單元測試時需要mock配置的場景,使用方法如下:
6.1 引入pom依賴
<dependency>
<groupId>com.ctrip.framework.apollo</groupId> <artifactId>apollo-mockserver</artifactId> <version>1.1.0</version> </dependency>
6.2 在test的resources下放置mock的數據
文件名格式約定為mockdata-{namespace}.properties

6.3 寫測試類
更多使用demo可以參考ApolloMockServerApiTest.java和ApolloMockServerSpringIntegrationTest.java。
@RunWith(SpringJUnit4ClassRunner.class) @SpringApplicationConfiguration(classes = TestConfiguration.class) public class SpringIntegrationTest { // 啟動apollo的mockserver @ClassRule public static EmbeddedApollo embeddedApollo = new EmbeddedApollo(); @Test @DirtiesContext // 這個注解很有必要,因為配置注入會弄臟應用上下文 public void testPropertyInject(){ assertEquals("value1", testBean.key1); assertEquals("value2", testBean.key2); } @Test @DirtiesContext public void testListenerTriggeredByAdd() throws InterruptedException, ExecutionException, TimeoutException { String otherNamespace = "othernamespace"; embeddedApollo.addOrModifyPropery(otherNamespace,"someKey","someValue"); ConfigChangeEvent changeEvent = testBean.futureData.get(5000, TimeUnit.MILLISECONDS); assertEquals(otherNamespace, changeEvent.getNamespace()); assertEquals("someValue", changeEvent.getChange("someKey").getNewValue()); } @EnableApolloConfig("application") @Configuration static class TestConfiguration{ @Bean public TestBean testBean(){ return new TestBean(); } } static class TestBean{ @Value("${key1:default}") String key1; @Value("${key2:default}") String key2; SettableFuture<ConfigChangeEvent> futureData = SettableFuture.create(); @ApolloConfigChangeListener("othernamespace") private void onChange(ConfigChangeEvent changeEvent) { futureData.set(changeEvent); } } }
附:wiki:https://github.com/ctripcorp/apollo/wiki/Java%E5%AE%A2%E6%88%B7%E7%AB%AF%E4%BD%BF%E7%94%A8%E6%8C%87%E5%8D%97
