IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> 动态修改SpringBoot配置 -> 正文阅读

[Java知识库]动态修改SpringBoot配置

SpringBoot应用的配置文件默认是application.properties,而且必须在启动前就已经配置好,在运行过程中不允许修改。如果确实想让应用在运行过程中修改配置呢,我们可以将配置记录在Zookeeper上,借助Zookeeper的watcher机制来实现配置变更通知。

一般的属性获取示例:
application.properties的配置如下:

name=ljh

ConfigController如下:

/**
 * @author lipeng
 * @date 2021/8/14
 */
@RestController
public class ConfigController {

    @Resource
    private Environment environment;

    @Value("${name}")
    private String name;

    @GetMapping("/")
    public String index() {
        String fromEnvironment = environment.getProperty("name");
        return String.format("fromEnvironment: %s, fromValue: %s", fromEnvironment, name);
    }
}

获取的两个属性值都是配置文件中配置的。

了解下这两个值是怎么获取的。Environment对象的属性如下:
在这里插入图片描述
我们自己配置的属性都被集中保存在了最后一个OriginTrackedMapPropertySource的source中,默认的实现用了不允许修改的Map。我们@Value注入的属性就是通过查找propertySource中的属性值设置的,因此我们只需要把加载的配置写入自定义的PropertySource中就可以在项目运行中从Environment获取。

ZK方式:
配置Zookeeper,先导入依赖,

<dependency>
    <groupId>org.apache.curator</groupId>
    <artifactId>curator-framework</artifactId>
    <version>2.12.0</version>
</dependency>
<dependency>
    <groupId>org.apache.curator</groupId>
    <artifactId>curator-recipes</artifactId>
    <version>2.12.0</version>
</dependency>

将ZK的服务地址配置在application.properties中

zookeeper.address=127.0.0.1:2181

配置ZK的客户端:

/**
 * @author lipeng
 * @date 2021/8/14
 */
@Configuration
public class ZKConfig {

    @Value("${zookeeper.address}")
    private String zkAddr;

    @Bean
    public CuratorFramework curatorFramework() {
        CuratorFramework curatorFramework = CuratorFrameworkFactory.builder()
                .connectString(zkAddr)
                .sessionTimeoutMs(5000)
                .retryPolicy(new ExponentialBackoffRetry(1000, 3))
                .build();
        curatorFramework.start();
        return curatorFramework;
    }
}

编写从ZK获取属性,并设置到Environment中:

/**
 * @author lipeng
 * @date 2021/8/14
 */
@Component
public class PropertySetting {

    private static final String PATH = "/config";

    /**
     * 用来找对应的propertySource
     */
    private static final String ZKPROPERTY = "config resource zookeeper";

    private static final String SCOPE = "ljhScope";

    private static ConcurrentHashMap<String, String> sourceMap = new ConcurrentHashMap();

    @Resource
    private ConfigurableApplicationContext applicationContext;

    @Resource
    private CuratorFramework curatorFramework;

    @PostConstruct
    public void configProperty() {
        try {
            Stat stat = curatorFramework.checkExists().forPath(PATH);
            if (stat == null) {
                throw new RuntimeException("zookeeper上没有配置参数");
            }
            addPropertiesToSpringEnvironment();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 初始化属性-从zk加载
     */
    private void addPropertiesToSpringEnvironment() {
        ConfigurableEnvironment environment = applicationContext.getEnvironment();
        MutablePropertySources mutablePropertySources = environment.getPropertySources();
        //创建属性
        OriginTrackedMapPropertySource originTrackedMapPropertySource = new OriginTrackedMapPropertySource(ZKPROPERTY, sourceMap);
        //遍历zk节点下的配置
        try {
            List<String> paths = curatorFramework.getChildren().forPath(PATH);
            for (String path : paths) {
                sourceMap.put(path, new String(curatorFramework.getData().forPath(PATH + "/" + path)));
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        mutablePropertySources.addLast(originTrackedMapPropertySource);
    }
}

现在已经把属性注入到了环境变量中,但是ConfigController实例可能已经初始化了,所以为了验证,我把PropertySetting实例在ConfigController之前初始化,使用了Spring的org.springframework.beans.factory.config.SmartInstantiationAwareBeanPostProcessor接口,示例如下:

/**
 * @author lipeng
 * @date 2021/8/14
 */
@Component
public class ConfigInstantiationAwareBeanPostProcessor implements SmartInstantiationAwareBeanPostProcessor {

    @Resource
    private ConfigurableApplicationContext applicationContext;

    @Override
    public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException {
        if (beanClass == ConfigController.class) {
            applicationContext.getBean(PropertySetting.class);
        }
        return null;
    }
}

我把application.properties中的name属性注释掉,在ZK的/config/name下写入baba,现在运行结果两个属性输出都是baba。
在这里插入图片描述

这个时候我们在ZK上修改属性,程序还没有任何反应,所以需要在PropertySetting中加入配置变更通知,新加入代码如下:
在这里插入图片描述

/**
     * 添加子节点的监听
     */
    private void childNodeListener() {
        try {
            PathChildrenCache cache = new PathChildrenCache(curatorFramework, PATH, false);
            cache.start(PathChildrenCache.StartMode.POST_INITIALIZED_EVENT);
            // Normal / BUILD_INITIAL_CACHE /POST_INITIALIZED_EVENT

            cache.getListenable().addListener(new PathChildrenCacheListener() {
                @Override
                public void childEvent(CuratorFramework curatorFramework, PathChildrenCacheEvent pathChildrenCacheEvent) {
                    switch (pathChildrenCacheEvent.getType()) {
                        case CHILD_ADDED:
                            System.out.println("增加子节点");
                            //添加新属性到环境中
                            addPropertyToSpringEnvironment(pathChildrenCacheEvent.getData());
                            break;
                        case CHILD_REMOVED:
                            System.out.println("删除子节点");
                            deletePropertyFromSpringEnvironment(pathChildrenCacheEvent.getData());
                            break;
                        case CHILD_UPDATED:
                            System.out.println("更新子节点");
                            addPropertyToSpringEnvironment(pathChildrenCacheEvent.getData());
                            break;
                        default:
                            break;
                    }
                }
            });
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 删除一个属性
     *
     * @param data
     */
    private void deletePropertyFromSpringEnvironment(ChildData data) {
        sourceMap.remove(data.getPath().substring(PATH.length() + 1));
    }

    /**
     * 添加一个属性
     *
     * @param data
     */
    private void addPropertyToSpringEnvironment(ChildData data) {
        try {
            byte[] value = curatorFramework.getData().forPath(data.getPath());
            sourceMap.put(data.getPath().substring(PATH.length() + 1), new String(value));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

现在重新运行程序,在ZK中修改name的值后会发现environment.getProperty("name")输出新设置的值,但是通过@Value引入的值还是启动时的值。为什么呢?因为@Value的值在Bean初始化的时候就已经设置完成了,尽管环境变量中的值已被修改,但是不会重新读取,所以还是原来的值。那么为了解决这个问题,我们就需要在配置修改的时候,去动态的刷新这些Bean,让他们重新从环境中读取一次配置。这里我自定义了一个scope,将需要动态刷新的Bean都使用自定义的Scope来维持。代码如下:

/**
 * @author lipeng
 * @date 2021/8/14
 */
public class RefreshScope implements Scope {

    private ConcurrentHashMap map = new ConcurrentHashMap();

    @Override
    public Object get(String name, ObjectFactory<?> objectFactory) {
        if (map.containsKey(name)) {
            return map.get(name);
        } else {
        	//执行Bean初始化中的createBean方法
            Object obj = objectFactory.getObject();
            map.put(name, obj);
            return obj;
        }
    }

    @Override
    public Object remove(String name) {
        return map.remove(name);
    }

    @Override
    public void registerDestructionCallback(String name, Runnable callback) {

    }

    @Override
    public Object resolveContextualObject(String key) {
        return null;
    }

    @Override
    public String getConversationId() {
        return null;
    }
}

对应的Spring源码部分为:
在这里插入图片描述
只是定义没用,需要注册到Spring容器中,代码如下:

/**
 * @author lipeng
 * @date 2021/8/14
 */
@Getter
@Component
public class RefreshScopeRegistry implements BeanDefinitionRegistryPostProcessor {

    private BeanDefinitionRegistry registry;

    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
        this.registry = registry;
    }

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        beanFactory.registerScope("ljhScope", new RefreshScope());
    }
}

现在在ConfigController上配置自定义的Scope,代码如下:
在这里插入图片描述
到这里已经将需要动态修改的Bean都保存到了RefreshScope的Map中,现在只需要在watcher监听到的事件中加入刷新Bean的动作就可以了。如下:
在这里插入图片描述

@Resource
private RefreshScopeRegistry registry;

/**
 * 属性变动,通知刷新Bean
 */
private void refreshBeans() {
    //找出需要刷新的Bean
    BeanDefinitionRegistry beanDefinitionRegistry = registry.getRegistry();
    Arrays.stream(applicationContext.getBeanDefinitionNames()).forEach(beanDefinitionName -> {
        BeanDefinition beanDefinition = beanDefinitionRegistry.getBeanDefinition(beanDefinitionName);
        if (beanDefinition.getScope().equals(SCOPE)) {
            //从scope中删除原有的Bean
            applicationContext.getBeanFactory().destroyScopedBean(beanDefinitionName);
            //重新创建Bean
            applicationContext.getBean(beanDefinitionName);
        }
    });
}

现在运行程序,在ZK中修改属性值,可以发现不管通过Environment还是通过@Value都可以获取到最新的值。
大功告成~

本文示例的源代码下载

  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2021-08-17 15:15:56  更:2021-08-17 15:17:55 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/21 2:34:25-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码