參考攜程官網提供的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
整個項目組的代碼如下
項目的pom.xml代碼如下
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.itmayiedu</groupId> <artifactId>springboot-abl</artifactId> <version>0.0.1-SNAPSHOT</version> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.0.1.RELEASE</version> <relativePath /> <!-- lookup parent from repository --> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> <spring-cloud.version>Finchley.RC1</spring-cloud.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <!-- <exclusions> <exclusion> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> </exclusion> </exclusions> --> </dependency> <!-- apollo 攜程apollo配置中心框架 --> <dependency> <groupId>com.ctrip.framework.apollo</groupId> <artifactId>apollo-client</artifactId> <version>1.7.0</version> </dependency> <dependency> <groupId>com.ctrip.framework.apollo</groupId> <artifactId>apollo-core</artifactId> <version>1.7.0</version> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.3</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>${spring-cloud.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <configuration> <source>1.8</source> <target>1.8</target> </configuration> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-resources-plugin</artifactId> <version>3.0.1</version> <executions> <execution> <id>copy-conf</id> <phase>package</phase> <goals> <goal>copy-resources</goal> </goals> <configuration> <encoding>UTF-8</encoding> <outputDirectory>${project.build.directory}/ext/conf</outputDirectory> <resources> <resource> <directory>ext/conf</directory> <includes> <include>logback.xml</include> </includes> <filtering>true</filtering> </resource> </resources> </configuration> </execution> </executions> </plugin> <plugin> <groupId>org.jacoco</groupId> <artifactId>jacoco-maven-plugin</artifactId> <version>0.7.5.201505241946</version> <executions> <execution> <id>default-prepare-agent</id> <goals> <goal>prepare-agent</goal> </goals> </execution> <execution> <id>default-prepare-agent-integration</id> <goals> <goal>prepare-agent-integration</goal> </goals> </execution> </executions> </plugin> <plugin> <groupId>com.spotify</groupId> <artifactId>docker-maven-plugin</artifactId> <version>0.4.3</version> <configuration> <imageName>hy_uav_gateway</imageName> <dockerDirectory>src/main/docker</dockerDirectory> <resources> <resource> <targetPath>/</targetPath> <directory>${project.build.directory}</directory> <include>${project.build.finalName}.jar</include> <include>ext/conf/logback.xml</include> </resource> </resources> </configuration> </plugin> </plugins> </build> <repositories> <repository> <id>spring-milestones</id> <name>Spring Milestones</name> <url>https://repo.spring.io/milestone</url> <snapshots> <enabled>false</enabled> </snapshots> </repository> </repositories> </project>
這里引入攜程的客戶端apollo-client為最新的1.7版本
環境准備:
客戶端要指定應用的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。
這里我們采用classpath:/META-INF/app.properties下面來指定應用的appID
app.properties的內容如下,app.id的值必須和apollo上面創建的應用的AppID一致
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
- 也可以通過程序指定,如
System.setProperty("apollo.meta", "http://config-service-url");
- 通過Spring Boot的配置文件
- 可以在Spring Boot的
application.properties
或bootstrap.properties
中指定apollo.meta=http://config-service-url
該配置方式不適用於多個war包部署在同一個tomcat的使用場景
- 通過操作系統的System Environment
APOLLO_META
- 可以通過操作系統的System Environment
APOLLO_META
來指定- 注意key為全大寫,且中間是
_
分隔- 通過
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
- 通過操作系統的System Environment
${ENV}_META
(1.2.0版本開始支持)
- 如果當前env是
dev
,那么用戶可以配置操作系統的System EnvironmentDEV_META=http://config-service-url
- 注意key為全大寫
- 使用該配置方式,那么就必須要正確配置Environment,詳見1.2.4.1 Environment
- 通過
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地址
這里我們采用第8鍾方式,apollo-env.properties
文件,這里支持兩種環境,一種是dev,一種是pro
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的是內網地址,本地開發環境無法直接連接
- 如果通過公網 SLB 對外暴露 Config Service的話,記得要設置 IP 白名單,避免數據泄露
- 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.properti
-
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都掛掉的情況下需要擴容,那么也可以先把配置從已有機器上的緩存路徑復制到新機器上的相同緩存路徑,我們來驗證下緩存數據
我們通過第二種方式
通過Spring Boot的配置文件
可以在Spring Boot的application.properties或bootstrap.properties中指定apollo.cacheDir=/opt/data/some-cache-dir我們來看下文件
接下來我們來看下緩存文件
可以看到應用緩存文件的命名為:weiyuantest+default+application.properties
第一個weiyuantest是對應的應用的appID
第二個default是當前應用對於的集群名稱
第三個application是當前應用的namespace
如weiyuantest+default+spring-redis.properties
第一個weiyuantest是對應的應用的appID
第二個default是當前應用對於的集群名稱
第三個spring-redis是當前應用的namespace
我們打開一個配置文件看看里面的內容weiyuantest+default+spring-rocketmq.properties,里面就是key和value的鍵值隊
#Persisted by DefaultConfig #Wed Sep 02 13:27:29 CST 2020 yushengjun=jjkfeskkjdskjf kjkjdfskj jk333333333333333333333333333337777777766666666666666666 skywalking=22222
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
這里環境里面,我們在windows環境上,我們采用最后一個推薦的方式是通過配置文件來指定env=YOUR-ENVIRONMENT
這里我們設置環境變量為DEV環境,我們來看下server.properties的內容
這里設置之后,當前的應用就只能收到dev環境下面的配置,不能收到pro環境或者其他環境的配置信息了
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
)加載配置
- 我們會從默認的集群(
這里我們可以在Spring Boot的application.properties或bootstrap.properties中指定apollo.cluster=SomeCluster,來設置集群的數據
這里我們指定當前應用訪問的集群為jiqun1,這里需要注意的是如果jiqun1中沒有對於的配置項的值,這里的配置項包括自己私有的namespace和公共的namespace,默認會訪問apollo默認的default集群會從默認的集群(
default
)加載配置,因為上面我們收到的指定了集群為jiqun1,就不會再去訪問默認的default集群了,如果沒有指定會默認訪問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=tru
- 可以在
我們來驗證下配置項的順序驗證功能
1.2.4.4 配置訪問秘鑰
適用於1.6.0及以上版本
Apollo從1.6.0版本開始增加訪問秘鑰機制,從而只有經過身份驗證的客戶端才能訪問敏感配置。如果應用開啟了訪問秘鑰,客戶端需要配置秘鑰,否則無法獲取配置。
配置方式按照優先級從高到低分別為:
- 通過Java System Property
apollo.accesskey.secret
- 可以通過Java的System Property
apollo.accesskey.secret
來指定 - 在Java程序啟動腳本中,可以指定
-Dapollo.accesskey.secret=1cf998c4e2ad4704b45a98a509d15719
- 如果是運行jar文件,需要注意格式是
java -Dapollo.accesskey.secret=1cf998c4e2ad4704b45a98a509d15719 -jar xxx.jar
- 如果是運行jar文件,需要注意格式是
- 也可以通過程序指定,如
System.setProperty("apollo.accesskey.secret", "1cf998c4e2ad4704b45a98a509d15719");
- 可以通過Java的System Property
- 通過Spring Boot的配置文件
- 可以在Spring Boot的
application.properties
或bootstrap.properties
中指定apollo.accesskey.secret=1cf998c4e2ad4704b45a98a509d15719
- 可以在Spring Boot的
- 通過操作系統的System Environment
- 還可以通過操作系統的System Environment
APOLLO_ACCESSKEY_SECRET
來指定 - 注意key為全大寫
- 還可以通過操作系統的System Environment
- 通過
app.properties
配置文件- 可以在
classpath:/META-INF/app.properties
指定apollo.accesskey.secret=1cf998c4e2ad4704b45a98a509d15719
- 可以在
首先我們需要在apollo的管理上設置秘鑰
這里會生成對於的秘鑰,接下來
通過Spring Boot的配置文件
可以在Spring Boot的application.properties或bootstrap.properties中指定apollo.accesskey.secret=1cf998c4e2ad4704b45a98a509d15719我們在客戶端看看對於的application.yml中添加對於的配置項
server: port: 8001 spring: application: name: springboot-abl eureka: client: service-url: defaultZone: http://localhost:8080/eureka apollo: cacheDir: d:\cache-dir cluster: default accesskey: secret: 5b02a3e3a9ee4a72aefe3786fc02b2fe property: order: enable: true
這樣客戶端訪問的時候就能夠獲得對於的配置項了,如果不攜帶token就會有問題
Spring Boot集成方式(推薦)
使用方式很簡單,只需要在application.properties/bootstrap.properties中按照如下樣例配置即可。
- 注入默認
application
namespace的配置示例
# will inject 'application' namespace in bootstrap phase apollo.bootstrap.enabled = true
- 注入非默認
application
namespace或多個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
我們來看下具體的配置server: port: 8001 spring: application: name: springboot-abl eureka: client: service-url: defaultZone: http://localhost:8080/eureka apollo: cacheDir: d:\cache-dir cluster: default bootstrap: enabled: true namespaces: application,spring-rocketmq,spring-redis accesskey: secret: 5b02a3e3b9ee4a72aefe3786fc02b2fe1 property: order: enable: true
我們來看下對於的代碼
/** * 功能說明: * 功能作者: * 創建日期: * 版權歸屬:每特教育|螞蟻課堂所有 www.itmayiedu.com */ package com.itmayiedu.api.controller; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import com.ctrip.framework.apollo.spring.annotation.EnableApolloConfig; /** * 功能說明: <br> * 創建作者:每特教育-余勝軍<br> * 創建時間:2018年8月28日 下午9:09:14<br> * 教育機構:每特教育|螞蟻課堂<br> * 版權說明:上海每特教育科技有限公司版權所有<br> * 官方網站:www.itmayiedu.com|www.meitedu.com<br> * 聯系方式:qq644064779<br> * 注意:本內容有每特教育學員共同研發,請尊重原創版權 */ //@EnableApolloConfig({"application", "spring-rocketmq","spring-redis"}) @EnableApolloConfig @SpringBootApplication public class App { public static void main(String[] args) { SpringApplication.run(App.class, args); } }
/** * 功能說明: * 功能作者: * 創建日期: * 版權歸屬:每特教育|螞蟻課堂所有 www.itmayiedu.com */ package com.itmayiedu.api.controller; import org.springframework.beans.factory.annotation.Value; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; /** * 功能說明: <br> * 創建作者:每特教育-余勝軍<br> * 創建時間:2018年8月28日 下午9:07:25<br> * 教育機構:每特教育|螞蟻課堂<br> * 版權說明:上海每特教育科技有限公司版權所有<br> * 官方網站:www.itmayiedu.com|www.meitedu.com<br> * 聯系方式:qq644064779<br> * 注意:本內容有每特教育學員共同研發,請尊重原創版權 */ @RestController public class IndexController { @Value("${yushengjun:test}") private String yushengjun; @RequestMapping("/getYushengjun") public String getYushengjun() { return yushengjun; } }
我們在配置文件中配置了 apollo.bootstrap.namespaces = application,spring-rocketmq,spring-redis,用來監聽application,spring-rocketmq,spring-redis這幾個namespace的值,這里一定要注意配置要生效一定要將 apollo.bootstrap設置為true
除了在application.yml中指定監聽namespace的值之外,我們還可以在在啟動類中使用下面的方式指定監聽類
@EnableApolloConfig({"application", "spring-rocketmq","spring-redis"}) @SpringBootApplication public class App { public static void main(String[] args) { SpringApplication.run(App.class, args); } }
在IndexController中我們使用,代碼中直接使用@Value("${yushengjun:test}")來獲得
yushengjun配置的值, 從v0.10.0開始的版本支持 @Value("${yushengjun:test}") - 通過Java System Property
-
在運行時自動更新
我們來驗證下我們的功能
整個應用的application.yml為
server: port: 8001 spring: application: name: springboot-abl eureka: client: service-url: defaultZone: http://localhost:8080/eureka apollo: cacheDir: d:\cache-dir cluster: default bootstrap: enabled: true namespaces: application,spring-rocketmq,spring-redis accesskey: secret: 5b02a3e3b9ee4a72aefe3786fc02b2fe1 property: order: enable: true
當前應用環境為dev環境,監控的集群是default集群,監控的namespace為application,spring-rocketmq,spring-redis
我們在,spring-rocketmq這個namespace下面修改了屬性yushengjun的值,訪問應用IndexController提供的端口,能夠實時的更新配置的值
第二個注意點:
這里spring-rocketmq是一個私有的namespace,spring-redis是一個公共的namespace,里面都配置了yushengjun這個屬性值,訪問的時候
以私有的namespace的屬性值有效
上面的配置中在應用weiyuantest下面有一個公共的namespace配置spring-redis,對於公共的namespace下面的值,其他應用的任何環境都是可以訪問的
我們新建一個test22的應用來測試下
我們在weiyuantest應用下面修改了公共namespace的spring-redis下面yushengjun的值,訪問test22應用能立刻獲得公共的namespace的值,這里要注意
如果當前應用test22的私有的namespace下面存在yushengjun的值,那么就不會訪問公共的spring-redis下面的值
接下來對於集群的驗證
我們應用訪問jiqun1下面的yushengjun的值,首先會訪問jiqun1下面全部的私有namespace看是否存在yushengjun的值,如果沒有就訪問公共的namespace,如果公共的namespace的值都沒有就直接返回。這里集群之間的配置是隔離的這里如果其他集群例如jiqun2里面存在yushengjun的值,這里也不會去訪問的
這里還有一種情況,集群jiqun1下面application和spring-rocketmq兩個私有的namespace都設置了yushengjun的值,訪問的時候以哪個為准了
通過驗證和應用配置namespace的順序和關系,如下圖所示
上面application的順序高於spring-rocketmq的,就以application的值優先,如果spring-rocketmq的順序高於application的,就以spring-rocketmq的為准
.注入多個namespace,並且指定順序Spring的配置是有順序的,如果多個property source都有同一個key,那么最終是順序在前的配置生效。
注意點6:
/** * 功能說明: * 功能作者: * 創建日期: * 版權歸屬:每特教育|螞蟻課堂所有 www.itmayiedu.com */ package com.itmayiedu.api.controller; import org.springframework.beans.factory.annotation.Value; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; /** * 功能說明: <br> * 創建作者:每特教育-余勝軍<br> * 創建時間:2018年8月28日 下午9:07:25<br> * 教育機構:每特教育|螞蟻課堂<br> * 版權說明:上海每特教育科技有限公司版權所有<br> * 官方網站:www.itmayiedu.com|www.meitedu.com<br> * 聯系方式:qq644064779<br> * 注意:本內容有每特教育學員共同研發,請尊重原創版權 */ @RestController public class IndexController { @Value("${yushengjun:test}") private String yushengjun; @RequestMapping("/getYushengjun") public String getYushengjun() { return yushengjun; } }
默認情況下
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:
@Value("${timeout:100}")能夠實現一個屬性值的更新,如果我們有多個屬性值都需要更新,如果寫多次@Value("${timeout:100}")比較麻煩,
我們可以把屬性封裝成一個對象,同一來進行處理
假設我有一個TestJavaConfigBean,通過Java Config的方式還可以使用@Value的方式注入:
package com.itmayiedu.api.controller; import org.springframework.beans.factory.annotation.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; } }
然后在啟動類中按照下面的方式注入bean
/** * 功能說明: * 功能作者: * 創建日期: * 版權歸屬:每特教育|螞蟻課堂所有 www.itmayiedu.com */ package com.itmayiedu.api.controller; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.Bean; import com.ctrip.framework.apollo.spring.annotation.EnableApolloConfig; /** * 功能說明: <br> * 創建作者:每特教育-余勝軍<br> * 創建時間:2018年8月28日 下午9:09:14<br> * 教育機構:每特教育|螞蟻課堂<br> * 版權說明:上海每特教育科技有限公司版權所有<br> * 官方網站:www.itmayiedu.com|www.meitedu.com<br> * 聯系方式:qq644064779<br> * 注意:本內容有每特教育學員共同研發,請尊重原創版權 */ @EnableApolloConfig({"application", "spring-rocketmq","spring-redis"}) @SpringBootApplication public class App { public static void main(String[] args) { SpringApplication.run(App.class, args); } @Bean public TestJavaConfigBean javaConfigBean() { return new TestJavaConfigBean(); } }
我們修改下IndexController
/** * 功能說明: * 功能作者: * 創建日期: * 版權歸屬:每特教育|螞蟻課堂所有 www.itmayiedu.com */ package com.itmayiedu.api.controller; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; /** * 功能說明: <br> * 創建作者:每特教育-余勝軍<br> * 創建時間:2018年8月28日 下午9:07:25<br> * 教育機構:每特教育|螞蟻課堂<br> * 版權說明:上海每特教育科技有限公司版權所有<br> * 官方網站:www.itmayiedu.com|www.meitedu.com<br> * 聯系方式:qq644064779<br> * 注意:本內容有每特教育學員共同研發,請尊重原創版權 */ @RestController public class IndexController { @Value("${yushengjun:test}") private String yushengjun; @Autowired private TestJavaConfigBean bean; @RequestMapping("/getYushengjun") public String getYushengjun() { return yushengjun; } @RequestMapping("/test") public String getYushengjun2() { return bean.getTimeout()+""; } }
當我們在配置中心頁面上面修改了配置之后,訪問http://localhost:8001/test,我們能夠實時的得到配置變更的值
注意4:
使用@ConfigurationProperties 可以替換@value,但是需要注意的是,@ConfigurationProperties如果需要在Apollo配置變化時自動更新注入的值,需要配合使用EnvironmentChangeEvent或RefreshScope
我們來看下具體的代碼
首先我們定義一個配置類
package com.itmayiedu.api.controller; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.cloud.context.config.annotation.RefreshScope; import org.springframework.context.annotation.Configuration; @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; } public int getExpireSeconds() { return expireSeconds; } public int getCommandTimeout() { return commandTimeout; } }
這里的配置類SampleRedisConfig一定要能夠被springboot啟動類掃描到,默認springboot掃描啟動類及其子包下面的配置
接下來第二個我們要增加一個配置類
package com.itmayiedu.api.controller; import com.ctrip.framework.apollo.core.ConfigConsts; import com.ctrip.framework.apollo.model.ConfigChangeEvent; import com.ctrip.framework.apollo.spring.annotation.ApolloConfigChangeListener; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.BeansException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cloud.context.environment.EnvironmentChangeEvent; import org.springframework.cloud.context.scope.refresh.RefreshScope; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.stereotype.Component; /** * apollo 自動刷新 * * @author qubianzhong * @Date 20:32 2019/11/11 */ @Component @Slf4j public class ApolloRefreshConfig implements ApplicationContextAware { @Autowired ApplicationContext applicationContext; @Autowired RefreshScope refreshScope; //application,spring-rocketmq,spring-redis //這里指定Apollo的namespace,非常重要,如果不指定,默認只使用application @ApolloConfigChangeListener(value = {ConfigConsts.NAMESPACE_APPLICATION,"application","spring-rocketmq"}) public void onChange(ConfigChangeEvent changeEvent) { for (String changedKey : changeEvent.changedKeys()) { System.err.println(changeEvent.getNamespace()+":"+changedKey+":"+changeEvent.getChange(changedKey)); log.error("apollo changed namespace:{} Key:{} value:{}", changeEvent.getNamespace(), changedKey, changeEvent.getChange(changedKey)); } refreshProperties(changeEvent); } public void refreshProperties(ConfigChangeEvent changeEvent) { this.applicationContext.publishEvent(new EnvironmentChangeEvent(changeEvent.changedKeys())); refreshScope.refreshAll(); } @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.applicationContext = applicationContext; } }
ApolloRefreshConfig這個類也是一定要能夠被springboot啟動類掃描到,默認springboot掃描啟動類及其子包下面的配置,這里有幾個注意點
@ApolloConfigChangeListener(value = {ConfigConsts.NAMESPACE_APPLICATION,"application","spring-rocketmq"}),這里要指定你要監控的
namespace
啟動類需要把配置類注入到容器中
/** * 功能說明: * 功能作者: * 創建日期: * 版權歸屬:每特教育|螞蟻課堂所有 www.itmayiedu.com */ package com.itmayiedu.api.controller; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.Bean; import com.ctrip.framework.apollo.spring.annotation.EnableApolloConfig; /** * 功能說明: <br> * 創建作者:每特教育-余勝軍<br> * 創建時間:2018年8月28日 下午9:09:14<br> * 教育機構:每特教育|螞蟻課堂<br> * 版權說明:上海每特教育科技有限公司版權所有<br> * 官方網站:www.itmayiedu.com|www.meitedu.com<br> * 聯系方式:qq644064779<br> * 注意:本內容有每特教育學員共同研發,請尊重原創版權 */ @EnableApolloConfig({"application", "spring-rocketmq","spring-redis"}) @SpringBootApplication public class App { public static void main(String[] args) { SpringApplication.run(App.class, args); } @Bean public TestJavaConfigBean javaConfigBean() { return new TestJavaConfigBean(); } @Bean public SampleRedisConfig sampleRedisConfig() { return new SampleRedisConfig(); } }
我們來看下調用的接口
/** * 功能說明: * 功能作者: * 創建日期: * 版權歸屬:每特教育|螞蟻課堂所有 www.itmayiedu.com */ package com.itmayiedu.api.controller; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; /** * 功能說明: <br> * 創建作者:每特教育-余勝軍<br> * 創建時間:2018年8月28日 下午9:07:25<br> * 教育機構:每特教育|螞蟻課堂<br> * 版權說明:上海每特教育科技有限公司版權所有<br> * 官方網站:www.itmayiedu.com|www.meitedu.com<br> * 聯系方式:qq644064779<br> * 注意:本內容有每特教育學員共同研發,請尊重原創版權 */ @RestController public class IndexController { @Value("${yushengjun:test}") private String yushengjun; @Autowired private TestJavaConfigBean bean; @Autowired private SampleRedisConfig bean2; @RequestMapping("/getYushengjun") public String getYushengjun() { return yushengjun; } @RequestMapping("/test") public String getYushengjun2() { return bean.getTimeout()+""; } @RequestMapping("/test2") public String getYushengjun22() { return bean2.getCommandTimeout()+""; } }
配置中心頁面為
上圖簡要描述了Apollo客戶端的實現原理:
- 客戶端和服務端保持了一個長連接,從而能第一時間獲得配置更新的推送。(通過Http Long Polling實現)
- 客戶端還會定時從Apollo配置中心服務端拉取應用的最新配置。
- 這是一個fallback機制,為了防止推送機制失效導致配置不更新
- 客戶端定時拉取會上報本地版本,所以一般情況下,對於定時拉取的操作,服務端都會返回304 - Not Modified
- 定時頻率默認為每5分鍾拉取一次,客戶端也可以通過在運行時指定System Property:
apollo.refreshInterval
來覆蓋,單位為分鍾。
- 客戶端從Apollo配置中心服務端獲取到應用的最新配置后,會保存在內存中
- 客戶端會把從服務端獲取到的配置在本地文件系統緩存一份
- 在遇到服務不可用,或網絡不通的時候,依然能從本地恢復配置
- 應用程序可以從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不會實時監測文件內容是否有變化,所以如果修改了配置,需要重啟應用生效。
我們來驗證下,第一步修改server.properties的內容
env=Local
接下來修改緩存文件,我的緩存文件目錄位於D:\cache-dir\weiyuantest\config-cache目錄下
這里集群為jiqun1,對於的namespace為application、spring-rocketmq,所以這里修改緩存配置weiyuantest+jiqun1+application.properties
這樣修改完成就可以了,應用就只用本地緩存的配置文件
接下來我們來驗證下面的一個功能:配置中心Apollo存儲加密字段
應用中需要加入jasypt的依賴
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.itmayiedu</groupId> <artifactId>springboot-abl</artifactId> <version>0.0.1-SNAPSHOT</version> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.0.1.RELEASE</version> <relativePath /> <!-- lookup parent from repository --> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> <spring-cloud.version>Finchley.RC1</spring-cloud.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <!-- <exclusions> <exclusion> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> </exclusion> </exclusions> --> </dependency> <!-- apollo 攜程apollo配置中心框架 --> <dependency> <groupId>com.ctrip.framework.apollo</groupId> <artifactId>apollo-client</artifactId> <version>1.7.0</version> </dependency> <dependency> <groupId>com.ctrip.framework.apollo</groupId> <artifactId>apollo-core</artifactId> <version>1.7.0</version> </dependency> <!--jasypt加密 --> <dependency> <groupId>com.github.ulisesbocchio</groupId> <artifactId>jasypt-spring-boot-starter</artifactId> <version>1.16</version> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.3</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>${spring-cloud.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <configuration> <source>1.8</source> <target>1.8</target> </configuration> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-resources-plugin</artifactId> <version>3.0.1</version> <executions> <execution> <id>copy-conf</id> <phase>package</phase> <goals> <goal>copy-resources</goal> </goals> <configuration> <encoding>UTF-8</encoding> <outputDirectory>${project.build.directory}/ext/conf</outputDirectory> <resources> <resource> <directory>ext/conf</directory> <includes> <include>logback.xml</include> </includes> <filtering>true</filtering> </resource> </resources> </configuration> </execution> </executions> </plugin> <plugin> <groupId>org.jacoco</groupId> <artifactId>jacoco-maven-plugin</artifactId> <version>0.7.5.201505241946</version> <executions> <execution> <id>default-prepare-agent</id> <goals> <goal>prepare-agent</goal> </goals> </execution> <execution> <id>default-prepare-agent-integration</id> <goals> <goal>prepare-agent-integration</goal> </goals> </execution> </executions> </plugin> <plugin> <groupId>com.spotify</groupId> <artifactId>docker-maven-plugin</artifactId> <version>0.4.3</version> <configuration> <imageName>hy_uav_gateway</imageName> <dockerDirectory>src/main/docker</dockerDirectory> <resources> <resource> <targetPath>/</targetPath> <directory>${project.build.directory}</directory> <include>${project.build.finalName}.jar</include> <include>ext/conf/logback.xml</include> </resource> </resources> </configuration> </plugin> </plugins> </build> <repositories> <repository> <id>spring-milestones</id> <name>Spring Milestones</name> <url>https://repo.spring.io/milestone</url> <snapshots> <enabled>false</enabled> </snapshots> </repository> </repositories> </project>
接下來需要在應用的application.yml中進行設置
server: port: 8001 spring: application: name: springboot-abl apollo: cacheDir: d:\cache-dir cluster: jiqun1 bootstrap: enabled: true namespaces: application,spring-rocketmq,spring-redis accesskey: secret: 5b02a3e3b9ee4a72aefe3786fc02b2fe1 refreshInterval: 1 property: order: enable: true jasypt: encryptor: password: yinjihaunkey
上面配置的就是加密和解密的key,jasypt.encryptor.password:配置加密的Key
創建一個加密的工具類,用於加密配置:
package com.itmayiedu.api.controller; import java.io.ByteArrayOutputStream; import java.io.PrintStream; import java.util.HashMap; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.jasypt.intf.cli.JasyptPBEStringEncryptionCLI; import com.ctrip.framework.apollo.core.utils.StringUtils; public class EncryptUtil { /** * 制表符、空格、換行符 PATTERN */ private static Pattern BLANK_PATTERN = Pattern.compile("\\s*|\t|\r|\n"); /** * 加密Key */ private static String PASSWORD = "yinjihaunkey"; /** * 加密算法 */ private static String ALGORITHM = "PBEWithMD5AndDES"; public static Map<String, String> getEncryptedParams(String input) { //輸出流 ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(1024); PrintStream cacheStream = new PrintStream(byteArrayOutputStream); //更換數據輸出位置 System.setOut(cacheStream); //加密參數組裝 String[] args = {"input=" + input, "password=" + PASSWORD, "algorithm=" + ALGORITHM}; JasyptPBEStringEncryptionCLI.main(args); //執行加密后的輸出 String message = byteArrayOutputStream.toString(); String str = replaceBlank(message); int index = str.lastIndexOf("-"); //返回加密后的數據 Map<String, String> result = new HashMap<String, String>(); result.put("input", str.substring(index + 1)); result.put("password", PASSWORD); return result; } /** * 替換制表符、空格、換行符 * * @param str * @return */ private static String replaceBlank(String str) { String dest = ""; if (!StringUtils.isEmpty(str)) { Matcher matcher = BLANK_PATTERN.matcher(str); dest = matcher.replaceAll(""); } return dest; } public static void main(String[] args) { System.out.println(getEncryptedParams("hello2222222222222")); } }
我們運行可以看到明文hello2222222222222使用秘鑰yinjihaunkey加密之后的密文變為了A8RyPX5rqBuQ6O/CSY7GF5kTUqfhZiylVJ6JFdNdyxE=
{input=A8RyPX5rqBuQ6O/CSY7GF5kTUqfhZiylVJ6JFdNdyxE=, password=yinjihaunkey}
nput就是hello2222222222222加密之后的內容,將input的值復制存儲到Apollo中,存儲的格式需要按照一定的規則才行:
ENC(A8RyPX5rqBuQ6O/CSY7GF5kTUqfhZiylVJ6JFdNdyxE=)
需要將加密的內容用ENC包起來,這樣jasypt才會去解密這個值。
其它地方的代碼不需要做任何修改
@Value("${yushengjun:test}")
private String yushengjun;
在配置中心下發ENC(A8RyPX5rqBuQ6O/CSY7GF5kTUqfhZiylVJ6JFdNdyxE=)這個配置項之后,應用收到ENC(A8RyPX5rqBuQ6O/CSY7GF5kTUqfhZiylVJ6JFdNdyxE=)這個配置項,因為這個配置項是ENC開頭的,jasypt框架就會使用application.yml里面jasypt:encryptor: password這個秘鑰來解密,應用使用的是明文
我們來看看配置中心
配置項在數據庫中也是以密文的形式保存的
apollo對日志級別的動態調整
編寫一個配置類,這個類一定也要被springboot的配置類掃描到
package com.itmayiedu.api.controller; import com.ctrip.framework.apollo.Config; import com.ctrip.framework.apollo.model.ConfigChange; import com.ctrip.framework.apollo.model.ConfigChangeEvent; import com.ctrip.framework.apollo.spring.annotation.ApolloConfig; import com.ctrip.framework.apollo.spring.annotation.ApolloConfigChangeListener; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.logging.LogLevel; import org.springframework.boot.logging.LoggingSystem; import org.springframework.context.annotation.Configuration; import org.springframework.util.StringUtils; import java.util.Set; /** * @author ChengJianSheng * @date 2019-05-31 */ @Slf4j @Configuration public class LoggerConfig { private static final String LOGGER_TAG = "logging.level."; /** * 注入默認的命名空間配置 */ @ApolloConfig private Config config; @Autowired private LoggingSystem loggingSystem; @ApolloConfigChangeListener private void onChange(ConfigChangeEvent configChangeEvent) { System.out.println("配置發生變化"); System.out.println("Changes for namespace " + configChangeEvent.getNamespace()); for (String key : configChangeEvent.changedKeys()) { ConfigChange change = configChangeEvent.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())); } Set<String> keyNames = config.getPropertyNames(); for (String key : keyNames) { if (StringUtils.isEmpty(key)) { continue; } if (!key.startsWith(LOGGER_TAG)) { continue; } String loggerName = key.replace(LOGGER_TAG, ""); String strLevel = config.getProperty(key, "info"); LogLevel level = LogLevel.valueOf(strLevel.toUpperCase()); loggingSystem.setLogLevel(loggerName, level); log.info("{}:{}", key, strLevel); } } }
實現起來超級簡單,使用spring boot自帶的LoggingSystem的api來動態設置日志級別,當然的項目需要提供動態調整的接口,來達動態調整的目的,這里注意的是 @ApolloConfigChangeListener監聽的是默認的namespace的是application,這里一定要注意下
怎么配置?
和在spring環境下正常配置日志級別一樣配置即可,如
logging.level.org.springframework = info logging.level.com.yudianbank.sales = debug logging.level.org.hibernate = info
如上代表spring體系工具開啟INFO級別日志,業務系統開啟DEBUG級別日志,hibernate開啟INFO級別日志
然后在application.yml的配置文件中一定要增加下面的配置
從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
整個配置文件如下
server: port: 8001 spring: application: name: springboot-abl apollo: cacheDir: d:\cache-dir cluster: jiqun1 bootstrap: enabled: true namespaces: application,spring-rocketmq,spring-redis eagerLoad: enabled: true accesskey: secret: 5b02a3e3b9ee4a72aefe3786fc02b2fe1 refreshInterval: 1 property: order: enable: true jasypt: encryptor: password: yinjihaunkey
我們來模擬下日志的打印
/** * 功能說明: * 功能作者: * 創建日期: * 版權歸屬:每特教育|螞蟻課堂所有 www.itmayiedu.com */ package com.itmayiedu.api.controller; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import lombok.extern.slf4j.Slf4j; /** * 功能說明: <br> * 創建作者:每特教育-余勝軍<br> * 創建時間:2018年8月28日 下午9:07:25<br> * 教育機構:每特教育|螞蟻課堂<br> * 版權說明:上海每特教育科技有限公司版權所有<br> * 官方網站:www.itmayiedu.com|www.meitedu.com<br> * 聯系方式:qq644064779<br> * 注意:本內容有每特教育學員共同研發,請尊重原創版權 */ @Slf4j @RestController public class IndexController { @Value("${yushengjun:test}") private String yushengjun; @Autowired private TestJavaConfigBean bean; @Autowired private SampleRedisConfig bean2; @RequestMapping("/getYushengjun") public String getYushengjun() { log.info("123456hjhhjhjhj"); log.debug("123456hjhhjhjhj"); log.warn("123456hjhhjhjhj"); log.error("123456hjhhjhjhj"); return yushengjun; } @RequestMapping("/test") public String getYushengjun2() { return bean.getTimeout()+""; } @RequestMapping("/test2") public String getYushengjun22() { return bean2.getCommandTimeout()+""; } }
我們在配置中心的頁面上進行調整
如果要調整hibernate的日志打印,只需要寫成
logging.level.org.hibernate = info
這樣就可以了
apollo的回滾發布機制:
如果發現已發布的配置有問題,可以通過點擊『回滾』按鈕來將客戶端讀取到的配置回滾到上一個發布版本。
這里的回滾機制類似於發布系統,發布系統中的回滾操作是將部署到機器上的安裝包回滾到上一個部署的版本,但代碼倉庫中的代碼是不會回滾的,從而開發可以在修復代碼后重新發布。
Apollo中的回滾也是類似的機制,點擊回滾后是將發布到客戶端的配置回滾到上一個已發布版本,也就是說客戶端讀取到的配置會恢復到上一個版本,但頁面上編輯狀態的配置是不會回滾的,從而開發可以在修復配置后重新發布。
這里當前的日志級別的值是info,回退到上一版本的話就是error,回滾之后客戶端讀取的值是error,但是配置中心的頁面上顯示的還是info,效果如下
apollo安全權限的設置:
1.2 項目權限分配
1.2.1 項目管理員權限
項目管理員擁有以下權限:
- 可以管理項目的權限分配
- 可以創建集群
- 可以創建Namespace
創建項目時填寫的應用負責人默認會成為項目的管理員之一,如果還需要其他人也成為項目管理員,可以按照下面步驟操作:
-
點擊頁面左側的“管理項目”
-
搜索需要添加的成員並點擊添加
1.2.2 配置編輯、發布權限
配置權限分為編輯和發布:
- 編輯權限允許用戶在Apollo界面上創建、修改、刪除配置
- 配置修改后只在Apollo界面上變化,不會影響到應用實際使用的配置
- 發布權限允許用戶在Apollo界面上發布、回滾配置
- 配置只有在發布、回滾動作后才會被應用實際使用到
- Apollo在用戶操作發布、回滾動作后實時通知到應用,並使最新配置生效
項目創建完,默認沒有分配配置的編輯和發布權限,需要項目管理員進行授權。
-
點擊application這個namespace的授權按鈕
-
分配修改權限
-
分配發布權限
Apollo 默認允許所有登錄用戶創建項目,如果只允許部分用戶創建項目,可以開啟創建項目權限控制
11. role.manage-app-master.enabled - 是否開啟項目管理員分配權限控制
適用於1.5.0及以上版本
默認為false,所有項目的管理員可以為項目添加/刪除管理員
如果設置為true,那么只有超級管理員和擁有項目管理員分配權限的帳號可以為特定項目添加/刪除管理員,超級管理員可以通過管理員工具 - 系統權限管理
給用戶分配特定項目的管理員分配權限
接下來第二個設置:
配置apollo-adminservice是否開啟訪問控制,,如果開啟之后,apollo的portal訪問的時候必須攜帶對應的token的值
現在我們配置dev環境的adminserver開啟權限訪問,需要在對於的數據庫中
apolloconfigdb-dev,如果是開啟pro環境的,需要修改apolloconfigdb-pro
接下來,需要在portal中進行配置,portal中需要攜帶上面的dev的token才能訪問dev的環境, 調整ApolloConfigDB配置
配置之后修改如下
第二個配置2:
2.1.3.2 調整ApolloConfigDB配置
我們來配置下dev環境下面,我們開啟配置一次發布只能有一個人修改,另外一個人發布的功能,避免出現配置出現問題
我們倆看下開啟之后的效果為
對於配置2:
3. config-service.cache.enabled - 是否開啟配置緩存
這是一個功能開關,如果配置為true的話,config service會緩存加載過的配置信息,從而加快后續配置獲取性能。
默認為false,開啟前請先評估總配置大小並調整config service內存配置。
開啟緩存后必須確保應用中配置的app.id大小寫正確,否則將獲取不到正確的配置
附件:和ApolloConfigDB配置相關的配置如下
2.1.3.2 調整ApolloConfigDB配置 配置項統一存儲在ApolloConfigDB.ServerConfig表中,需要注意每個環境的ApolloConfigDB.ServerConfig都需要單獨配置,修改完一分鍾實時生效。 1. eureka.service.url - Eureka服務Url 不適用於基於Kubernetes原生服務發現場景 不管是apollo-configservice還是apollo-adminservice都需要向eureka服務注冊,所以需要配置eureka服務地址。 按照目前的實現,apollo-configservice本身就是一個eureka服務,所以只需要填入apollo-configservice的地址即可,如有多個,用逗號分隔(注意不要忘了/eureka/后綴)。 需要注意的是每個環境只填入自己環境的eureka服務地址,比如FAT的apollo-configservice是1.1.1.1:8080和2.2.2.2:8080,UAT的apollo-configservice是3.3.3.3:8080和4.4.4.4:8080,PRO的apollo-configservice是5.5.5.5:8080和6.6.6.6:8080,那么: 在FAT環境的ApolloConfigDB.ServerConfig表中設置eureka.service.url為: http://1.1.1.1:8080/eureka/,http://2.2.2.2:8080/eureka/ 在UAT環境的ApolloConfigDB.ServerConfig表中設置eureka.service.url為: http://3.3.3.3:8080/eureka/,http://4.4.4.4:8080/eureka/ 在PRO環境的ApolloConfigDB.ServerConfig表中設置eureka.service.url為: http://5.5.5.5:8080/eureka/,http://6.6.6.6:8080/eureka/ 注1:這里需要填寫本環境中全部的eureka服務地址,因為eureka需要互相復制注冊信息 注2:如果希望將Config Service和Admin Service注冊到公司統一的Eureka上,可以參考部署&開發遇到的常見問題 - 將Config Service和Admin Service注冊到單獨的Eureka Server上章節 注3:在多機房部署時,往往希望config service和admin service只向同機房的eureka注冊,要實現這個效果,需要利用ServerConfig表中的cluster字段,config service和admin service會讀取所在機器的/opt/settings/server.properties(Mac/Linux)或C:\opt\settings\server.properties(Windows)中的idc屬性,如果該idc有對應的eureka.service.url配置,那么就只會向該機房的eureka注冊。比如config service和admin service會部署到SHAOY和SHAJQ兩個IDC,那么為了實現這兩個機房中的服務只向該機房注冊,那么可以在ServerConfig表中新增兩條記錄,分別填入SHAOY和SHAJQ兩個機房的eureka地址即可,default cluster的記錄可以保留,如果有config service和admin service不是部署在SHAOY和SHAJQ這兩個機房的,就會使用這條默認配置。 Key Cluster Value Comment eureka.service.url default http://1.1.1.1:8080/eureka/ 默認的Eureka服務Url eureka.service.url SHAOY http://2.2.2.2:8080/eureka/ SHAOY的Eureka服務Url eureka.service.url SHAJQ http://3.3.3.3:8080/eureka/ SHAJQ的Eureka服務Url 2. namespace.lock.switch - 一次發布只能有一個人修改開關,用於發布審核 這是一個功能開關,如果配置為true的話,那么一次配置發布只能是一個人修改,另一個發布。 生產環境建議開啟此選項 3. config-service.cache.enabled - 是否開啟配置緩存 這是一個功能開關,如果配置為true的話,config service會緩存加載過的配置信息,從而加快后續配置獲取性能。 默認為false,開啟前請先評估總配置大小並調整config service內存配置。 開啟緩存后必須確保應用中配置的app.id大小寫正確,否則將獲取不到正確的配置 4. item.key.length.limit - 配置項 key 最大長度限制 默認配置是128。 5. item.value.length.limit - 配置項 value 最大長度限制 默認配置是20000。 6. admin-service.access.control.enabled - 配置apollo-adminservice是否開啟訪問控制 適用於1.7.1及以上版本 默認為false,如果配置為true,那么apollo-portal就需要正確配置訪問該環境的access token,否則訪問會被拒絕 7. admin-service.access.tokens - 配置允許訪問apollo-adminservice的access token列表 適用於1.7.1及以上版本 如果該配置項為空,那么訪問控制不會生效。如果允許多個token,token 之間以英文逗號分隔 樣例: admin-service.access.tokens=098f6bcd4621d373cade4e832627b4f6 admin-service.access.tokens=098f6bcd4621d373cade4e832627b4f6,ad0234829205b9033196ba818f7a872b
附件:和ApolloConfigDB配置相關的配置如下
2.1.3.2 調整ApolloConfigDB配置 配置項統一存儲在ApolloConfigDB.ServerConfig表中,需要注意每個環境的ApolloConfigDB.ServerConfig都需要單獨配置,修改完一分鍾實時生效。 1. eureka.service.url - Eureka服務Url 不適用於基於Kubernetes原生服務發現場景 不管是apollo-configservice還是apollo-adminservice都需要向eureka服務注冊,所以需要配置eureka服務地址。 按照目前的實現,apollo-configservice本身就是一個eureka服務,所以只需要填入apollo-configservice的地址即可,如有多個,用逗號分隔(注意不要忘了/eureka/后綴)。 需要注意的是每個環境只填入自己環境的eureka服務地址,比如FAT的apollo-configservice是1.1.1.1:8080和2.2.2.2:8080,UAT的apollo-configservice是3.3.3.3:8080和4.4.4.4:8080,PRO的apollo-configservice是5.5.5.5:8080和6.6.6.6:8080,那么: 在FAT環境的ApolloConfigDB.ServerConfig表中設置eureka.service.url為: http://1.1.1.1:8080/eureka/,http://2.2.2.2:8080/eureka/ 在UAT環境的ApolloConfigDB.ServerConfig表中設置eureka.service.url為: http://3.3.3.3:8080/eureka/,http://4.4.4.4:8080/eureka/ 在PRO環境的ApolloConfigDB.ServerConfig表中設置eureka.service.url為: http://5.5.5.5:8080/eureka/,http://6.6.6.6:8080/eureka/ 注1:這里需要填寫本環境中全部的eureka服務地址,因為eureka需要互相復制注冊信息 注2:如果希望將Config Service和Admin Service注冊到公司統一的Eureka上,可以參考部署&開發遇到的常見問題 - 將Config Service和Admin Service注冊到單獨的Eureka Server上章節 注3:在多機房部署時,往往希望config service和admin service只向同機房的eureka注冊,要實現這個效果,需要利用ServerConfig表中的cluster字段,config service和admin service會讀取所在機器的/opt/settings/server.properties(Mac/Linux)或C:\opt\settings\server.properties(Windows)中的idc屬性,如果該idc有對應的eureka.service.url配置,那么就只會向該機房的eureka注冊。比如config service和admin service會部署到SHAOY和SHAJQ兩個IDC,那么為了實現這兩個機房中的服務只向該機房注冊,那么可以在ServerConfig表中新增兩條記錄,分別填入SHAOY和SHAJQ兩個機房的eureka地址即可,default cluster的記錄可以保留,如果有config service和admin service不是部署在SHAOY和SHAJQ這兩個機房的,就會使用這條默認配置。 Key Cluster Value Comment eureka.service.url default http://1.1.1.1:8080/eureka/ 默認的Eureka服務Url eureka.service.url SHAOY http://2.2.2.2:8080/eureka/ SHAOY的Eureka服務Url eureka.service.url SHAJQ http://3.3.3.3:8080/eureka/ SHAJQ的Eureka服務Url 2. namespace.lock.switch - 一次發布只能有一個人修改開關,用於發布審核 這是一個功能開關,如果配置為true的話,那么一次配置發布只能是一個人修改,另一個發布。 生產環境建議開啟此選項 3. config-service.cache.enabled - 是否開啟配置緩存 這是一個功能開關,如果配置為true的話,config service會緩存加載過的配置信息,從而加快后續配置獲取性能。 默認為false,開啟前請先評估總配置大小並調整config service內存配置。 開啟緩存后必須確保應用中配置的app.id大小寫正確,否則將獲取不到正確的配置 4. item.key.length.limit - 配置項 key 最大長度限制 默認配置是128。 5. item.value.length.limit - 配置項 value 最大長度限制 默認配置是20000。 6. admin-service.access.control.enabled - 配置apollo-adminservice是否開啟訪問控制 適用於1.7.1及以上版本 默認為false,如果配置為true,那么apollo-portal就需要正確配置訪問該環境的access token,否則訪問會被拒絕 7. admin-service.access.tokens - 配置允許訪問apollo-adminservice的access token列表 適用於1.7.1及以上版本 如果該配置項為空,那么訪問控制不會生效。如果允許多個token,token 之間以英文逗號分隔 樣例: admin-service.access.tokens=098f6bcd4621d373cade4e832627b4f6 admin-service.access.tokens=098f6bcd4621d373cade4e832627b4f6,ad0234829205b9033196ba818f7a872b
附件:和ApolloPortalDB配置相關的配置如下
Apollo自身的一些配置是放在數據庫里面的,所以需要針對實際情況做一些調整。 以下配置除了支持在數據庫中配置以外,也支持通過-D參數、application.properties等配置,且-D參數、application.properties等優先級高於數據庫中的配置 2.1.3.1 調整ApolloPortalDB配置 配置項統一存儲在ApolloPortalDB.ServerConfig表中,也可以通過管理員工具 - 系統參數頁面進行配置,無特殊說明則修改完一分鍾實時生效。 1. apollo.portal.envs - 可支持的環境列表 默認值是dev,如果portal需要管理多個環境的話,以逗號分隔即可(大小寫不敏感),如: DEV,FAT,UAT,PRO 修改完需要重啟生效。 注1:一套Portal可以管理多個環境,但是每個環境都需要獨立部署一套Config Service、Admin Service和ApolloConfigDB,具體請參考:2.1.2 創建ApolloConfigDB,2.1.3.2 調整ApolloConfigDB配置,2.2.1.1.2 配置數據庫連接信息,另外如果是為已經運行了一段時間的Apollo配置中心增加環境,別忘了參考2.1.2.1 從別的環境導入ApolloConfigDB的項目數據對新的環境做初始化。 注2:只在數據庫添加環境是不起作用的,還需要為apollo-portal添加新增環境對應的meta server地址,具體參考:2.2.1.1.2.4 配置apollo-portal的meta service信息。apollo-client在新的環境下使用時也需要做好相應的配置,具體參考:1.2.2 Apollo Meta Server。 注3:如果希望添加自定義的環境名稱,具體步驟可以參考Portal如何增加環境。 注4:1.1.0版本增加了系統信息頁面(管理員工具 -> 系統信息),可以通過該頁面檢查配置是否正確 2. apollo.portal.meta.servers - 各環境Meta Service列表 適用於1.6.0及以上版本 Apollo Portal需要在不同的環境訪問不同的meta service(apollo-configservice)地址,所以我們需要在配置中提供這些信息。默認情況下,meta service和config service是部署在同一個JVM進程,所以meta service的地址就是config service的地址。 樣例如下: { "DEV":"http://1.1.1.1:8080", "FAT":"http://apollo.fat.xxx.com", "UAT":"http://apollo.uat.xxx.com", "PRO":"http://apollo.xxx.com" } 修改完需要重啟生效。 該配置優先級高於其它方式設置的Meta Service地址,更多信息可以參考2.2.1.1.2.4 配置apollo-portal的meta service信息。 3. organizations - 部門列表 Portal中新建的App都需要選擇部門,所以需要在這里配置可選的部門信息,樣例如下: [{"orgId":"TEST1","orgName":"樣例部門1"},{"orgId":"TEST2","orgName":"樣例部門2"}] 4. superAdmin - Portal超級管理員 超級管理員擁有所有權限,需要謹慎設置。 如果沒有接入自己公司的SSO系統的話,可以先暫時使用默認值apollo(默認用戶)。等接入后,修改為實際使用的賬號,多個賬號以英文逗號分隔(,)。 5. consumer.token.salt - consumer token salt 如果會使用開放平台API的話,可以設置一個token salt。如果不使用,可以忽略。 6. wiki.address portal上“幫助”鏈接的地址,默認是Apollo github的wiki首頁,可自行設置。 7. admin.createPrivateNamespace.switch 是否允許項目管理員創建private namespace。設置為true允許創建,設置為false則項目管理員在頁面上看不到創建private namespace的選項。了解更多Namespace 8. emergencyPublish.supported.envs 配置允許緊急發布的環境列表,多個env以英文逗號分隔。 當config service開啟一次發布只能有一個人修改開關(namespace.lock.switch)后,一次配置發布只能是一個人修改,另一個發布。為了避免遇到緊急情況時(如非工作時間、節假日)無法發布配置,可以配置此項以允許某些環境可以操作緊急發布,即同一個人可以修改並發布配置。 9. configView.memberOnly.envs 只對項目成員顯示配置信息的環境列表,多個env以英文逗號分隔。 對設定了只對項目成員顯示配置信息的環境,只有該項目的管理員或擁有該namespace的編輯或發布權限的用戶才能看到該私有namespace的配置信息和發布歷史。公共namespace始終對所有用戶可見。 從1.1.0版本開始支持,詳見PR 1531 10. role.create-application.enabled - 是否開啟創建項目權限控制 適用於1.5.0及以上版本 默認為false,所有用戶都可以創建項目 如果設置為true,那么只有超級管理員和擁有創建項目權限的帳號可以創建項目,超級管理員可以通過管理員工具 - 系統權限管理給用戶分配創建項目權限 11. role.manage-app-master.enabled - 是否開啟項目管理員分配權限控制 適用於1.5.0及以上版本 默認為false,所有項目的管理員可以為項目添加/刪除管理員 如果設置為true,那么只有超級管理員和擁有項目管理員分配權限的帳號可以為特定項目添加/刪除管理員,超級管理員可以通過管理員工具 - 系統權限管理給用戶分配特定項目的管理員分配權限 12. admin-service.access.tokens - 設置apollo-portal訪問各環境apollo-adminservice所需的access token 適用於1.7.1及以上版本 如果對應環境的apollo-adminservice開啟了訪問控制,那么需要在此配置apollo-portal訪問該環境apollo-adminservice所需的access token,否則會訪問失敗 格式為json,如下所示: { "dev" : "098f6bcd4621d373cade4e832627b4f6", "pro" : "ad0234829205b9033196ba818f7a872b" }