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

注意,commons-configuration2 2.4以上版本已经内置了UNICODE非转义特性,参见本文最后的《补记》章节

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 = new PropertiesConfiguration();
	// 指定使用自定义的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 = 中文测试

补记

感谢@JayHooz的评论提醒:

commons-configuration2的 2.4版本之后,PropertiesConfiguration自身实现的JupIOFactory,支持不使用unicode转义,更贴合java.util.properties能力

经验证,与我写的MyIOFactory效果一致,使用commons-configuration2的开发者不需要再重复造轮子了。如下为测试代码:

import java.io.File;
import java.io.FileOutputStream;
import java.io.OutputStreamWriter;

import org.apache.commons.configuration2.PropertiesConfiguration;
import org.junit.FixMethodOrder;
import org.junit.Test;
import org.junit.runners.MethodSorters;
import org.apache.commons.configuration2.PropertiesConfiguration.JupIOFactory;


@FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class IOFactoryTest {

	@Test
	public void test1IoFactoryNoescape() {
		try {
		    PropertiesConfiguration config = new PropertiesConfiguration();
		    	    // 指定使用自定义的MyPropertiesWriter实例来完成文件写操作
		    	    config.setIOFactory(new MyIOFactory);
		    	    config.setProperty("database.jdbc.username", "中文测试-by MyIOFactory");
		    	    // 创建OutputStreamWriter实例,指定编码为"UTF-8"
		    	    OutputStreamWriter wirter = new OutputStreamWriter(
		    	        new FileOutputStream(new File("d:\\tmp\\test.properties")), "UTF-8");
		    	    config.write(wirter);
		    	    wirter.close();
		} catch (Throwable e) {
			e.printStackTrace();
			fail();
		}
	}
	@Test
	public void test2JupIOFactory() {
		try {
			PropertiesConfiguration config = new PropertiesConfiguration();
			// 指定使用自定义的MyPropertiesWriter实例来完成文件写操作
			config.setIOFactory(new JupIOFactory(false));
			config.setProperty("database.jdbc.username", "中文测试-by JupIOFactory");
			// 创建OutputStreamWriter实例,指定编码为"UTF-8"
			OutputStreamWriter wirter = new OutputStreamWriter(
					new FileOutputStream(new File("d:\\tmp\\test.properties")), "UTF-8");
			config.write(wirter);
			wirter.close();
		} catch (Throwable e) {
			e.printStackTrace();
			fail();
		}
	}

}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

10km

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值