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 条评论
登录 后参与评论

相关文章

来自专栏FreeBuf

Python黑客学习笔记:从HelloWorld到编写PoC(上)

本系列文章适合CS在读学生和万年工具党,本文会在英文原文的基础上做些修改,并适当增加些解释说明。 ? 本篇包含原文的前几部分: 0x0 – Getting St...

18110
来自专栏逸鹏说道

C# 温故而知新:Stream篇(六)

BufferedStream 目录: 简单介绍一下BufferedStream 如何理解缓冲区? BufferedStream的优势 从BufferedStre...

3305
来自专栏柠檬先生

Angularjs基础(四)

AngularJS过滤器     过滤器可以使用一个管道符(|)添加到表达式和指令中。       AngularJS过滤器可用于转换数据:    ...

1849
来自专栏进击的君君的前端之路

jQuery实现图片懒加载

2432
来自专栏haifeiWu与他朋友们的专栏

Netty源码中对Redis协议的实现

近期一直在做网络协议相关的工作,所以博客也就与之相关的比较多,今天楼主结合 Redis的协议 RESP 看看在 Netty 源码中是如何实现的。

532
来自专栏无所事事者爱嘲笑

经常遇到的浏览器的兼容性有哪些?

1395
来自专栏Android 研究

APK安装流程详解6——PackageManagerService启动前奏

由于在后面讲解PackageManager流程启动的时候会 涉及到Setting类,我们就先预热下 Settings.java源码地址

782
来自专栏haifeiWu与他朋友们的专栏

Netty 源码中对 Redis 协议的实现

近期一直在做网络协议相关的工作,所以博客也就与之相关的比较多,今天楼主结合 Redis的协议 RESP 看看在 Netty 源码中是如何实现的。

735
来自专栏用户2442861的专栏

我的VS2010+VAssistX

最近越来越觉得VAssistX好用,可能是以前没有去仔细研究过吧,也可能是因为我是个快捷键控吧,不管怎样,用或不用,方便或不方便,它就是那里,一动也不动,进入...

301
来自专栏GreenLeaves

EF 约定介绍

当前环境为EF Code First开发模式中 一、EF默认约定 1、常用约定 (1)、当没有显示指定实体主键的时候,EF会默认将长得最像Id的属性(且类型为G...

18610

扫码关注云+社区