在使用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<String, Object> 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,這樣就可以避免上面的不一致的問題。