配置中心是大部分服务的强需求。自己运维一套会各种踩坑。现在云上的TSE已经支持了Apollo的托管,一键部署~ 还不心动么?这篇文章,我来介绍一下如何使用Java Spring Framework接入Apollo
入门教学Demo
场景描述
- 从Apollo读取简单的Key-Value配置
- 从Apollo读取给予yaml/yml格式的Key-Value配置
- 从Apollo读取Key-Value配置,并映射到Java Class的属性中
Apollo配置中心状态
请在Apollo配置中心做如下配置:
- 在默认namespace - "application"下 创建简单的Key-Value配置
- 在同一个App.ID下创建另一个私有的namespace 并且是yaml/yml格式。并写一个简单的yaml/yml格式的Key-Value配置
Pom 依赖列表
代码语言:javascript复制 <dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.ctrip.framework.apollo</groupId>
<artifactId>apollo-client</artifactId>
<version>1.8.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.ctrip.framework.apollo</groupId>
<artifactId>apollo-mockserver</artifactId>
<version>1.8.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
spring.properties 属性文件内容
代码语言:javascript复制app.id=100001
apollo.meta=http://172.17.16.14:8080
apollo.cluster=default
apollo.bootstrap.enabled=true
apollo.bootstrap.namespaces = name.yaml
# namespace application will always be loaded by default
创建测试程序运行时的环境变量配置
代码语言:javascript复制export ENV=dev
export APOLLO_ACCESSKEY_SECRET=**************ac407c7
代码片段
- 直接用@Value读取Key-Value配置,和Spring Framework的标准玩法一致。
// Java Config
@Configuration
@EnableApolloConfig
public class ApolloConfig {
@Value("${app.key01:unknown}")
String hello01;
@Value("${app.key02:unknown}")
String hello02;
@Value("${app.key03:unknown}")
String hello03;
@Value("${app.key04:unknown}")
String hello04;
@PostConstruct // after the values are initialized, print their values
void print() {
System.out.println(this.getClass().getName() " - " hello01);
System.out.println(this.getClass().getName() " - " hello02);
System.out.println(this.getClass().getName() " - " hello03);
System.out.println(this.getClass().getName() " - " hello04);
}
}
- 使用Bean的方式,并且用@Value对属性进行注入
@Component
@RefreshScope
public class ValuesDemoBean {
@Value("${app.key01:unknown}")
String hello01;
@Value("${app.key02:unknown}")
String hello02;
@Value("${app.key03:unknown}")
String hello03;
@Value("${app.key04:unknown}")
String hello04;
@PostConstruct // after the values are initialized, print their values
void print() {
System.out.println(this.getClass().getName() " - " hello01);
System.out.println(this.getClass().getName() " - " hello02);
System.out.println(this.getClass().getName() " - " hello03);
System.out.println(this.getClass().getName() " - " hello04);
}
}
- 映射到Java Class的属性里
@Component
@RefreshScope
@ConfigurationProperties(prefix = "app") // map object to keys with prefix
public class ObjectDemoBean {
private String key01;
private String key02;
private String key03;
private String key04;
public void setKey01(String key01) {
this.key01 = key01;
}
public void setKey02(String key02) {
this.key02 = key02;
}
public void setKey03(String key03) {
this.key03 = key03;
}
public void setKey04(String key04) {
this.key04 = key04;
}
@PostConstruct // after the values are initialized, print their values
void print() {
System.out.println(this.getClass().getName() " - " key01);
System.out.println(this.getClass().getName() " - " key02);
System.out.println(this.getClass().getName() " - " key03);
System.out.println(this.getClass().getName() " - " key04);
}
}
代码效果
所有的键值都拿到了~ Voila!
从多个App ID获取配置信息
配置服务需要允许一个服务同时从多个AppID(配置源)获取配置信息, 比如通用信息. DNS 服务器地址, 数据库连接地址. 在下面的案例中,我们演示如何从多个AppID获取配置信息。
准备一个Global可见的namespace
- 首先在另外一个AppID(100002)下,创建一个叫global-demo的namespace
- 关联新创建的namespace到之前已经存在AppID(100001)下
- 之后切换到100001 AppID的界面,可以看到配置已经被显示出来了
私有Namespace vs 公有Namespace
私有的Namespace只能对当前的AppID可见,而公有的Namespace可以被关联到多个AppID下。所以生命周期是不同的。
改进排期
请注意
- 公有namespace当前还不支持yaml/json/properties等格式,只有最基础的Key-Value Pair格式。近期会排期支持。
- @ApolloConfigChangeListener 这个Annotation必须列出所有相关的Namespace。 比如: @ApolloConfigChangeListener({ "name.yaml", "application", "TEST1.global-demo" })
代码片段
代码语言:javascript复制@Component
@RefreshScope
@ConfigurationProperties(prefix = "global.url")
public class GlobalConfigDemoBean {
private String mysql;
private String dns;
public void setMysql(String mysql) {
this.mysql = mysql;
}
public void setDns(String dns) {
this.dns = dns;
}
@PostConstruct // after the values are initialized, print their values
void print() {
System.out.println(this.getClass().getName() " - " mysql);
System.out.println(this.getClass().getName() " - " dns);
}
}
代码效果
就出来了!Fantastic!
配置自动刷新
@Value Annotation
基于@Value注入的消息会被自动更新,比如下面这个代码
代码语言:javascript复制@Configuration
@EnableApolloConfig
public class HelloConfig {
@Value("${app.key01:unknown}")
String hello01;
@Value("${app.key02:unknown}")
String hello02;
@PostConstruct // after the values are initialized, print their values
void print() {
System.out.println(this.getClass().getName() " - " hello01);
System.out.println(this.getClass().getName() " - " hello02);
}
}
用@ConfigurationProperties注入的代码
- 需要添加一个新的依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-context</artifactId>
<version>2.0.3.RELEASE</version>
</dependency>
- 然后在Bean的定义添加@RefreshScope注释
@Component
@RefreshScope
@ConfigurationProperties(prefix = "global.url")
- 添加如下的代码(@ApolloConfigChangeListener)
@Configuration
public class ApolloConfigRefreshBean implements ApplicationContextAware {
private ApplicationContext applicationContext;
@Autowired
RefreshScope refreshScope;
// all related namespaces
@ApolloConfigChangeListener({ "name.yaml", "application", "TEST1.global-demo" })
private void someChangeHandler(ConfigChangeEvent changeEvent) {
System.out.println("Apollo Auto-refresh Started");
for (String changedKey : changeEvent.changedKeys()) {
ConfigChange configChange = changeEvent.getChange(changedKey);
String oldValue = configChange.getOldValue();
String newValue = configChange.getNewValue();
System.out.printf("[changedKey:%s,oldValue=%s, newValue:%s]", changedKey, oldValue, newValue);
}
refreshProperties(changeEvent);
System.out.println("Apollo Auto-refresh done");
}
public void refreshProperties(ConfigChangeEvent changeEvent) {
//all refreshScoped and annotated with @ConfigurationProperties
this.applicationContext.publishEvent(new EnvironmentChangeEvent(changeEvent.changedKeys()));
refreshScope.refreshAll();
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
}
代码效果
键值也自动刷新了 - Ja, voll!
如何运行这个Demo
手动配置项
- 使用正确的用户名密码登录Apollo
- 创建app.id 100001。
- 创建app.id 100002。
- 在100001 appid下创建key-value键值对,比如:app.key01:hey01 - 注意创建之后,需要发布才会生效。
- 创建一个叫 "name"的私有namespace,并使用yaml格式。写一些yaml格式的内容,如下。
app:
key01: hello01
key02: hello02
- 在appid 100002下创建一个叫"global-demo"的公有 namespace,并且把它关联到appid 100001。
启动Java程序
下面的配置会加载所有在app.id 100001的namespace application下的键值。
代码语言:javascript复制app.id=100001
apollo.meta=http://[apollo-meta-server]:8080
apollo.cluster=default
apollo.bootstrap.enabled=true
如果你想加载更多的namespace,比如yaml等,需要写如下的配置 apollo.bootstrap.namespaces = name.yaml,TEST1.global-demo
代码语言:javascript复制app.id=100001
apollo.meta=http://[apollo-meta-server]:8080
apollo.cluster=default
apollo.bootstrap.enabled=true
apollo.bootstrap.namespaces = name.yaml,TEST1.global-demo
# 这里关联namespace格式是[original-app.id].[namespace]
如何Mock本地Unit Test
使用这个文档 来构建Mock Unit Test