zoukankan      html  css  js  c++  java
  • 细数Java项目中用过的配置文件(properties篇)

    灵魂拷问:在不重启服务的前提下,如何让配置修改生效的呢?有什么奇技淫巧吗?

    灵魂拷问:在 Java 项目中,总能看到以 .properties 为后缀的文件踪影,这类配置文件是怎么加载的呢?

    项目研发过程中,总会遇到一些经常改变的参数,比如要连接的数据库的连接地址、名称、用户名、密码;再比如访问三方服务的 URL 等等。考虑到程序的通用性,这些参数往往不能直接写死在程序里,通常借助配置文件来优雅处理。

     在 Java 项目中,properties 文件当属使用较简单一类,不过虽然简单,还是要好好说说项目中都是怎么使用的,尝试通过源码解读,让你真正懂它,并带你深刻体会 Java 中重载的意义。

    1. 虽说简单,格式还是要看一看。

    相比上次谈及的 ini 配置文件,properties 文件格式没有 Section (节)的概念,反而是简单了不少。

     

     上图是一个 jdbc 连接所需要的配置,其中以 # 开始的每一行是注释信息,而以等号分割的每行配置,就是常说的键-值对,等号左边的为 key(代码中的变量),等号右边的为 value(是依据实际场景而配置的值)。

    2. 虽说简单,Java 源码还是去要看看。

    在 Java 中提供了 java.util.Properties 类,主要用于对配置文件的读写操作。

    一图掌握血缘关系,很显然 Properties 继承自 Hashtable,归根结底是个 Map,而 Properties 最特殊的地方,就是它的键和值都是字符串类型。

    从全局了解梗概,然后走进 JDK 源码,按照思路,步步去深入。

    首先,要看看写好的配置文件是怎么加载的?

    源码很清晰,提供字符流 Reader、字节流 InputStream两种方式加载配置文件(方法重载的目的:让使用者更方便),再深入去看最终会调用 Hashtable 的 put(key, value) 来设置键值对,最终完成配置文件中的加载。

    然后,要看看怎么根据 key 获得对应的 value(放进去了,还要考虑拿出来)?

    源码很清晰,通过参数 key 获得对应的 value,考虑到使用者的方便,对 getProperty 方法进行了重载,其中标注 2 的方法,当根据 key 获得值是 null 时,会返回一个调用方法时传入的默认值(很多场景下,确实很有用)。

    知道了怎么加载配置文件,知道了怎么获取 key 对应的值,按照常理说,项目中已经够用了,但是有些时候项目启动后,还真需要再额外设置一下参数的值,不过没关系,因为 Java 已经想到了这一点,对外提供了 setProperty 的方法,让额外设置参数成为可能。

    若能在项目研发中,熟练使用上面提到的这些 API,已经足矣。既然打开了源码,索性把杂七杂八的都提提,说不定某些 API 也能解决你碰到的其它场景的问题呢。

    Properties 类不仅提供了 load 进行加载配置,而且还提供了把键值对写到文件中的能力。如上面源码所示,考虑到使用者的方便,对 store 方法进行重载,提供了面向字节流 OutputStream、字符流 Writer 两种方式的 store。

    如上面源码所示,Properties 除了提供对常规配置文件读写能力支撑,对 xml 配置文件加载、写入也提供了支撑。

    如上图源码所示,Properties 类提供了重载的 list 方法,为了方便调试,可以把键值对列表给整齐的打印出来。

    3. 虽说简单,不能赋予实践一切都是扯淡。

    场景一:在 APM 性能监控时,获取 Java 应用画像信息常用 API。

    import java.util.Properties;
    
    /**
     * @author 一猿小讲
     */
    public class JVMDetails {
        public static void main(String[] args) {
            // 获取系统信息(JDK信息、Java虚拟机信息、Java提供商信息、当前计算机用户信息)
            Properties properties = System.getProperties();
            // 把系统信息打印一下
            properties.list(System.out);
        }
    }
    

    程序跑起来,部分输出截图示意如下。

    场景二:业务开发中,让配置替代硬编码,并考虑配置更新时,程序能够读到最新的值。

    import java.io.*;
    import java.util.Hashtable;
    import java.util.Properties;
    
    /**
     * 配置文件工具类
     * 1. 支持加载 .properties文件、.ini文件
     * 2. 支持配置文件更新
     * @author 一猿小讲
     */
    public class PropertiesUtil {
    
        // private static final Log4j LOG =  .....;
    
        private static Hashtable<String, PropCache> propCache = new Hashtable<String, PropCache>();
    
        public static String getString(String propFile, String key) {
            return getString(propFile, key, null);
        }
    
        public static String getString(String propFile, String key, String defaultValue) {
            if ((!propFile.endsWith(".properties")) && (!propFile.endsWith(".ini"))) {
                propFile = propFile + ".properties";
            }
            PropCache prop = propCache.get(propFile);
            if (prop == null) {
                try {
                    prop = new PropCache(propFile);
                } catch (IOException e) {
                    // LOG.warn(e);
                    System.out.println(String.format("读取 %s 出现异常%s", propFile,e));
                    return defaultValue;
                }
                propCache.put(propFile, prop);
            }
            String value = prop.getProperty(key, defaultValue);
            if (value != null) {
                value = value.trim();
            }
            return value;
        }
    
        public static void setString(String propFile, String key, String value)
                throws IOException {
            if ((!propFile.endsWith(".properties")) && (!propFile.endsWith(".ini"))) {
                propFile = propFile + ".properties";
            }
            File file = new File(propFile);
            Properties prop = new Properties();
            try {
                FileInputStream fis = new FileInputStream(file);
                prop.load(fis);
                fis.close();
            } catch (FileNotFoundException e) {
                // LOG.warn(e);
                System.out.println(String.format("文件 %s 不存在", propFile));
            }
            FileOutputStream fos = new FileOutputStream(file);
            prop.put(key, value);
            prop.store(fos, null);
            fos.close();
            PropCache localPropCache = propCache.get(propFile);
            if (localPropCache != null) {
                localPropCache.reload();
            }
        }
    
        private static class PropCache {
    
            private String fileName;
            private long lastLoad;
            private Properties prop;
    
            public PropCache(String propFileName) throws IOException {
                File file = new File(propFileName);
                FileInputStream fis = new FileInputStream(file);
                this.prop = new Properties();
                this.prop.load(fis);
                fis.close();
                this.fileName = file.getAbsolutePath();
                this.lastLoad = file.lastModified();
            }
    
            public String getProperty(String key, String defaultValue) {
                File file = new File(this.fileName);
                if (this.lastLoad < file.lastModified()) {
                    reload();
                }
                return this.prop.getProperty(key, defaultValue);
            }
    
            public void reload() {
                File file = new File(this.fileName);
                try {
                    Properties prop = new Properties();
                    FileInputStream fis = new FileInputStream(file);
                    prop.load(fis);
                    fis.close();
                    this.prop.clear();
                    this.prop.putAll(prop);
                    this.lastLoad = file.lastModified();
                } catch (IOException e) {
                    // PropertiesUtil.LOG.warn(e);
                    System.out.println(String.format("文件 %s 重新加载出现异常%s", this.fileName, e));
                }
                // PropertiesUtil.LOG.all(new Object[]{this.fileName, " reloaded."});
                System.out.println(String.format("文件 %s 重新加载完毕", this.fileName));
            }
        }
    }
    

    借助开篇提到的 jdbc 配置文件,进行验证。尝试获取数据库类型,默认配置为 db2,中途修改参数的值为 mysql,看看效果如何?

    /**
     * 测试类
     * @author 一猿小讲
     */
    public class M {
        public static void main(String[] args) {
            while(true) {
                System.out.println(PropertiesUtil.getString("db", "jdbc.type"));
                try {
                    Thread.sleep(10000);
                } catch (InterruptedException e) {
                }
            }
        }
    }
    

    程序跑起来,效果还是让人很满意。

    4. 虽说简单,洋洋洒洒分享一大篇。

    有关配置文件的分享网上有很多,而我们的分享却显得不太一样。

    我们的初衷是:结合实际项目及源码,说说这些年用过的那些有关配置的奇技淫巧,帮你提高研发能力(那怕是提高一丢丢,就算成功)。

    它山之石可以攻玉,相信会对你有所帮助。

    为了能够帮你提高研发能力(那怕是提高一丢丢呢),后续将继续结合实际项目,看看用到的其它形式的配置文件,敬请期待。

  • 相关阅读:
    魔法方法中的__str__和__repr__区别
    新建分类目录后,点击显示错误页面?
    3.用while和for循环分别计算100以内奇数和偶数的和,并输出。
    2.for循环实现打印1到10
    1.while循环实现打印1到10
    021_for语句
    014_运算符_字符串连接
    020_while语句
    019_增强switch语句
    018_switch语句
  • 原文地址:https://www.cnblogs.com/socoool/p/12683501.html
Copyright © 2011-2022 走看看