转载

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

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

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

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

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

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

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

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

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

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

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

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

如何解决?

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

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

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

这样就会在 prepareContext 中执行。 基于Mina的配置中心(六)配置中心遗留问题

经过测试还是不行,因为我们使用到了 SpringBoot 的事件发布与订阅。 而这个Listener是在下面的 refreshContextorg.springframework.context.support.AbstractApplicationContext#refresh 这里绑定的。 基于Mina的配置中心(六)配置中心遗留问题

org.springframework.context.support.AbstractApplicationContext#registerListeners 基于Mina的配置中心(六)配置中心遗留问题

所以会报一个错 ApplicationEventMulticaster not initialized

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

Nacos 查找解决办法

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

在启动时初始化

com.alibaba.boot.nacos.config.autoconfigure.NacosConfigApplicationContextInitializer#initialize 基于Mina的配置中心(六)配置中心遗留问题

获取配置信息

com.alibaba.boot.nacos.config.autoconfigure.NacosConfigApplicationContextInitializer#reqGlobalNacosConfig 基于Mina的配置中心(六)配置中心遗留问题
基于Mina的配置中心(六)配置中心遗留问题 com.alibaba.nacos.spring.util.config.NacosConfigLoader#load(java.lang.String, java.lang.String, java.util.Properties) 基于Mina的配置中心(六)配置中心遗留问题 com.alibaba.nacos.spring.util.NacosUtils#getContent 基于Mina的配置中心(六)配置中心遗留问题 com.alibaba.nacos.client.config.NacosConfigService#getConfig 基于Mina的配置中心(六)配置中心遗留问题 com.alibaba.nacos.client.config.NacosConfigService#getConfigInner 基于Mina的配置中心(六)配置中心遗留问题 com.alibaba.nacos.client.config.impl.ClientWorker#getServerConfig 基于Mina的配置中心(六)配置中心遗留问题

通过发送http请求,从服务端获取配置

com.alibaba.nacos.client.config.http.MetricsHttpAgent#httpGet 基于Mina的配置中心(六)配置中心遗留问题
基于Mina的配置中心(六)配置中心遗留问题
基于Mina的配置中心(六)配置中心遗留问题
基于Mina的配置中心(六)配置中心遗留问题
基于Mina的配置中心(六)配置中心遗留问题
基于Mina的配置中心(六)配置中心遗留问题

在项目启动时, 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, "获取配置信息失败!");
            }
        }
    }
}
复制代码

数据库中配置的信息 基于Mina的配置中心(六)配置中心遗留问题

可以看到,数据库配置信息已经可以取到,并且注入到了Mybatis-Plus配置类中 基于Mina的配置中心(六)配置中心遗留问题

剩下的问题:

当我在修改配置时, 基于Mina的配置中心(六)配置中心遗留问题

服务端发出消息 基于Mina的配置中心(六)配置中心遗留问题

客户端接收到消息,并且修改了值 基于Mina的配置中心(六)配置中心遗留问题

但是数据库连接是没有改变的,查询还是可以正常查询。不过重启的时候会报错。 基于Mina的配置中心(六)配置中心遗留问题 重启报错 基于Mina的配置中心(六)配置中心遗留问题

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

总结

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

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

Server最新源码

Client最新源码

Client-Demo最新源码

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

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

本文使用 mdnice 排版

原文  https://juejin.im/post/5f0d8a0b5188252e5734100e
正文到此结束
Loading...