`
ajoo
  • 浏览: 453823 次
社区版块
存档分类
最新评论

动态properties转换

阅读更多
今天同事和我讨论他遇到的一个问题。具体要求是这样的,在运行时,我们会从模块G得到一个Map,这个Map里面都是一些字符串对,你可以理解成一个字典,有字符串的key和字符串的value。简短节说,就是
Map<String, String>

非常非常复杂深奥。

好,现在我们事先知道要从这个map里读取一些数据点,比如:id, name, sex等等。

对id,我们知道读出来的是int;对name,是string;对sex,应该对应一个叫Gender的enum类型。

这就涉及一个自动类型转换的问题。我们希望不用对每个数据点做手工类型转换。

另外一个需求,一些数据点是有缺省值的。比如name我们可以缺省为空字符串。
这样,如果map里面没有某个值,我们就看缺省值,如果有,就用这个缺省值,如果没有,就抛异常。

手工做的话,大概是这样:
String idValue = map.get("id");
if (idValue == null) {
  throw ...;
}
int id = Integer.parseInt(idValue);

String name = map.get("name");
if (name == null) {
  name = "";
}

String sexValue = map.get("sex");
if (sexValue == null) {
  throw ...;
}
Gender sex = Gender.valueOf(sexValue);
...


比较痛苦。于是做了一个动态代理:
public final class PropertyConverter<T> {
  private final Class<T> targetType;
  
  private PropertyConverter(Class<T> targetType) {...}

  public static <T> PropertyConverter<T> to(Class<T> targetType) {
    return new PropertyConverter<T>(targetType);
  }

  public T from(final Map<String, String> map) {
    return Proxy.newProxyInstance(
      new Class[]{targetType}, targetType.getClassLoader(), new InvocationHandler() {
        public Object invoke(Object proxy, Method method, Object[] args) {
          String value = map.get(method.getName());
          if (value == null) {
            Object defaultValue = method.getDefaultValue();
            if (defaultValue == null) {
              throw ...;
            }
            return defaultValue;
          }
          return convert(value, method.getReturnType());
        }
    });
  }
}


convert()函数是调用apache的ConvertUtilsBean做的,没什么说的。

那么,用法呢?

@interface Foo {
  int id();
  String name() default "";
  Gender sex();
}

Map<String, String> map = ...;
Foo foo = PropertyConverter.to(Foo.class).from(map);
foo.id();
foo.name();


这里面,对annotation的用法比较特别。不过不这么做,java也不提供一个简单并且类型安全的指定缺省值的方法。当然,如果你凑巧不需要缺省值,那么也不用annotation,直接用interface就好。
分享到:
评论
12 楼 ajoo 2008-05-08  
引用
很优雅,很有创意,改进了两点
1.能够自动为基本类型赋默认值,比如 int id();不用写成 int id() default 0,还有支持enum类型转化;

原始的需求是所有的域都是缺省强制的,如果map里面没有是要抛异常的。所以不能自动提供默认值。enum本来就是支持的,只要自己在converter bean里面注册一个enum converter就是了。ConvertUtils不能直接用,它对非法字符串保持沉默而不抛异常。所以要自己用ConvertUtilsBean。


引用
2.支持getter,方便jstl,ognl之类的表达式, name()和getName()是一样的

哦,在annotation里,没有getName()这个命名规范的。不用操心的吧?

其实,可以另外弄一个annotation来处理key的名字和方法名不匹配的情况:

@Rentention(RUNTIME)
@Target(METHOD)
@interface Property {
  String value();
}

@interface Foo {
  @Property("标识符")
  int id();
  String name() default "";
}
11 楼 armorking 2008-05-08  
myyate 写道
armorking 写道
ajoo 写道
bottom 写道
BeanUtils.copyProperties(foo, map);

这个,不管在必须的property不存在的情况下报错吧?而且,BeanUtils是1.5以前的吧?管enum么?


BeanUtils与ConvertUtilsBean都是“org.apache.commons.beanutils ”提供的
而且BeanUtils.copyProperties方法正是用ConvertUtilsBean实现的类型转换
如果BeanUtils搞不定enum的话,ConvertUtilsBean就可以了么?

ConvertUtilsBean和enum或者jdk1.5没有任何关系的,不就是进行enum转换嘛,你写个EnumConverter注册到ConvertUtilsBean就行了。


Converter接口的convert方法签名如下:
    public Object convert(Class type, Object value);


而Enum的valueOf方法签名如下:
public static <T extends Enum<T>> T valueOf(Class<T> enumType,
                                            String name);



我没用过JDK1.5,手边也没有合适的环境
所以,我想问一下:这个基于范型的EnumConverter类真的能实现么?
10 楼 myyate 2008-05-08  
armorking 写道
ajoo 写道
bottom 写道
BeanUtils.copyProperties(foo, map);

这个,不管在必须的property不存在的情况下报错吧?而且,BeanUtils是1.5以前的吧?管enum么?


BeanUtils与ConvertUtilsBean都是“org.apache.commons.beanutils ”提供的
而且BeanUtils.copyProperties方法正是用ConvertUtilsBean实现的类型转换
如果BeanUtils搞不定enum的话,ConvertUtilsBean就可以了么?

ConvertUtilsBean和enum或者jdk1.5没有任何关系的,不就是进行enum转换嘛,你写个EnumConverter注册到ConvertUtilsBean就行了。
9 楼 quaff 2008-05-08  
现在才知道@interface也是interface的一种,可以用来做动态代理的接口也可以被类实现.
8 楼 quaff 2008-05-08  
很优雅,很有创意,改进了两点
1.能够自动为基本类型赋默认值,比如 int id();不用写成 int id() default 0,还有支持enum类型转化;
2.支持getter,方便jstl,ognl之类的表达式, name()和getName()是一样的

public final class PropertyConverter<T> {
	private final Class<T> targetType;

	private PropertyConverter(Class<T> targetType) {
		this.targetType = targetType;
	}

	public static <T> PropertyConverter<T> to(Class<T> targetType) {
		return new PropertyConverter<T>(targetType);
	}

	public T from(final Map<String, String> map) {
		return (T) Proxy.newProxyInstance(targetType.getClassLoader(),
				new Class[] { targetType }, new InvocationHandler() {
					public Object invoke(Object proxy, Method method,
							Object[] args) {
						String name = method.getName();
						Class type = method.getReturnType();
						if (name.startsWith("get"))
							name = Character.toLowerCase(name.charAt(3))
									+ name.substring(4);
						if (name.startsWith("is") && type == boolean.class)
							name = Character.toLowerCase(name.charAt(2))
									+ name.substring(3);
						String value = map.get(name);
						if (value == null && !type.isPrimitive())
							return method.getDefaultValue();
						if(Enum.class.isAssignableFrom(type))
							return Enum.valueOf(type, value);
						return ConvertUtils.convert(value, type);
					}
				});
	}
}
7 楼 armorking 2008-05-08  
我也给一个基于org.apache.commons.collections.BeanMap的实现

import java.math.BigDecimal;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

import org.apache.commons.collections.BeanMap;

public abstract class MapInstantUtil
{
    public static void putAllPropertiesFromMap(
        Object targetObj,
        Map sourceMap)
    {
        if (targetObj == null
        ){
            return;
        }

        String className = targetObj.getClass().getName();
        Set essentialKeySet = searchEssentialKeySet(className);

        if (sourceMap == null
        ){
            if (essentialKeySet.size() > 0
            ){
                throw new IllegalArgumentException("this bean can not be instanted by an empty map!");
            }

            sourceMap = Collections.EMPTY_MAP;
        }

        Map beanMap = new BeanMap(targetObj);

        for (Iterator i = sourceMap.keySet().iterator(); i.hasNext() ; )
        {
            String key = (String)i.next();
            if (!beanMap.containsKey(key)
            ){
                continue;
            }

            Object value = sourceMap.get(key);
            if (value == null
            ){
                continue;
            }
            beanMap.put(key, value);
        }

        Map defaultValueMap = searchDefaultValueMap(className);
        for (Iterator i = defaultValueMap.keySet().iterator(); i.hasNext() ; )
        {
            String key = (String)i.next();

            if (!beanMap.containsKey(key)
            ){
                continue;
            }

            Object value = sourceMap.get(key);
            if (value != null
            ){
                continue;
            }

            beanMap.put(key, defaultValueMap.get(key));
        }

        for (Iterator i = essentialKeySet.iterator(); i.hasNext() ; )
        {
            String key = (String)i.next();

            if (!beanMap.containsKey(key)
            ){
                continue;
            }

            Object value = beanMap.get(key);
            if (value == null
            ){
                throw new IllegalArgumentException("the property(" + key + ") must not be null!");
            }
        }
    }


    //Map<String className, Map<String key, Object defaultValue>>
    private static Map DefaultValueMap_MAP;

    //Map<String className, Set<String essentialKey>>
    private static Map EssentialKeySet_MAP;

    static
    {
        DefaultValueMap_MAP = new HashMap();
        EssentialKeySet_MAP = new HashMap();

        //for Foo
        String className = Foo.class.getName();

        Map fooDefaultValueMap = new HashMap();
        fooDefaultValueMap.put("int_1", new Integer("1"));
        fooDefaultValueMap.put("int_1", "1");
        fooDefaultValueMap.put("bigdecimal_2", new BigDecimal("2"));
        fooDefaultValueMap.put("string_3", "3");
        fooDefaultValueMap.put("long_4", new Long("4"));
        fooDefaultValueMap.put("integer_7", new Integer("7"));
        DefaultValueMap_MAP.put(className, fooDefaultValueMap);

        Set fooEssentialKeySet = new HashSet();
        fooEssentialKeySet.add("short_5");
        fooEssentialKeySet.add("string_6");
        EssentialKeySet_MAP.put(className, fooEssentialKeySet);

        //TODO : for others
    }

    private static Map searchDefaultValueMap(String className)
    {
        return DefaultValueMap_MAP.containsKey(className)
            ? (Map)DefaultValueMap_MAP.get(className)
            : Collections.EMPTY_MAP
        ;
    }

    private static Set searchEssentialKeySet(String className)
    {
        return EssentialKeySet_MAP.containsKey(className)
            ? (Set)EssentialKeySet_MAP.get(className)
            : Collections.EMPTY_SET
        ;
    }

}


Foo
import java.math.BigDecimal;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import org.apache.commons.collections.BeanMap;

public class Foo
{

    //properties

    private int int_1;
    private BigDecimal bigdecimal_2;
    private String string_3;
    private long long_4;
    private short short_5;
    private String string_6;
    private Integer integer_7;

    //getter & setter

    public BigDecimal getBigdecimal_2()
    {
        return this.bigdecimal_2;
    }
    public void setBigdecimal_2(BigDecimal bigdecimal_2)
    {
        this.bigdecimal_2 = bigdecimal_2;
    }
    public int getInt_1()
    {
        return this.int_1;
    }
    public void setInt_1(int int_1)
    {
        this.int_1 = int_1;
    }
    public long getLong_4()
    {
        return this.long_4;
    }
    public void setLong_4(long long_4)
    {
        this.long_4 = long_4;
    }
    public short getShort_5()
    {
        return this.short_5;
    }
    public void setShort_5(short short_5)
    {
        this.short_5 = short_5;
    }
    public String getString_3()
    {
        return this.string_3;
    }
    public void setString_3(String string_3)
    {
        this.string_3 = string_3;
    }
    public String getString_6()
    {
        return this.string_6;
    }
    public void setString_6(String string_6)
    {
        this.string_6 = string_6;
    }

    public Integer getInteger_7()
    {
        return this.integer_7;
    }
    public void setInteger_7(Integer integer_7)
    {
        this.integer_7 = integer_7;
    }
    public static void main(String[] args)
    {
        Map sourceMap = new HashMap();
        sourceMap.put("short_5", new Short("55"));
        sourceMap.put("string_6", new String("66"));

        Foo foo = new Foo();
        MapInstantUtil.putAllPropertiesFromMap(foo, sourceMap);

        Map beanMap = new BeanMap(foo);
        for (Iterator i = beanMap.keySet().iterator(); i.hasNext() ; )
        {
            String key = (String)i.next();
            System.out.println("key : " + key + "\t\tvalue : " + beanMap.get(key));
        }
    }

}


测试结果
key : integer_7		value : 7
key : short_5		value : 55
key : string_3		value : 3
key : long_4		value : 4
key : bigdecimal_2		value : 2
key : int_1		value : 1
key : string_6		value : 66
6 楼 ajoo 2008-05-08  
主要是这个默认值,除了annotation想不到别的好办法解决。

别的人也给了些建议,比如自己写一个Property<T>类,在这个类里面实现类型转换和默认值处理。不过都要求比较多的代码,而且用起来客户代码还要依赖于一个不是很容易mock的Property类。反而不如动态代理简单干净,侵入性基本没有。用起来可以注射进一个接口,而不是PropertyConverter类,这样最容易测试。

你要用随便啦。反正都贴在blog里了,随便拷贝。
5 楼 sorphi 2008-05-08  
主贴的价值不在于类型转换,而是展示了annotation的优点。再说一句,真的用的很妙。

ajoo大哥,我能不能拿来用啊
/*
* @author ajoo.iteye.com
* @See http://www.iteye.com/topic/190440
*/
4 楼 armorking 2008-05-08  
ajoo 写道
bottom 写道
BeanUtils.copyProperties(foo, map);

这个,不管在必须的property不存在的情况下报错吧?而且,BeanUtils是1.5以前的吧?管enum么?


BeanUtils与ConvertUtilsBean都是“org.apache.commons.beanutils ”提供的
而且BeanUtils.copyProperties方法正是用ConvertUtilsBean实现的类型转换
如果BeanUtils搞不定enum的话,ConvertUtilsBean就可以了么?
3 楼 ajoo 2008-05-08  
bottom 写道
BeanUtils.copyProperties(foo, map);

这个,不管在必须的property不存在的情况下报错吧?而且,BeanUtils是1.5以前的吧?管enum么?
2 楼 bottom 2008-05-08  
BeanUtils.copyProperties(foo, map);
1 楼 sorphi 2008-05-07  
签名、类型、默认值,这个方案,实在是高

相关推荐

    excel与properties文件相互转换

    在IT行业中,数据管理和配置管理经常涉及到不同格式的文件转换,比如Excel和Properties文件。Excel是一种广泛用于数据处理和分析的电子表格工具,而Properties文件则常见于Java开发中,用于存储配置信息。两者之间的...

    Java .properties中文资源批量转换工具和用法

    为了解决这个问题,开发人员通常需要使用特定的工具来批量处理和转换`.properties`文件,以确保它们正确地支持中文字符。 本话题将介绍一种Java `.properties`中文资源批量转换工具的使用方法,帮助开发者高效地...

    properties与json互转的简单实现,支持XDiamond批量增加

    此文件可以批量的将properties转换成json字符串并输出为txt文件. 使用:PropToJson.toJSONString(resourseArr,true) resourseArr为资源文件地址数组 true为对应txt文件到本地; false为不输出到本地

    properties资源文件转换插件

    "properties资源文件转换插件"是一款专为Eclipse开发环境设计的工具,旨在简化对.properties配置文件进行中文转换的过程。通常,在Java开发中,.properties文件用于存储应用程序的配置信息,如国际化(i18n)的文本。...

    java_properties编辑器支持自动转换ascii

    java_properties编辑器支持自动转换ascii,方便快捷无需再用JDK自动工具转换。

    properties文件文中乱码问题解决.doc

    使用 native2ascii 工具可以将 Properties 文件转换为 Unicode 编码字符的文件,然后将转换后的文件内容替换为原始文件的内容。这样可以解决 Properties 文件文中乱码问题。 Properties 文件是一种资源文件,用于...

    读取properties文件返回map

    4. **将Properties转换为Map** `Properties`类本身就是一个`Map`,可以直接通过`entrySet()`方法访问键值对。但如果你需要一个标准的`Map, String&gt;`,可以进行转换: ```java Map, String&gt; map = new HashMap(); ...

    Properties Editor for Eclipse

    2. **自动编码检测和转换**:该插件能够自动检测`.properties`文件的编码,并允许用户选择不同的字符集进行转换,确保国际化文本的正确显示。 3. **支持Unicode和特殊字符**:由于国际化的需要,Properties Editor...

    java properties文件中文转化

    博主分享了一个实用的方法,即通过Unicode转换工具来处理含有中文字符的properties文件,以确保数据的正确性。 首先,我们需要理解Java Properties文件的编码问题。默认情况下,Java Properties类在读取和写入文件...

    java解析Properties配置文件为对象Bean

    利用java的反射解析Properties文件转成对象 /** * 解析properties文件为对象 * @param * @param propPath * @param cls * @return * @throws InstantiationException * @throws ...

    js读取properties文件

    有时候,将.properties文件转换为JSON格式会更方便,因为JSON与JavaScript天生兼容。可以使用在线工具或者自定义脚本完成转换,然后在JavaScript中使用`JSON.parse()`来解析。 7. **错误处理** 在处理文件读取和...

    Properties Editor(在eclipse编辑struts2 资源文件的插件 由unicode转换成中文)

    Properties Editor是一款专为Eclipse开发的插件,主要用于编辑Struts2框架中的资源文件,尤其在处理Unicode编码和中文字符转换方面提供了极大的便利。这款插件解决了开发人员在使用Eclipse进行国际化(i18n)开发时...

    properties-yml.jar

    yml properties相互转换 工具 java小工具 命令:java -jar properties-yml.jar application.properties 生成application.properties 命令:java -jar properties-yml.jar application.yml 生成application.yml

    读取properties返回map并写入文件

    以下是一个简单的示例,展示如何使用Properties类加载文件并将其内容转换为Map: ```java import java.io.*; import java.util.*; public class PropertyHandler { public static Map, String&gt; loadProperties...

    解决Properties写中文乱码

    这个问题通常由两个主要因素引起:编码格式不匹配和读写过程中的编码转换不当。 首先,Properties文件默认使用ISO-8859-1编码,这是Java的标准编码。如果在文件中直接写入中文,而编辑器或程序以UTF-8或其他支持...

    propertiesToJSON:将Java .properties文件转换为JSON(使用JavaScript)

    将Java .properties文件转换为JSON(使用JavaScript)。 函数propertiesToJSON接受一个字符串并返回一个JavaScript对象。 读取node的本地文件: const fs = require ( "fs" ) ; const path = require ( "path" ) ;...

    java操作properties方法

    1. **预处理**:在编辑`.properties`文件时,可以使用Java提供的`native2ascii`工具或Eclipse的属性编辑器将中文字符转换为对应的UTF-8编码表示形式。 2. **程序中转换**:在代码中,可以使用`new String...

    myeclipse插件properties插件

    然而,尽管Eclipse本身已经提供了对`properties`文件的基本编辑功能,但有时开发者可能需要更高级的功能,如语法高亮、自动完成、编码转换等,这就是`myeclipse插件properties插件`的作用。 这个插件专为解决`...

    eclipse离线安装properties插件包,5.3.3版本

    2. **编码检测与转换**:插件可以自动检测文件的编码,并允许用户转换文件的编码格式,以适应不同的项目需求。 3. **语法高亮**:通过颜色区分关键字和普通文本,提高代码可读性。 4. **自动完成**:提供属性键的...

    写入properties文件时间并且读出时间

    - 如果需要将读取出的时间字符串转换回日期对象,可以使用`SimpleDateFormat.parse`或`java.time.format.DateTimeFormatter.parse`方法。例如: ```java try { Date lastUpdateTime = new SimpleDateFormat(...

Global site tag (gtag.js) - Google Analytics