0. 配置中心简单交互
- 编写自己的sdk:拉取配置、服务器端更新后客户端能感知到并且更新到本地
- 和Springboot 做整合:(依赖Springcloud)
(1). Springcloud 预留了做配置中心的接口,相当于是注入自己的PropertySourceLocator, Springcloud 环境启动过程中会读取bootstrap.properties, 然后进行Springcloud 环境初始化(包括加载PropertySourceLocator)
(2). 配置更新后通知Spring的environment和@Value 注入进去的bean。 依赖于Springcloud 提供的@RefreshScope 注解以及发布RefreshEvent 事件。
@RefreshScope 相当于重写Spring Bean 的作用域,在org.springframework.cloud.context.scope.refresh.RefreshScope 获取对象(逻辑交给父类,实际类似单例缓存到内部);接收到RefreshEvent 事件之后清除缓存,下次getBean就会重新建对象以及依赖注入。
1. pom
<?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">
<modelVersion>4.0.0</modelVersion>
<groupId>com.bj58</groupId>
<artifactId>spring-cloud-starter-alg-config</artifactId>
<version>1.0.0-SNAPSHOT</version>
<packaging>jar</packaging>
<name>demo</name>
<description>Demo project for Spring Boot</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.2.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>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- 自己的客户端和服务端通信的sdk -->
<!-- sdk包含拉取配置,配置更新后能够再次拉取以及发出通知 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-context</artifactId>
<version>2.2.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-commons</artifactId>
<version>2.2.2.RELEASE</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
2. 编写自己的sdk
一般配置中心分为两端。server端和client端。server 端用于配置的CRUD以及对外暴露接口,供SDK拉取配置以及接收变更后的配置。
3. 实验服务启动拉取配置
1. 重要类
ConfigProperties (配置类)
package com.demo.test;
import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties(ConfigProperties.PREFIX)
public class ConfigProperties {
public static final String PREFIX = "spring.cloud.config";
private String serverAddr;
private String policyName;
private String env;
public String getServerAddr() {
return serverAddr;
}
public void setServerAddr(String serverAddr) {
this.serverAddr = serverAddr;
}
public String getPolicyName() {
return policyName;
}
public void setPolicyName(String policyName) {
this.policyName = policyName;
}
public String getEnv() {
return env;
}
public void setEnv(String env) {
this.env = env;
}
}
CustomConfigPropertySource、CustomConfigPropertySourceBuilder、CustomConfigPropertySourceLocator
package com.demo.test.client;
import org.springframework.core.env.MapPropertySource;
import java.util.Date;
import java.util.Map;
public class CustomConfigPropertySource extends MapPropertySource {
private final String dataId;
private final Date timestamp;
private final boolean isRefreshable;
CustomConfigPropertySource(String dataId, Map<String, Object> source, Date timestamp, boolean isRefreshable) {
super(dataId, source);
this.dataId = dataId;
this.timestamp = timestamp;
this.isRefreshable = isRefreshable;
}
}
package com.demo.test.client;
import com.demo.test.ConfigProperties;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
public class CustomConfigPropertySourceBuilder {
private ConfigProperties algConfigProperties;
public CustomConfigPropertySourceBuilder(ConfigProperties algConfigProperties) {
this.algConfigProperties = algConfigProperties;
}
CustomConfigPropertySource build(String dataId, boolean isRefreshable) {
String policyName = algConfigProperties.getPolicyName();
Map<String, Object> result = new HashMap<>();
// todo 调用自己的SDK 拉取配置
Map<String, String> allConfig = null;
// 添加到缓存用于refresh
// ConfigCacheData.getInstance().addConfig(policyName, config);
if (allConfig != null && allConfig.size() > 0) {
allConfig.forEach((k, v) -> {
result.put(k, v);
});
}
CustomConfigPropertySource algConfigPropertySource = new CustomConfigPropertySource(dataId, result, new Date(), isRefreshable);
return algConfigPropertySource;
}
}
package com.demo.test.client;
import com.demo.test.ConfigProperties;
import org.springframework.cloud.bootstrap.config.PropertySourceLocator;
import org.springframework.core.env.CompositePropertySource;
import org.springframework.core.env.Environment;
import org.springframework.core.env.PropertySource;
public class CustomConfigPropertySourceLocator implements PropertySourceLocator {
private static final String ALG_CONFIG_PROPERTY_SOURCE_NAME = "CUSTOM_CONFIG";
private static final boolean isRefreshable = true;
private CustomConfigPropertySourceBuilder customConfigPropertySourceBuilder;
private ConfigProperties configProperties;
public CustomConfigPropertySourceLocator(ConfigProperties algConfigProperties) {
this.configProperties = algConfigProperties;
this.customConfigPropertySourceBuilder = new CustomConfigPropertySourceBuilder(algConfigProperties);
}
@Override
public PropertySource<?> locate(Environment environment) {
CompositePropertySource composite = new CompositePropertySource(ALG_CONFIG_PROPERTY_SOURCE_NAME);
String dataId = configProperties.getPolicyName();
CustomConfigPropertySource ps = customConfigPropertySourceBuilder.build(dataId, isRefreshable);
composite.addFirstPropertySource(ps);
// 遍历激活的环境进行获取
// for (String profile : environment.getActiveProfiles()) {
// String dataId = policyName + "-" + profile;
// 遍历去获取配置ps对象
// composite.addFirstPropertySource(ps);
// }
return composite;
}
}
2. 自动配置类
package com.demo.test;
import com.demo.test.client.CustomConfigPropertySourceLocator;
import org.springframework.beans.factory.BeanFactoryUtils;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
//@Configuration
//@ConditionalOnProperty(name = "spring.cloud.alg.config.enabled", matchIfMissing = true)
public class CustomConfigAutoConfiguration {
@Bean
public ConfigProperties configProperties(ApplicationContext context) {
if (context.getParent() != null
&& BeanFactoryUtils.beanNamesForTypeIncludingAncestors(
context.getParent(), ConfigProperties.class).length > 0) {
return BeanFactoryUtils.beanOfTypeIncludingAncestors(context.getParent(),
ConfigProperties.class);
}
ConfigProperties nacosConfigProperties = new ConfigProperties();
return nacosConfigProperties;
}
@Bean
public CustomConfigPropertySourceLocator customConfigPropertySourceLocator(ConfigProperties configProperties) {
return new CustomConfigPropertySourceLocator(configProperties);
}
}
3. bootstrap.properties 配置
spring.cloud.config.policyName=qlq_test02
server.port=8090
4.resources/META-INF/spring.factories 文件
增加如下 springcloud 自动配置
org.springframework.cloud.bootstrap.BootstrapConfiguration=\
com.demo.test.CustomConfigAutoConfiguration
5. 测试
到这里可以实现服务启动后自动拉取配置,且注入到Spring的Environment 对象中。 只需要自己完成CustomConfigPropertySourceBuilder 类中调用自己的SDK从服务器端拉取配置(可以总RPC或者HTTP)
4. 自动刷新
1. 重要类
- 自己的业务listener (监听到数据改变后发布事件)
package com.demo.test.refresh;
import com.xxx.sdk.ConfigNotification;
import com.xxx.sdk.RepositoryChangeListener;
import org.springframework.beans.BeansException;
import org.springframework.cloud.endpoint.event.RefreshEvent;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
public class CustomConfigChangeListener implements RepositoryChangeListener, ApplicationContextAware {
private ApplicationContext context;
// 相当于client 端监听到server 端数据发生变更
@Override
public void onRepositoryChange(String policyName, ConfigNotification configNotification) {
// 发布Springcloud 事件
context.publishEvent(new RefreshEvent(this, null, "Refresh config " + policyName));
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.context = applicationContext;
}
}
- Refresher
package com.demo.test.refresh;
import com.demo.test.ConfigProperties;
import com.demo.test.cache.ConfigCacheData;
import com.xxx.sdk.RepositoryChangeListener;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.ApplicationListener;
import java.util.List;
public class CustomConfigRefresher implements ApplicationListener<ApplicationReadyEvent> {
private List<RepositoryChangeListener> listeners;
private ConfigCacheData configCacheData;
private ConfigProperties algConfigProperties;
public CustomConfigRefresher(List<RepositoryChangeListener> listeners, ConfigCacheData configCacheData, ConfigProperties algConfigProperties) {
this.listeners = listeners;
this.configCacheData = configCacheData;
this.algConfigProperties = algConfigProperties;
}
@Override
public void onApplicationEvent(ApplicationReadyEvent applicationReadyEvent) {
configCacheData.addListener(algConfigProperties.getPolicyName(), this.listeners);
}
}
- ConfigCacheData
用于缓存。 实际上相当于SDK拉取的配置和Spring 环境中的对象做的一个关联。用于添加监听器以及发布Spring事件。
2. ConfigRefresherAutoConfiguration 自动配置类
package com.demo.test;
import com.bj58.hy.algorith.platform.config.internals.RepositoryChangeListener;
import com.demo.test.cache.ConfigCacheData;
import com.demo.test.refresh.CustomConfigChangeListener;
import com.demo.test.refresh.CustomConfigRefresher;
import org.springframework.context.annotation.Bean;
import java.util.List;
public class ConfigRefresherAutoConfiguration {
@Bean
public CustomConfigChangeListener customConfigChangeListener() {
return new CustomConfigChangeListener();
}
@Bean
public CustomConfigRefresher customConfigRefresher(List<RepositoryChangeListener> listeners, ConfigProperties algConfigProperties) {
return new CustomConfigRefresher(listeners, ConfigCacheData.getInstance(), algConfigProperties);
}
}
3. resources/META-INF/spring.factories 文件
增加如下Springboot 自动配置
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.demo.test.ConfigRefresherAutoConfiguration
5. 测试类
package com.demo.test.test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.core.env.Environment;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping
@RefreshScope
public class TestController {
@Autowired
private Environment environment;
@Value("${manager:''}")
private String test2;
/**
* 测试从environment 获取
*
* @return
*/
@GetMapping("/test")
public String test() {
return environment.getProperty("manager");
}
/**
* 测试自动注入
*
* @return
*/
@GetMapping("/test2")
public String test2() {
return test2;
}
/**
* 测试 @RefreshScope 更新注解
*
* @return
*/
@GetMapping("/test3")
public String test3() {
return this.toString();
}
}