手写springcloud|配置中心 客户端

手写springcloud|配置中心 客户端

github示例代码

github对应代码仓库地址:https://github.com/huajiexiewenfeng/spring-cloud-project-learn

前言

在上篇中介绍了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,不需要重启服务,一直刷新页面,等待数据更新
在这里插入图片描述

感谢

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值