commons-configuration2:properties文件写入中文(no escape)

properties 是java标准支持的配置文件格式,默认编码ISO 8859-1,unicode字符会被转义(Unicode escapes)

参见 https://docs.oracle.com/javase/6/docs/api/java/util/Properties.html?is-external=true

所以在使用commons-configuration2写properties文件时,即使你将编码设置为UTF-8,写入properties文件的中文也会被转义。就像下面这段代码:

    PropertiesConfiguration config = ...
    config.setProperty("database.jdbc.username", "中文测试");
    // 创建OutputStreamWriter实例,指定编码为"UTF-8"
    OutputStreamWriter wirter = new OutputStreamWriter(
        new FileOutputStream(new File("d:\\tmp\\test.properties")), "UTF-8");
    config.write(wirter);
    wirter.close();

生成的test.properties文件内容如下

database.jdbc.username = \u4E2D\u6587\u6D4B\u8BD5

不完美

如果只考虑properties配置被程序读写的情况,这并不是问题。 但在工程应用中,有的时候我们需要在应用环境手工编辑properties配置,这种情况下就尴尬了,根本看不懂啊。

所以我不需要commons-configuration2写properties文件时对uncode字符转义.

为了解决这个问题,花时间研究了commons-configuration2的代码,搞清楚了状况: properties文件的写操作是由org.apache.commons.configuration2.PropertiesConfiguration.PropertiesWriter类实现的。 具体是由下面这个PropertiesWriter类中的 ValueTransformer接口实例实现的(不得不说apache旗下的开源项目质量很高,文档注释真是很完备,变量方法命名规范,看注释就够清晰了)

    /**
     * A translator for escaping property values. This translator performs a
     * subset of transformations done by the ESCAPE_JAVA translator from Commons
     * Lang 3.
     */
    // escape 转义实现
    private static final CharSequenceTranslator ESCAPE_PROPERTIES = new AggregateTranslator(
            new LookupTranslator(new String[][] { { "\\", "\\\\" } }),
            new LookupTranslator(EntityArrays.JAVA_CTRL_CHARS_ESCAPE()), UnicodeEscaper.outsideOf(32, 0x7f));

    /**
     * A {@code ValueTransformer} implementation used to escape property values.
     * This implementation applies the transformation defined by the
     * {@link #ESCAPE_PROPERTIES} translator.
     */
    private static final ValueTransformer TRANSFORMER = new ValueTransformer() {
        @Override
        public Object transformValue(Object value) {
            // value转String
            String strVal = String.valueOf(value);
            // 调用ESCAPE_PROPERTIES完成escape
            return ESCAPE_PROPERTIES.translate(strVal);
        }
    };

找到了问题原因就好办了,因为properties文件对unicode字符转义是java默认标准,所以这个类没有什么开关可以禁止escape, 但看上面这个transformValue方法中,第一步先将value转为String,第二步将String转义,如果省掉这第二步,直接返回String不就满足要求了么?

MyPropertiesWriter

所以解决办法就是自己定义一个PropertiesWriter子类,重写调用TRANSFORMER 常量的writeProperty(String key, Object value, boolean forceSingleLine)方法(TRANSFORMER常量只被writeProperty方法使用)。 在writeProperty方法中调用自己写的ValueTransformer就可以了。 下面是代码实现 MyPropertiesWriter.java

package net.gdface.facelog.service;

import java.io.IOException;
import java.io.Writer;
import java.util.List;

import org.apache.commons.configuration2.PropertiesConfiguration.PropertiesWriter;
import org.apache.commons.configuration2.convert.ListDelimiterHandler;
import org.apache.commons.configuration2.convert.ValueTransformer;

/**
 * 实现properties文件中unicode字符不转义(escape)直接输出
 * @author guyadong
 *
 */
public class MyPropertiesWriter extends PropertiesWriter {
    // 自定义实现的ValueTransformer,直接返回String,没有escape过程
    private final ValueTransformer TRANSFORMER = new ValueTransformer(){

        @Override
        public Object transformValue(Object value) {
            //
            return String.valueOf(value);
        }};
    public MyPropertiesWriter(Writer writer, ListDelimiterHandler delHandler) {
        super(writer, delHandler);
    }

    /**
     * 代码从父类方法中原样复制没有改变,主要是为调用自定义的TRANSFORMER 
     */
    @Override
    public void writeProperty(String key, Object value, boolean forceSingleLine) throws IOException {
        String v;

        if (value instanceof List)
        {
            v = null;
            List<?> values = (List<?>) value;
            if (forceSingleLine)
            {
                try
                {
                    v = String.valueOf(getDelimiterHandler()
                                    .escapeList(values, TRANSFORMER));
                }
                catch (UnsupportedOperationException uoex)
                {
                    // the handler may not support escaping lists,
                    // then the list is written in multiple lines
                }
            }
            if (v == null)
            {
                writeProperty(key, values);
                return;
            }
        }
        else
        {
            v = String.valueOf(getDelimiterHandler().escape(value, TRANSFORMER));
        }

        write(escapeKey(key));
        write(fetchSeparator(key, value));
        write(v);

        writeln(null);
    }
}

MyIOFactory

有了自定义的MyPropertiesWriter如何让PropertiesConfiguration使用它呢?

进一步研究代码,可以知道PropertiesConfiguration是通过 IOFactory接口来获取PropertiesWriter实例的。 参见PropertiesConfiguration.setIOFactory(IOFactory ioFactory)方法。 commons-configuration2提供的IOFactory 默认实现DefaultIOFactory类提供的就是PropertiesWriter实例。 所以我们可以自实现一个返回MyPropertiesWriter实例的IOFactory通过上面setIOFactory方法提供给PropertiesConfiguration就可以了。

下面是IOFactory实现代码 MyIOFactory.java

package net.gdface.facelog.service;

import java.io.Writer;

import org.apache.commons.configuration2.PropertiesConfiguration.DefaultIOFactory;
import org.apache.commons.configuration2.PropertiesConfiguration.PropertiesWriter;
import org.apache.commons.configuration2.convert.ListDelimiterHandler;

public class MyIOFactory extends DefaultIOFactory {

    @Override
    public PropertiesWriter createPropertiesWriter(Writer out, ListDelimiterHandler handler) {
        // 返回 MyPropertiesWriter实例
        return new MyPropertiesWriter(out, handler);
    }

}

最后一步

再回到本文最开始的那段代码,只需要增一行setIOFactory方法调用代码就大功告成:

    PropertiesConfiguration config = ...
    // 指定使用自定义的MyPropertiesWriter实例来完成文件写操作
    config.setIOFactory(new MyIOFactory());
    config.setProperty("database.jdbc.username", "中文测试");
    // 创建OutputStreamWriter实例,指定编码为"UTF-8"
    OutputStreamWriter wirter = new OutputStreamWriter(
        new FileOutputStream(new File("d:\\tmp\\test.properties")), "UTF-8");
    config.write(wirter);
    wirter.close();

不用想结果肯定是正确的

database.jdbc.username = 中文测试

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏java 成神之路

编码规范

3549
来自专栏野路子程序员

Thinkphp修改一句代码,使得foreach标签支持对象,增加变量[数组对象]混合解析法!

2798
来自专栏進无尽的文章

编码篇 — 一个DataModel小例领略指针的魅力

做过的项目中曾经有这样的需求:货品录入(商品入库),弹框弹出所有的货品(很多),选择其中的一个,则下次弹框弹出所有货品时不再显示选择了的那件货品。当然,录入功能...

683
来自专栏点滴积累

shell脚本学习心得

近来主要捣鼓ubuntu,大多数项目中都用到了sh脚本作为启动脚本等,以前只是大概明白如何使用,今天需要自己修改并运行脚本就碰到了很多问题,所以决定静下心来学习...

2444
来自专栏生信宝典

NGS基础 - GTF/GFF文件格式解读和转换

GFF 文件 GFF全称为general feature format,这种格式主要是用来注释基因组。 从 Ensembl 导出的GFF文件示例: X E...

78710
来自专栏fixzd

redis系列:通过文章点赞排名案例学习sortedset命令

这一篇文章将讲述Redis中的sortedset类型命令,同样也是通过demo来讲述,其他部分这里就不在赘述了。

771
来自专栏CRPER折腾记

TypeScript 2 : 获取当前日期及前后范围日期【Array】

就是用时间戳进行换算,然后通过内置函数获取对应字段进行拼接,,这里没有带时分秒,有兴趣的可以加个可选参数把时分秒带上。。因为我这里不需要用到,所以我就没加进去了...

592
来自专栏linux驱动个人学习

ELF文件结构描述

ELF目标文件格式最前部ELF文件头(ELF Header),它包含了描述了整个文件的基本属性,比如ELF文件版本、目标机器型号、程序入口地址等。其中ELF文件...

3405
来自专栏数据小魔方

左手用R右手Python系列之——字符串格式化进阶

关于R语言字符串格式化之前无论是专题还是案例教程中均有所涉及,今日这一篇之所以重提是因为又找到了一个很好用的字符串格式化包。 这个包的语法源于Python风格,...

28212
来自专栏向治洪

浅谈前端JavaScript编程风格

前言 多家公司和组织已经公开了它们的风格规范,具体可参阅jscs.info,下面的内容主要参考了Airbnb的JavaScript风格规范。当然还有google...

1927

扫码关注云+社区