欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

细数Java项目中用过的配置文件(properties篇)

程序员文章站 2022-07-09 20:10:40
灵魂拷问:在不重启服务的前提下,如何让配置修改生效的呢?有什么奇技淫巧吗? 灵魂拷问:在 Java 项目中,总能看到以 .properties 为后缀的文件踪影,这类配置文件是怎么加载的呢? 项目研发过程中,总会遇到一些经常改变的参数,比如要连接的数据库的连接地址、名称、用户名、密码;再比如访问三方 ......

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

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

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

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

 

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

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

 细数Java项目中用过的配置文件(properties篇)

 

 

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

 

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

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

细数Java项目中用过的配置文件(properties篇)

 

 

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

 

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

 

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

细数Java项目中用过的配置文件(properties篇)

 

 

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

细数Java项目中用过的配置文件(properties篇)

 

 

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

细数Java项目中用过的配置文件(properties篇)

 

 

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

 

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

细数Java项目中用过的配置文件(properties篇)

 

 

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

细数Java项目中用过的配置文件(properties篇)

 

 

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

细数Java项目中用过的配置文件(properties篇)

 

 

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

细数Java项目中用过的配置文件(properties篇)

 

 

如上图源码所示,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);
    }
}

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

细数Java项目中用过的配置文件(properties篇)

 

 

 

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

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) {
            }
        }
    }
}

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

细数Java项目中用过的配置文件(properties篇)

 

 

 

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

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

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

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

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

细数Java项目中用过的配置文件(properties篇)