在使用Java开发中,很多时候会遇到需要将Map中的值复制到对象中。如果通过手动方式将map中的值取出然后在set到对象中,那对于属性比较多的情形来说,这明显不是一个好办法。当然有比较方便的拌办法,就是使用Apache Commons BeanUtils中的BeanUtils.copyProperties(Object, Object)
方法。但是笔者最近在使用这个方法时,遇到属性无法复制到对象中的情况,于是对其中原因进行了分析,总结如下。
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)
很明显,这个问题肯定出在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,这与我们预期的不一致,导致复制失败。
找到原因后 ,修改起来相对容易,那就是命名时尽量规范,不要以单个字母开头然后接一个单词,如iAppNo,可以改成inAppNo,这样就可以避免上面的不一致的问题。