Spring Boot 整合 Apollo 分布式配置中心

核心依赖

1
2
3
4
5
6
<!-- apollo客户端 -->
<dependency>
<groupId>com.ctrip.framework.apollo</groupId>
<artifactId>apollo-client</artifactId>
<version>1.9.2</version>
</dependency>

创建工程

创建 Spring Boot 项目 xuejian-apollo,引入 apollo 客户端及 web 依赖,完整 pom.xml 文件如下:

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
<?xml version="1.0" encoding="UTF-8"?>
<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">
<parent>
<groupId>io.github.llnancy</groupId>
<artifactId>xuejian-parent</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

<artifactId>xuejian-apollo</artifactId>

<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
</properties>

<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${springboot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>

<dependencies>
<!-- web环境 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- apollo客户端 -->
<dependency>
<groupId>com.ctrip.framework.apollo</groupId>
<artifactId>apollo-client</artifactId>
<version>1.9.2</version>
</dependency>
</dependencies>
</project>

基本配置

Spring Boot 项目的配置文件 application.yml 中添加以下配置项:

1
2
3
4
5
6
7
8
9
apollo:
bootstrap:
enabled: true # 注入默认 application namespace 的配置
namespaces: application.yml # 注入其它多个 namespace(非默认 application)的配置
eagerLoad:
enabled: true # 将 Apollo 配置加载提到初始化日志系统之前:可以将日志相关的配置也交由 Apollo 进行管理

app:
id: xuejian-apollo

注意还需要在 VM Options 中配置环境 envMeta Server 的地址(apollo.meta):

1
-Denv=DEV -Dapollo.meta=http://localhost:8080

添加配置

todo

读取配置

可使用 @ApolloConfig@ApolloJsonValue@Value@ConfigurationProperties@ApolloConfigChangeListener 等注解读取配置。

使用 @ApolloConfig 注解

使用 @ApolloConfig 注解自动注入对应 namespaceConfig 对象,代码示例如下:

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
package io.github.llnancy.xuejian.apollo.web.controller;

import com.ctrip.framework.apollo.Config;
import com.ctrip.framework.apollo.spring.annotation.ApolloConfig;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

/**
* Apollo 客户端读取配置项
* 需添加 VM Options 启动参数:-Denv=DEV -Dapollo.meta=http://localhost:8080
*
* @author sunchaser admin@lilu.org.cn
* @since JDK8 2022/3/27
*/
@RestController
public class ApolloConfigController {

@ApolloConfig(value = "application.yml")
private Config config;

@GetMapping("/config")
public String getConfig(String key) {
return config.getProperty(key, null);
}
}

注入了 application.yml 这个 namespace 对应的 Config 对象,从而可以读取 key-value 类型的字符串配置项。

使用 @ApolloJsonValue 注解

使用 @ApolloJsonValue 注解把配置的 json 字符串自动注入为 Java Bean 对象,代码示例如下:

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
package io.github.llnancy.xuejian.apollo.web.controller;

import com.ctrip.framework.apollo.Config;
import com.ctrip.framework.apollo.spring.annotation.ApolloConfig;
import com.ctrip.framework.apollo.spring.annotation.ApolloJsonValue;
import lombok.Data;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

/**
* Apollo 客户端读取配置项
* 需添加 VM Options 启动参数:-Denv=DEV -Dapollo.meta=http://localhost:8080
*
* @author sunchaser admin@lilu.org.cn
* @since JDK8 2022/3/27
*/
@RestController
public class ApolloConfigController {

/**
* yml format:
* xuejian:
* apollo:
* jsonstr: '[{"name":"sun", "age": 18}, {"name":"chaser", "age": 20}]'
*/
@ApolloJsonValue("${xuejian.apollo.jsonstr:[]}")
private List<JsonBean> jsonBeanList;

@Data
public static class JsonBean {
private String name;
private Integer age;
}

@GetMapping("/json-value")
public List<JsonBean> getJsonValue() {
return jsonBeanList;
}
}

注意:yml 格式下配置 json 字符串时需要用单引号 '' 将整个 json 串括起来进行转义。

使用 @Value 注解

使用 @Value 注释实际上是通过 SpringPlaceholder 占位符的形式来注入配置项,其格式支持 SpEL 表达式,例如 ${key:defaultValue},冒号左边的 key 是配置项,冒号右边的 defaultValue 是默认值(可不提供)。代码示例如下:

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
26
27
28
29
30
31
32
33
34
35
package io.github.llnancy.xuejian.apollo.web.controller;

import com.ctrip.framework.apollo.Config;
import com.ctrip.framework.apollo.spring.annotation.ApolloConfig;
import com.ctrip.framework.apollo.spring.annotation.ApolloJsonValue;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

/**
* Apollo 客户端读取配置项
* 需添加 VM Options 启动参数:-Denv=DEV -Dapollo.meta=http://localhost:8080
*
* @author sunchaser admin@lilu.org.cn
* @since JDK8 2022/3/27
*/
@RestController
@Slf4j
public class ApolloConfigController {

@Value("${xuejian.apollo.name:defaultVal}")
private String key;

@Value("#{'${xuejian.apollo.jsonstr:[]}'.split(',')}")
private List<String> jsonList;

@GetMapping("/placeholder")
public void getConfigByPlaceholder() {
log.info("key={}, jsonList={}", key, jsonList);
}
}

配合 @ConfigurationProperties 注解使用

首先定义一个 config 配置类代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package io.github.llnancy.xuejian.apollo.config;

import lombok.Getter;
import lombok.Setter;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;

import java.util.List;

/**
* @author sunchaser admin@lilu.org.cn
* @since JDK8 2022/3/28
*/
@ConfigurationProperties(prefix = "xuejian.apollo")
@Configuration
@Getter
@Setter
public class XueJianApolloConfig {

private String name;

private List<String> jsonstr;
}

然后就可以通过注入的方式进行使用:

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
26
27
28
29
30
31
package io.github.llnancy.xuejian.apollo.web.controller;

import config.io.github.llnancy.xuejian.apollo.XueJianApolloConfig;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

/**
* Apollo 客户端读取配置项
* 需添加 VM Options 启动参数:-Denv=DEV -Dapollo.meta=http://localhost:8080
*
* @author sunchaser admin@lilu.org.cn
* @since JDK8 2022/3/27
*/
@RestController
@Slf4j
public class ApolloConfigController {

@Autowired
private XueJianApolloConfig xueJianApolloConfig;

@GetMapping("/configurationProperties")
public void getXueJianApolloConfig() {
String name = xueJianApolloConfig.getName();
List<String> jsonstr = xueJianApolloConfig.getJsonstr();
log.info("name={}, jsonstr={}", name, jsonstr);
}
}

使用 @ApolloConfigChangeListener 注解

使用 @ApolloConfigChangeListener 注解可自动注册 ConfigChangeListener 监听器监听配置项变更事件,示例代码如下:

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
package io.github.llnancy.xuejian.apollo.config;

import com.ctrip.framework.apollo.model.ConfigChange;
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.cloud.context.environment.EnvironmentChangeEvent;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Configuration;

import javax.annotation.Nonnull;
import java.util.Set;

/**
* apollo 配置更新监听器
*
* @author sunchaser admin@lilu.org.cn
* @since JDK8 2022/3/28
*/
@Configuration
@Slf4j
public class ApolloRefresherConfig implements ApplicationContextAware {

private ApplicationContext applicationContext;

@ApolloConfigChangeListener(value = {"application", "application.yml"})
private void refreshConfig(ConfigChangeEvent configChangeEvent) {
log.info("listened config change event, the namespace is {}", configChangeEvent.getNamespace());
Set<String> changedKeys = configChangeEvent.changedKeys();
for (String changedKey : changedKeys) {
ConfigChange change = configChangeEvent.getChange(changedKey);
log.info("Found change - key: {}, oldValue: {}, newValue: {}, changeType: {}", change.getPropertyName(), change.getOldValue(), change.getNewValue(), change.getChangeType());
}
// 更新 @ConfigurationProperties 注解注入的属性
this.applicationContext.publishEvent(new EnvironmentChangeEvent(changedKeys));
}

@Override
public void setApplicationContext(@Nonnull ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
}

对于使用 @ConfigurationProperties 注解注入的属性,需要发布 org.springframework.cloud.context.environment.EnvironmentChangeEvent 事件触发属性值的更新。所以需要在 pom.xml 中添加 spring-cloud-context 依赖:

1
2
3
4
5
6
<!-- spring cloud 上下文管理   -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-context</artifactId>
<version>3.1.1</version>
</dependency>