Map与对象之间属性复制浅析

在使用Java开发中,很多时候会遇到需要将Map中的值复制到对象中。如果通过手动方式将map中的值取出然后在set到对象中,那对于属性比较多的情形来说,这明显不是一个好办法。当然有比较方便的拌办法,就是使用Apache Commons BeanUtils中的BeanUtils.copyProperties(Object, Object)方法。但是笔者最近在使用这个方法时,遇到属性无法复制到对象中的情况,于是对其中原因进行了分析,总结如下。

1. 示例代码

public class TestPropertiesCopy {	
	public static void main(String[] args) throws IllegalAccessException, InvocationTargetException {
		//1.将Map中的值复制到bean中
		Map inputMap = new HashMap<>();
		inputMap.put("iAppNo", "123456");
		inputMap.put("iAppName", "myname");
		
		InputBody inputBody = new InputBody();
		BeanUtils.copyProperties(inputBody, inputMap);
		System.out.println(inputBody);		
	}
}

/**
 * 输入报文实体 
 */

@Data
@ToString
class InputBody {	
	String iAppNo;
	String iAppName;	
}

当运行上述代码时,得到的结果如下,也就是map中的属性并没有复制到对象的同名属性中。

InputBody(iAppNo=null, iAppName=null)

2. 原因浅析

很明显,这个问题肯定出在copyProperties()方法中,那这个方法具体做了些什么呢?
当对copyProperties()方法进行跟踪时,最终在getTargetPropertyInfo()方法中,发现了如下一段关键性代码:

if (name.startsWith(GET_PREFIX)) {
	// Simple getter
    pd = new PropertyDescriptor(this.beanClass, name.substring(3), method, null);
} else if (resultType == boolean.class && name.startsWith(IS_PREFIX)) {
    // Boolean getter
    pd = new PropertyDescriptor(this.beanClass, name.substring(2), method, null);
}

从这段代码中可以看出,在将map中属性的值复制到对象时,会根据对象中的getter方法(类型为boolean时即为is()方法)来获取对象的属性名,具体的实现在实例化PropertyDescriptor对象时的setName()方法的decapitalize()方法中,具体如下:

public static String decapitalize(String name) {
    if (name == null || name.length() == 0) {
        return name;
    }
    if (name.length() > 1 && Character.isUpperCase(name.charAt(1)) &&
                    Character.isUpperCase(name.charAt(0))){
        return name;
    }
    char chars[] = name.toCharArray();
    chars[0] = Character.toLowerCase(chars[0]);
    return new String(chars);
}

从代码中可以看到,首先对getter方法名从第四位截取(is方法名从第三位截取),然后进行如下判断:

  • 当首字母和第二个字母都是大写字母时,直接返回截取后字符串作为属性名
  • 其他情况,则将首字母转为小写再返回作为属性名

到这里,也就是能解释第1节中的代码为什么不能复制成功了。lombok包在为属性生成getter方法时,会将属性名的第一个字符转成大写,比如iAppNo属性对应的getter方法名为getIAppNo。当BeanUtils.copyProperties()方法对其赋值而获取属性名的方法,会因为第一个和第二个字母均为大写为不进行转换,所以获取到的属性名为IAppNo,这与我们预期的不一致,导致复制失败。

3. 解决方法

找到原因后 ,修改起来相对容易,那就是命名时尽量规范,不要以单个字母开头然后接一个单词,如iAppNo,可以改成inAppNo,这样就可以避免上面的不一致的问题。

你可能感兴趣的:(Java)