目前項目中涉及到前端編輯後,向後臺做請求更新、寫入到數據庫的場景。
前端是一個表格控件,可以在單元格中編輯各種類型的數據。
舊做法
- 前端JS構造或者提取JSON對象(從表格控件中根據id獲取行數據);
- 對JSON對象進行裝飾,剔除前端自身增加的一些字段,去除可能影響對象轉換的一些空字段;
- ajax請求到後臺Spring MVC框架;
- Spring MVC做對象轉換,從JSON對象轉換爲對應的類對象;
- JPA執行對象保存,並返回保存結果;
- 前端執行刷新;
弊端
這樣的處理有如下的問題:
- 前後端交互的信息量較大(本來僅修改了一個字段,但是更新的時候需要上傳整個對象的所有信息);
- 後端對於更新信息不夠明確(接收到的是整個的對象,後臺搞不清楚前臺究竟做了什麼事情需要更新);
- 前端代碼很囉嗦,需要做構造對象的事情,而且需要剔除掉那些可能在Spring MVC的轉換中搗亂的字段(前臺顯示必要,增加了一些字段);
改造構思
基於前面的分析,考慮的做法就是:前端編輯了哪個屬性數據,就向後臺請求更新哪個屬性數據;不做額外的事情。
這樣,前端向後臺發送的數據就是:id,field,value;也就是數據ID、編輯的屬性、屬性的新值。
存在的一個問題就是:前端編輯的數據包括字符串、整型、浮點數、布爾型等各種類型;而JS作爲一個弱類型語言,不能強求去做數據類型解析的事情。
綜上考慮:
後臺利用Java反射機制來獲取屬性類型,然後獲取相應的set方法進行更新存儲。
實現
- Controller層:
增加相應的接口方法updateXXXInfo,接收前端請求,參數爲id,field,value(三者都是String類型,id爲uuid); - Service層:
根據id獲取到對應的Java類對象(JPA的findOne方式);
做相關的有效性校驗;
使用反射進行更新,代碼如下;
public static boolean update(Class clazz, Object object, String field, String value) {
String formatFieldName = field.substring(0, 1).toUpperCase() + field.substring(1);
String getMethodName = "get" + formatFieldName;
String setMethodName = "set" + formatFieldName;
try {
Method getMethod = clazz.getMethod(getMethodName);
Class type = getMethod.getReturnType();
Method setMethod = clazz.getMethod(setMethodName, type);
String typeName = type.getSimpleName();
String trimedValue = value.trim();
if (typeName.equalsIgnoreCase("String")) {
setMethod.invoke(object, trimedValue);
} else if (typeName.equalsIgnoreCase("Boolean")) {
setMethod.invoke(object, Boolean.parseBoolean(trimedValue));
} else if (typeName.equalsIgnoreCase("Double")) {
setMethod.invoke(object, Double.parseDouble(trimedValue));
} else if ((typeName.equalsIgnoreCase("Integer")) || typeName.equalsIgnoreCase("int")) {
setMethod.invoke(object, Integer.parseInt(trimedValue));
} else if (typeName.equalsIgnoreCase("Byte")) {
setMethod.invoke(object, Byte.parseByte(trimedValue));
} else if (typeName.equalsIgnoreCase("Short")) {
setMethod.invoke(object, Short.parseShort(trimedValue));
} else if (typeName.equalsIgnoreCase("Long")) {
setMethod.invoke(object, Long.parseLong(trimedValue));
} else if (typeName.equalsIgnoreCase("Float")) {
setMethod.invoke(object, Float.parseFloat(trimedValue));
} else if (typeName.equalsIgnoreCase("Date")) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
setMethod.invoke(object, sdf.parse(trimedValue));
}
} catch (Exception e) {
e.printStackTrace();
return false;
}
return true;
}
遺留的一點問題
上面的反射方法中,屬性的類型信息是通過get方法的returnType來獲取的(這樣做的原因在於,如果直接獲取Field,那麼存在父類中的Field獲取不到的問題)。
而get方法的獲取,需要注意boolean類型屬性的處理(IDE默認生成的get方法是以is做前綴,而不是get做前綴的)。
合理的處理方式是在反射方法中catch相關異常進行處理,因爲項目中boolean類型目前就一兩個字段,簡單處理爲get前綴了。