手写springcloud|配置中心 客户端
文章目录
github示例代码
github对应代码仓库地址:https://github.com/huajiexiewenfeng/spring-cloud-project-learn
spring-application 手写spring cloud系列
spring-cloud-client-appliaction 客户端
eventBus
spring-cloud-server-application 服务端
spring-boot-2.0-samples SpringBoot编程思想系列
spring-cloud-application 手写spring cloud系列 基础组件
- spring-cloud-servlet-gateway 网关
- spring-cloud-config-server 配置中心服务端
- spring-cloud-config-client 配置中心客户端
前言
在上篇中介绍了spring配置中心服务端的原理,以及如何自定义配置源来扩展配置中心;本篇是对客户端的是实现,实现的效果:
- 从服务端动态拉取配置信息
- 实时刷新到客户端中,不需要重启springboot应用
实现步骤
以
springboot2.0
环境为例
第一步:config-client
客户端如何获取config-server
服务端的配置
从上篇 服务端 的文章中可知,访问http://127.0.0.1:8888/config/test/master
可以获取配置的相关信息
config-server
服务端相关的实现代码如下,和我们写spring web MVC
业务代码类似
org.springframework.cloud.config.server.resource.ResourceController#retrieve()
...此处省略N行代码
@RequestMapping("/{name}/{profile}/{label}/**")
public String retrieve(@PathVariable String name, @PathVariable String profile,
@PathVariable String label, HttpServletRequest request,
@RequestParam(defaultValue = "true") boolean resolvePlaceholders)
throws IOException {
String path = getFilePath(request, name, profile, label);
return retrieve(name, profile, label, path, resolvePlaceholders);
}
@RequestMapping(value = "/{name}/{profile}/**", params = "useDefaultLabel")
public String retrieve(@PathVariable String name, @PathVariable String profile,
HttpServletRequest request,
@RequestParam(defaultValue = "true") boolean resolvePlaceholders)
throws IOException {
String path = getFilePath(request, name, profile, null);
return retrieve(name, profile, null, path, resolvePlaceholders);
}
...此处省略N行代码
synchronized String retrieve(String name, String profile, String label, String path,
boolean resolvePlaceholders) throws IOException {
if (name != null && name.contains("(_)")) {
// "(_)" is uncommon in a git repo name, but "/" cannot be matched
// by Spring MVC
name = name.replace("(_)", "/");
}
if (label != null && label.contains("(_)")) {
// "(_)" is uncommon in a git branch name, but "/" cannot be matched
// by Spring MVC
label = label.replace("(_)", "/");
}
// ensure InputStream will be closed to prevent file locks on Windows
try (InputStream is = this.resourceRepository.findOne(name, profile, label, path)
.getInputStream()) {
String text = StreamUtils.copyToString(is, Charset.forName("UTF-8"));
if (resolvePlaceholders) {
Environment environment = this.environmentRepository.findOne(name,
profile, label);
text = resolvePlaceholders(prepareEnvironment(environment), text);
}
return text;
}
}
接口返回效果:
客户端其实只需要访问该接口获取配置信息即可。
第二步:如何将参数加载到spring配置中
这里可以参考 springcloud config-client
的实现或者是 nacos
的实现
/**
* @author xiaojing
* @author pbting
*/
@Order(0)
public class NacosPropertySourceLocator implements PropertySourceLocator {
...此处省略N行代码
@Override
public PropertySource<?> locate(Environment env) {
...此处省略N行代码
CompositePropertySource composite = new CompositePropertySource(
NACOS_PROPERTY_SOURCE_NAME);
loadSharedConfiguration(composite);
loadExtConfiguration(composite);
loadApplicationConfiguration(composite, dataIdPrefix, nacosConfigProperties, env);
return composite;
}
都是实现了PropertySourceLocator
接口重写locate()
方法,通过这种方式将配置加入到spring
的配置中
在PropertySourceBootstrapConfiguration
中实现了ApplicationContextInitializer
,重写initialize()
方法,显然是要进行初始化;而在初始化的过程中,获取我们实现的propertySourceLocators
,调用locate()
方法获取配置信息
...此处省略N行代码
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
CompositePropertySource composite = new CompositePropertySource(
BOOTSTRAP_PROPERTY_SOURCE_NAME);
AnnotationAwareOrderComparator.sort(this.propertySourceLocators);
boolean empty = true;
ConfigurableEnvironment environment = applicationContext.getEnvironment();
//遍历所有的 propertySourceLocators
for (PropertySourceLocator locator : this.propertySourceLocators) {
PropertySource<?> source = null;
//调用locate方法获取配置资源
source = locator.locate(environment);
if (source == null) {
continue;
}
logger.info("Located property source: " + source);
composite.addPropertySource(source);
empty = false;
}
...此处省略N行代码
}
我们也只需要实现PropertySourceLocator
接口即可
第三步:如何实时刷新配置
使用Scheduled
定时任务进行调用,至于如何刷新配置?
这里又要找nacos
中的相关代码进行参考
com.alibaba.cloud.nacos.refresh.NacosContextRefresher
中的registerNacosListener()
方法
注册一个Nacos的监听,监听处理核心代码,发送一个RefreshEvent
的事件:
@Override
public void receiveConfigInfo(String configInfo) {
refreshCountIncrement();
String md5 = "";
if (!StringUtils.isEmpty(configInfo)) {
try {
MessageDigest md = MessageDigest.getInstance("MD5");
md5 = new BigInteger(1, md.digest(configInfo.getBytes("UTF-8")))
.toString(16);
}
catch (NoSuchAlgorithmException | UnsupportedEncodingException e) {
log.warn("[Nacos] unable to get md5 for dataId: " + dataId, e);
}
}
refreshHistory.add(dataId, md5);
applicationContext.publishEvent(
new RefreshEvent(this, null, "Refresh Nacos config"));
if (log.isDebugEnabled()) {
log.debug("Refresh Nacos config group " + group + ",dataId" + dataId);
}
spring
应该有一个对应的RefreshEvent
事件监听器,来刷新spring
的配置。
这里有点需要注意,此事件只是刷新@RefreshScope
标注类的属性。
手写代码
ConfigController类
-
name
从 配置中心服务端 获取对应的属性 -
使用 restTemplate 访问 url 获取 Environment 相关的配置信息
-
restTemplate.getForObject(url, org.springframework.cloud.config.environment.Environment.class)
-
-
将返回的配置信息封装成
PropertySource
对象,返回即可 -
使用
@Scheduled(fixedRate = 1 * 5000)
每5秒刷新一次配置信息- 使用
applicationContext.publishEvent( new RefreshEvent(this, null, "Refresh custom config"));
发布刷新事件
- 使用
@RefreshScope
@Controller
public class ConfigController implements PropertySourceLocator, EnvironmentAware, ApplicationContextAware {
private Environment environment;
private ApplicationContext applicationContext;
@Value("${name}")
private String name;
@Value("${spring.cloud.config.uri}")
private String serverUri;
private RestTemplate restTemplate = new RestTemplate();
private AtomicBoolean ready = new AtomicBoolean(false);
@GetMapping("/config/hello")
@ResponseBody
public String hello() {
return "hello" + name;
}
@Override
public void setEnvironment(Environment environment) {
this.environment = environment;
}
@Scheduled(fixedRate = 1 * 5000)
public void refreshConfig() {
applicationContext.publishEvent(
new RefreshEvent(this, null, "Refresh custom config"));
}
@Override
public org.springframework.core.env.PropertySource<?> locate(Environment environment) {
CompositePropertySource composite = new CompositePropertySource("configService");
String url = serverUri + "/config/test/master";
org.springframework.cloud.config.environment.Environment environmentObj = restTemplate.getForObject(url, org.springframework.cloud.config.environment.Environment.class);
List<PropertySource> propertySources = environmentObj.getPropertySources();
propertySources.stream().forEach(propertySource -> {
Map<String, Object> source = (Map<String, Object>) propertySource.getSource();
composite.addPropertySource(
new MapPropertySource(propertySource.getName(), source));
});
return composite;
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
}
其他类参考github
中的源码即可
测试
启动 SpringCloudConfigServer
配置中心服务端
启动 SpringCloudConfigClient
客户端服务
浏览器输入 http://127.0.0.1:8080/config/hello 访问客户端的hello()
借口
页面返回
修改test.txt文本中的值name=xwf_flie111222,不需要重启服务,一直刷新页面,等待数据更新
感谢
- 小马哥的分享 博客地址 https://mercyblitz.github.io/