专注于 JetBrains IDEA 全家桶,永久激活,教程
持续更新 PyCharm,IDEA,WebStorm,PhpStorm,DataGrip,RubyMine,CLion,AppCode 永久激活教程

基于Mina的配置中心(六)配置中心遗留问题

基于Mina的配置中心(六)配置中心遗留问题

首先要解决的问题是无法配置数据库的问题。

我们要知其然也要知其所以然。为什么无法配置数据库呢?

这就要说一下SpringBoot的启动流程了。

如果要说SpringBoot的启动流程,那就少不了这个方法 org.springframework.boot.SpringApplication#run(java.lang.String...)

89_1.png

最核心就是上面框的三个方法。

我们使用的是 org.springframework.boot.SpringApplicationRunListener=...

而这个SpringBootRunListener是在org.springframework.boot.SpringApplicationRunListeners#started这个方法里去调用的。

89_2.png

可见已经都要启动结束了。这个时候数据库配置信息没有的话,直接启动失败。

如何解决?

把获取配置信息并注入的操作提前。

我把配置的注入放到prepareContext中,这时还没有创建dataSource对象,只是准备上下文。

spring.factories里把org.springframework.boot.SpringApplicationRunListener=...改为org.springframework.context.ApplicationContextInitializer=... 实现的接口也要改,改为ApplicationContextInitializer<ConfigurableApplicationContext>,然后改一下方法,下面有完整例子。

这样就会在prepareContext中执行。 89_3.png

经过测试还是不行,因为我们使用到了SpringBoot的事件发布与订阅。 而这个Listener是在下面的refreshContextorg.springframework.context.support.AbstractApplicationContext#refresh这里绑定的。 89_4.png

org.springframework.context.support.AbstractApplicationContext#registerListeners 89_5.png

所以会报一个错ApplicationEventMulticaster not initialized

后来我修改为不使用事件发布与订阅,还是不行,因为这时,客户端是没有与服务端建立连接的,所以也就没有与服务端连接的Session,也就无法通信。仿佛进入了死胡同。

Nacos查找解决办法

万幸的是,我们还有Nacos可以去学习一下,看她是如何解决的。

在启动时初始化
com.alibaba.boot.nacos.config.autoconfigure.NacosConfigApplicationContextInitializer#initialize 89_6.png 获取配置信息
com.alibaba.boot.nacos.config.autoconfigure.NacosConfigApplicationContextInitializer#reqGlobalNacosConfig 89_7.png

89_8.png com.alibaba.nacos.spring.util.config.NacosConfigLoader#load(java.lang.String, java.lang.String, java.util.Properties) 89_9.png com.alibaba.nacos.spring.util.NacosUtils#getContent 89_10.png com.alibaba.nacos.client.config.NacosConfigService#getConfig 89_11.png com.alibaba.nacos.client.config.NacosConfigService#getConfigInner 89_12.png com.alibaba.nacos.client.config.impl.ClientWorker#getServerConfig 89_13.png 通过发送http请求,从服务端获取配置
com.alibaba.nacos.client.config.http.MetricsHttpAgent#httpGet 89_14.png

89_15.png89_16.png89_17.png89_18.png89_19.png

在项目启动时,Nacos是通过http请求,从服务端获取配置,所以我在Server端增加了一个接口,客户端可以通过这个接口获取配置信息。

@ApiOperation("获取单个配置信息")
@ApiImplicitParams({
        @ApiImplicitParam(name = "projectName", value = "项目名称", required = true),
        @ApiImplicitParam(name = "env", value = "环境", required = true),
        @ApiImplicitParam(name = "propertyValue", value = "application.properties 配置的值", required = true),
}) @GetMapping(value = "/conf", name = "获取配置信息") public Message conf(String projectName, String env, String propertyValue) { Message message = messageService.getOne(new QueryWrapper<Message>().lambda() .eq(Message::getProjectName, projectName) .eq(Message::getEnvValue, env) .eq(Message::getPropertyValue, propertyValue) .eq(Message::getIsDeleted, 0)); Assert.isTrue(message != null, "查询结果为空!"); return message; } 

然后在Client中调整了配置属性,增加了http端口。

/**
 * 服务器监听端口,默认 9123 Mian监听端口
 */
private Integer minaPort = 9123;

/** * http端口,默认8080 */ private Integer port = 8080; 

删除了原来的ConfStartCollectSendManager,使用了MinaInitializer在启动时获取配置。

package com.lww.mina.init;

import com.alibaba.fastjson.JSONObject;
import com.lww.mina.dto.MessageDO;
import com.lww.mina.util.Const;
import com.lww.mina.util.HttpUtils; import java.util.Map; import java.util.Map.Entry; import java.util.Properties; import java.util.concurrent.ConcurrentHashMap; import org.apache.commons.lang3.StringUtils; import org.springframework.context.ApplicationContextInitializer; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.MapPropertySource; import org.springframework.core.env.MutablePropertySources; import org.springframework.core.env.PropertiesPropertySource; import org.springframework.util.Assert; /** * 在 org.springframework.boot.SpringApplication#prepareContext 中执行, * 在bean创建注入之前,从服务器获取配置信息,如数据库等配置信息 * * @author lww * @date 2020-07-11 16:50 */ public class MinaInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> { private static final String PROPERTY_SOURCE_NAME = "applicationConfig"; private static final String ENV_KEY = "mina.client.env"; private static final String PROJECT_NAME = "mina.client.project-name"; private static final String PORT = "mina.client.port"; private static final String SERVER_ADDRESS = "mina.client.server-address"; public static Map<String, Object> configs = new ConcurrentHashMap<>(16); @Override public void initialize(ConfigurableApplicationContext context) { ConfigurableEnvironment environment = context.getEnvironment(); MutablePropertySources sources = environment.getPropertySources(); //遍历 Environment for (Object property : sources) { if (property instanceof MapPropertySource) { MapPropertySource propertySource = (MapPropertySource) property; //取到 applicationConfig 这个配置对象 if (propertySource.getName().contains(PROPERTY_SOURCE_NAME)) { String[] properties = propertySource.getPropertyNames(); for (String s : properties) { //如果是以 mina.config 开头的,保存到 configs map中 if (s.startsWith(Const.CONF)) { configs.put(s, propertySource.getProperty(s)); } } } } } final String env = StringUtils.isNotBlank(environment.getProperty(ENV_KEY)) ? environment.getProperty(ENV_KEY) : "local"; final String port = StringUtils.isNotBlank(environment.getProperty(PORT)) ? environment.getProperty(PORT) : "8080"; final String address = StringUtils.isNotBlank(environment.getProperty(SERVER_ADDRESS)) ? environment.getProperty(SERVER_ADDRESS) : "127.0.0.1"; final String projectName = environment.getProperty(PROJECT_NAME); final String remoteAddr = address.trim() + ":" + port.trim(); //通过http请求获取配置,修改配置的值 for (Entry<String, Object> entry : configs.entrySet()) { String value = entry.getValue().toString(); String param = "projectName=" + projectName + "&env=" + env + "&propertyValue=" + value; String result = HttpUtils.sendGetHttp("http://" + remoteAddr + "/message/conf", param, null); if (StringUtils.isNotBlank(result)) { MessageDO messageDO = JSONObject.parseObject(result, MessageDO.class); Properties props = new Properties(); props.put(entry.getKey(), messageDO.getConfigValue()); //修改 Environment 中的值,否则从 Environment 中获取,还是原来的值 environment.getPropertySources().addFirst(new PropertiesPropertySource(Const.CONF, props)); } else { Assert.isTrue(false, "获取配置信息失败!"); } } } } 

数据库中配置的信息 89_20.png

可以看到,数据库配置信息已经可以取到,并且注入到了Mybatis-Plus配置类中 89_21.png

剩下的问题:

当我在修改配置时, 89_22.png

服务端发出消息 89_23.png

客户端接收到消息,并且修改了值 89_24.png

但是数据库连接是没有改变的,查询还是可以正常查询。不过重启的时候会报错。 89_25.png 重启报错 89_26.png

因为,数据库连接属性已经注入了dataSource对象,这个对象保存在SpringBoot容器中,我们单纯的修改配置的值是无法影响SpringBoot容器的。

总结

最近在思考,如何不重启就可以刷新数据源配置。最近看到一篇文章 配置热更新,不想重启,如何更新Bean的状态? 如果可以动态刷新数据源,又牵扯到一系列问题,比如事务,数据库连接,如何平滑切换,结论就是还是重启最好用。

不过这也是一个问题,可以深入研究一下。

Server最新源码

Client最新源码

Client-Demo最新源码

欢迎大家关注我的公众号,共同学习,一起进步。加油

89_27.png

本文使用 tech.souyunku.com 排版

文章永久链接:https://tech.souyunku.com/33740

未经允许不得转载:搜云库技术团队 » 基于Mina的配置中心(六)配置中心遗留问题

JetBrains 全家桶,激活、破解、教程

提供 JetBrains 全家桶激活码、注册码、破解补丁下载及详细激活教程,支持 IntelliJ IDEA、PyCharm、WebStorm 等工具的永久激活。无论是破解教程,还是最新激活码,均可免费获得,帮助开发者解决常见激活问题,确保轻松破解并快速使用 JetBrains 软件。获取免费的破解补丁和激活码,快速解决激活难题,全面覆盖 2024/2025 版本!

联系我们联系我们