聊一聊生产环境中如何动态监听配置文件变化并重载

上一篇,我们谈到Java中的几种读取properties配置文件的方式,但是在生产环境中,最忌讳的就是重启应用了。比如某个系统的路径常量或者接口变更,需要线上及时生效,但是又不能轻易重启服务,那么如何做到平滑变更?下面就来聊一聊,如何实现配置文件的监听和重载。

timg.jpg

实现方式

PropertiesConfiguration

PropertiesConfiguration是一个配置文件的加载工具类,封装了从配置文件里获取值并转化为基本数据类型的方,依赖commons-configuration-1.10.jar。

PropertyUtil.java:

package com.itstyle.web.util;

import org.apache.commons.configuration.ConfigurationException;
import org.apache.commons.configuration.PropertiesConfiguration;
import org.apache.commons.configuration.reloading.FileChangedReloadingStrategy;
/**
 * 读取配置文件,并实现对配置文件的动态修改和自动保存(开关配置)
 * 创建者    科帮网
 * 创建时间    2017年5月12日
 *
 */
public class PropertyUtil {
    private PropertyUtil(){};
    
    private static class SingletonHolder{
        private static PropertiesConfiguration propertyUtil = init("config.properties");
    }
    public static PropertiesConfiguration getProps(){
        return SingletonHolder.propertyUtil;
    }
    /**
     * 初始化
     * @param propertiesFile
     * @see
     */
    private static PropertiesConfiguration init(String propertiesFile) {
        PropertiesConfiguration  PropertyUtil = null;
        try {
            PropertyUtil = new PropertiesConfiguration(propertiesFile);
            //自动重新加载
            PropertyUtil.setReloadingStrategy(new FileChangedReloadingStrategy());
            //自动保存 
            PropertyUtil.setAutoSave(true);
        } catch (ConfigurationException e) {
            e.printStackTrace();
        }
        return PropertyUtil;
    }

    /**
     * 根据Key获得对应的value
     *
     * @param key
     * @return
     * @see
     */
    public static Object getProperty(String key) {
        return getProps().getProperty(key);
    }

    /**
     * 设置属性
     *
     * @param key
     * @param value
     * @see
     */
    public static void setProperty(String key, String value) {
        getProps().setProperty(key, value);
    }
}

WatchService JDK7+

该类的对象就是操作系统原生的文件系统监控器!我们都知道OS自己的文件系统监控器可以监控系统上所有文件的变化,这种监控是无需遍历、无需比较的,是一种基于信号收发的监控,因此效率一定是最高的。现在Java对其进行了包装,可以直接在Java程序中使用OS的文件系统监控器了。

配置监听 ResourceListener.java

package com.itstyle.web.common.listener;
import java.io.IOException;
import java.nio.file.FileSystems;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardWatchEventKinds;
import java.nio.file.WatchEvent;
import java.nio.file.WatchKey;
import java.nio.file.WatchService;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import com.acts.web.common.utils.LogUtil;
import com.acts.web.common.utils.PropertyUtil;
/**
 * 监控配置文件变化
 * 创建者 科帮网
 * 创建时间    2017年5月12日
 *
 */
public class ResourceListener {
    
    private static ExecutorService fixedThreadPool = Executors
            .newFixedThreadPool(5);
    private WatchService ws;
    private String listenerPath;
    
    private ResourceListener(String path) {
        try {
            ws = FileSystems.getDefault().newWatchService();
            this.listenerPath = path;
            start();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private void start() {
        fixedThreadPool.execute(new Listner(ws, this.listenerPath));
    }

    public static void addListener(String path) throws IOException {
        ResourceListener resourceListener = new ResourceListener(path);
        Path p = Paths.get(path);
        p.register(resourceListener.ws, StandardWatchEventKinds.ENTRY_MODIFY,
                StandardWatchEventKinds.ENTRY_DELETE,
                StandardWatchEventKinds.ENTRY_CREATE);
    }

    public static void main(String[] args) throws IOException {
        ResourceListener.addListener("E:/test");
    }
}

class Listner implements Runnable {
    private WatchService service;
    private String rootPath;

    public Listner(WatchService service, String rootPath) {
        this.service = service;
        this.rootPath = rootPath;
    }

    public void run() {
        try {
            while (true) {
                WatchKey watchKey = service.take();
                List<WatchEvent<?>> watchEvents = watchKey.pollEvents();
                for (WatchEvent<?> event : watchEvents) {
                    String path = event.context().toString();
                    if(path.endsWith("config.properties")){
                        LogUtil.info("[" + rootPath + "/" + event.context()+ "]文件发生了[" + event.kind() + "]事件");
                        PropertyUtil.init();//配置变更初始化
                    }
                }
                watchKey.reset();
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                service.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

工具类 PropertyUtil.java

package com.itstyle.web.common.utils;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
/**
 * properties文件获取工具类
 * 创建者    科帮网
 * 创建时间    2017年5月12日
 *
 */
public class PropertyUtil {
    private PropertyUtil(){};
    
    private static class SingletonHolder{
        private static Properties props  = loadProps();
    }
    public static Properties getProps(){
        return SingletonHolder.props;
    }
    private static  Properties loadProps(){
        Properties props = new Properties();
        InputStream in = null;
        try {
            in = PropertyUtil.class.getClassLoader().getResourceAsStream("config.properties");
            props.load(in);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if(null != in) {
                    in.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return props;
    }

    public static String getProperty(String key){
        return getProps().getProperty(key);
    }

    public static String getProperty(String key, String defaultValue) {
        return getProps().getProperty(key, defaultValue);
    }
    public static void init(){//不要随便调用(不是私有的)
        SingletonHolder.props =  loadProps();
    }
}

总结

对比以上两种实现方式,各有优劣,大家可以根据实际情况选择合适的方式。

当然如果涉及的应用或者系统较多,就应该考虑使用配置中心进行统一配置了。

qrcode_for_gh_bf7a27ade681_258.jpg

作者: 小柒

出处: https://blog.52itstyle.com

分享是快乐的,也见证了个人成长历程,文章大多都是工作经验总结以及平时学习积累,基于自身认知不足之处在所难免,也请大家指正,共同进步。

本文版权归作者所有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出, 如有问题, 可邮件(345849402@qq.com)咨询。