實戰Ext -> Struts2 -> Spring資料傳遞與解析
在以Spring為核心的Web應用中,使用Ext作為Web前臺,通過Struts2作為資料交換的“跳板”。
原本Struts2自身具備的ModelDriven介面,在使用Ext前臺後變得已經沒有什麼大用了。
由於有struts2-json-plugin的支援,可以很方便的獲取前臺的資料。
有點像Ext將資料序列化後,再由後臺的Java進行反序列化。但是,Ext畢竟只能提供JSON資料,它的本質還只是POST過來的字串而已。
然而藉助struts2-json-plugin提供的 Object JSONUtil.deserialize(String) 方法,可以很簡單的將 request 中的字串“反序列化”為Java物件。
但是,在實戰中發現,由於“反序列化”的時候並沒有一個Java類作為“模板”,所以到底“反序列化”出來的物件是什麼型別的,完全由JSONUtil.deserialize方法自己決定。而它只能參考唯一的引數——那個大大的JSON字串來決定。於是 見到形如數字的就生成 Long,有小數點的就是 Double,其他統統 String,見到[ ]就是List,見到{ }就是Map。
然而我們Java端的JavaBean中真的是這些型別嗎?
那個Long可能僅僅是int,或者根本就是個String;
那個List或許是個陣列;
那個Map其實是另一個JavaBean。。。
但是,JSONUtil真的猜不到呀。
多麼希望JSONUtil能夠提供一個這樣的過載方法:deserialize(String, Class)
但是,它真的沒有。
於是我在我的框架中,從Struts2的Action入手。給所有的Action類編制一個抽象父類,並在其中實現這樣的“智慧反序列化”功能。
寫這段程式碼時,發現最為麻煩的就是,如果JSONUtil反序列化後得到的物件中的某個屬性是集合型別,而我們的JavaBean中,它其實應該是陣列或JavaBean時,如何得到集合型別泛型中的型別。
舉個例子,JSON字串經JSONUtil處理後,有個屬性 stuffs 的型別是List<E>,而按照JavaBean中的定義,這個 stuffs 的型別是個陣列 Human[] stuffs ,這個相對簡單一些,假設從JavaBean中反射得到各個屬性並遍歷時,得到的屬性型別為propType:
beanInfo = Introspector.getBeanInfo(type);
for (PropertyDescriptor descriptor : beanInfo.getPropertyDescriptors()) {
Class propType = descriptor.getPropertyType();
}
那麼 Class innerCls = propertyType.getComponentType(); 就能得到陣列的元素型別,如上例中的 Human。
更為複雜的是,當JSON中的 List<E> 對應於 JavaBean 中的 List<?> 時,如果上例中,JavaBean中 stuffs 屬性的型別改為 List<Human> :
那麼很遺憾,我沒有找到方法可以從 propTpye (List<Human>的Class)中找到並提取 Human 這個類。
只能通過 stuffs 這個屬性在其JavaBean中的setter方法,取得其引數裡面的泛型,來發現這個Human。
// get the inner Type
ParameterizedType rType = (ParameterizedType) wm.getGenericParameterTypes()[0];
Type fType = rType.getActualTypeArguments()[0];
// Type to Class
Field field = fType.getClass().getDeclaredField("name");
field.setAccessible(true);
String name = (String) field.get(fType);
Class innerCls = Class.forName(name);
Map中泛型也與此型別,唯一的區別是,Map中有key和value,需要取兩個泛型的型別。
剩下的工作就是遞迴了,這樣才能無線層次的構築複雜的JavaBean物件。
詳細程式碼如下:
AbstractAction.java
/**
* Action的抽象骨架類
*
*/
public abstract class AbstractAction extends ActionSupport {
/** Logger available to subclasses */
protected final Log logger = LogFactory.getLog(getClass());
// 此處省略一些與本功能無關的程式碼
/**
* 預處理。確認request有效,並強制編碼格式為UTF-8
*/
private boolean proParseRequestData() {
ServletRequest request = ServletActionContext.getRequest();
try {
request.setCharacterEncoding("UTF-8");
} catch (UnsupportedEncodingException e) {
logger.warn("Can't encoding to UTF-8. use the default encode.");
}
if (request == null) {
logger.error("Can't get http servlet request.");
return false;
} else {
return true;
}
}
/**
* 獲取陣列型JSON字串
* 因陣列型資料,前臺可能有兩種提交方式。一種是提交多個同名的專案,一種是提交單個以[]包裹、逗號分割的JSON格式的字串。本方法將這兩種提交方式統一化處理為JSON字串格式
*/
private String parseParametersJSONString(ServletRequest request, String propertyName) {
String[] stringArray = request.getParameterValues(propertyName);
if(stringArray == null){
return null;
}
if (stringArray.length == 1 && stringArray[0].startsWith("[")) {
return stringArray[0];
} else {
StringBuffer sb = new StringBuffer();
for (int i = 0; i < stringArray.length; i++) {
sb.append("[");
sb.append(stringArray[i]);
sb.append("]");
if (i < stringArray.length - 1) {
sb.append(",");
}
}
return sb.toString();
}
}
/**
* 解析頁面提交的所有資料 從request中提取JSON序列化字串,並據此解析出複雜的JavaBean資料(VOBean)
*
* @param type
* 裝填JavaBean的型別
* @return 裝填好的JavaBean
*/
protected <T> T parseRequestData(Class<T> type) {
// 檢查並預置HttpRequest
if (!proParseRequestData()) {
return null;
}
T result = null;
try {
result = (T) type.newInstance();
} catch (Exception e) {
logger.warn("Can't parse JOSN object.");
logger.warn(e.getMessage());
}
BeanInfo beanInfo = null;
try {
beanInfo = Introspector.getBeanInfo(type); // 獲取類屬性
} catch (Exception e) {
logger.warn("Can't parse JOSN object.");
logger.warn(e.getMessage());
}
ServletRequest request = ServletActionContext.getRequest();
// 給 JavaBean 物件的屬性賦值
for (PropertyDescriptor descriptor : beanInfo.getPropertyDescriptors()) {
Method wm = descriptor.getWriteMethod();
if (wm != null) {
String propertyName = descriptor.getName();
Class propType = descriptor.getPropertyType();
if (propType.isArray()) {
// 陣列型屬性
try {
String jsonString = parseParametersJSONString(request, propertyName);
Object jsonObj = JSONUtil.deserialize(jsonString);
Object value = parseProperties(jsonObj, propType);
wm.invoke(result, value);
} catch (Exception e) {
logger.warn("Can't set property " + propertyName);
logger.warn(e.toString());
}
} else if (List.class.isAssignableFrom(propType)) {
Class innerCls = null;
try {
String propertyString = parseParametersJSONString(request, propertyName);
if (propertyString != null) {
// get the inner Type
ParameterizedType rType = (ParameterizedType) wm.getGenericParameterTypes()[0];
Type fType = rType.getActualTypeArguments()[0];
// Type to Class
Field field = fType.getClass().getDeclaredField("name");
field.setAccessible(true);
String name = (String) field.get(fType);
innerCls = Class.forName(name);
Object jsonObj = JSONUtil.deserialize(propertyString);
Object value = parseProperties(jsonObj, propType, innerCls);
wm.invoke(result, value);
}
} catch (Exception e) {
logger.warn("Can't get inner generic class.");
logger.warn(e.getMessage());
}
} else if (Map.class.isAssignableFrom(propType)) {
try {
String propertyString = parseParametersJSONString(request, propertyName);
if (propertyString != null) {
// get the inner Type([0] is the Key type of the Map)
ParameterizedType rType1 = (ParameterizedType) wm.getGenericParameterTypes()[0];
Type fType1 = rType1.getActualTypeArguments()[0];
// Type to Class
Field field1 = fType1.getClass().getDeclaredField("name");
field1.setAccessible(true);
String name1 = (String) field1.get(fType1);
Class innerCls1 = Class.forName(name1);
// get the inner Type([1] is the Value type of the Map)
ParameterizedType rType2 = (ParameterizedType) wm.getGenericParameterTypes()[1];
Type fType2 = rType2.getActualTypeArguments()[0];
// Type to Class
Field field2 = fType2.getClass().getDeclaredField("name");
field2.setAccessible(true);
String name2 = (String) field2.get(fType2);
Class innerCls2 = Class.forName(name2);
Object jsonObj = JSONUtil.deserialize(propertyString);
Object value = parseProperties(jsonObj, propType, innerCls1, innerCls2);
wm.invoke(result, value);
}
} catch (Exception e) {
logger.warn("Can't get inner generic class.");
logger.warn(e.getMessage());
}
} else if (ClassUtil.isValueType(propType)) {
Object value = null;
try {
String propertyString = request.getParameter(propertyName);
if (propertyString != null) {
value = stringToObject(propertyString, propType);
wm.invoke(result, value);
}
} catch (Exception e) {
logger.warn("Can't set property " + propertyName);
logger.warn(e.getMessage());
}
} else {
Object value = null;
try {
String propertyString = request.getParameter(propertyName);
if (propertyString != null) {
Object jsonObj = JSONUtil.deserialize(propertyString);
value = parseProperties(jsonObj, propType);
wm.invoke(result, value);
}
} catch (Exception e) {
logger.warn("Can't set property " + propertyName);
logger.warn(e.getMessage());
}
}
}
}
// 返回結果
return result;
}
/**
* 解析頁面提交的某個資料 從request中提取JSON序列化字串,並據此解析出某個複雜的JavaBean資料(VOBean)
*
* @param type
* 裝填JavaBean的型別
* @param propertyName
* 預解析的屬性名(在request資訊中的名)
* @return 裝填好的JavaBean
*/
protected <T> T parseRequestData(Class<T> type, String propertyName) {
// 檢查並預置HttpRequest
if (!proParseRequestData()) {
return null;
}
ServletRequest request = ServletActionContext.getRequest();
if (type.isArray()) {
try {
String jsonString = parseParametersJSONString(request, propertyName);
if (jsonString != null) {
Object jsonObj = JSONUtil.deserialize(jsonString);
return parseProperties(jsonObj, type);
}
} catch (Exception e) {
logger.warn("Can't set property " + propertyName);
logger.warn(e.getMessage());
}
} else if (List.class.isAssignableFrom(type) || Map.class.isAssignableFrom(type)) {
try {
String propertyString = parseParametersJSONString(request, propertyName);
if (propertyString != null) {
// can't get the inner Type, use Object
Object jsonObj = JSONUtil.deserialize(propertyString);
return parseProperties(jsonObj, type, Object.class);
}
} catch (Exception e) {
logger.warn("Can't get inner generic class.");
logger.warn(e.getMessage());
}
} else if (ClassUtil.isValueType(type)) {
try {
String propertyString = request.getParameter(propertyName);
if (propertyString != null) {
return (T) stringToObject(propertyString, type);
}
} catch (Exception e) {
logger.warn("Can't get inner generic class.");
logger.warn(e.getMessage());
}
} else {
try {
String propertyString = request.getParameter(propertyName);
if (propertyString != null) {
Object jsonObj = JSONUtil.deserialize(propertyString);
return parseProperties(jsonObj, type);
}
} catch (Exception e) {
logger.warn("Can't get inner generic class.");
logger.warn(e.getMessage());
}
}
return null;
}
/**
* 解析屬性
* @param <T> 返回值型別
* @param source JSON字串反序列得到的源資料
* @param type 返回值的型別
* @return 解析得到的Java物件
*/
private <T> T parseProperties(Object source, Class<T> type) {
return parseProperties(source, type, null);
}
/**
* 解析屬性
* @param <T> 返回值型別
* @param source JSON字串反序列得到的源資料
* @param type 返回值的型別
* @param innerType 內部(泛型)型別,對應List<E>的E
* @return 解析得到的Java物件
*/
private <T> T parseProperties(Object source, Class<T> type, Class innerType) {
return parseProperties(source, type, innerType, null);
}
/**
* 解析屬性
* @param <T> 返回值型別
* @param source JSON字串反序列得到的源資料
* @param type 返回值的型別
* @param innerType1 內部(泛型)型別1,對應Map<K, V>的K
* @param innerType2 內部(泛型)型別2,對應Map<K, V>的V
* @return 解析得到的Java物件
*/
private <T> T parseProperties(Object source, Class<T> type, Class innerType1, Class innerType2) {
// JavaBean or Map
if (source instanceof Map) {
Map<Object, Object> jsonMap = (Map<Object, Object>) source;
if (Map.class.isAssignableFrom(type)) {
// type is Map
Map<Object, Object> rtnMap = new HashMap<Object, Object>();
for (Map.Entry<Object, Object> entry : jsonMap.entrySet()) {
rtnMap.put(parseProperties(entry.getKey(), innerType1), parseProperties(entry.getValue(), innerType2));
}
return (T) rtnMap;
} else {
// JavaBean
T obj = null;
try {
obj = type.newInstance();
} catch (Exception e) {
logger.warn("Can't parse JOSN object.");
logger.warn(e.getMessage());
}
BeanInfo beanInfo = null;
try {
beanInfo = Introspector.getBeanInfo(type); // 獲取類屬性
} catch (Exception e) {
logger.warn("Can't parse JOSN object.");
logger.warn(e.getMessage());
}
// 給 JavaBean 物件的屬性賦值
for (PropertyDescriptor descriptor : beanInfo.getPropertyDescriptors()) {
Method wm = descriptor.getWriteMethod();
if (wm != null) {
String propertyName = descriptor.getName();
Class propertyType = descriptor.getPropertyType();
try {
if (jsonMap.containsKey(propertyName)) {
Object value = jsonMap.get(propertyName);
if (value != null) {
if (propertyType.isArray()) {
// get the inner Type
Class innerCls = propertyType.getComponentType();
wm.invoke(obj, parseProperties(value, propertyType, innerCls));
} else if (List.class.isAssignableFrom(propertyType)) {
// get the inner Type
ParameterizedType rType = (ParameterizedType) wm.getGenericParameterTypes()[0];
Type fType = rType.getActualTypeArguments()[0];
// Type to Class
Field field = fType.getClass().getDeclaredField("name");
field.setAccessible(true);
String name = (String) field.get(fType);
Class innerCls = Class.forName(name);
wm.invoke(obj, parseProperties(value, propertyType, innerCls));
} else if (Map.class.isAssignableFrom(propertyType)) {
// get the inner Type([0] is the Key type of the Map)
ParameterizedType rType1 = (ParameterizedType) wm.getGenericParameterTypes()[0];
Type fType1 = rType1.getActualTypeArguments()[0];
// Type to Class
Field field1 = fType1.getClass().getDeclaredField("name");
field1.setAccessible(true);
String name1 = (String) field1.get(fType1);
Class innerCls1 = Class.forName(name1);
// get the inner Type([1] is the Value type of the Map)
ParameterizedType rType2 = (ParameterizedType) wm.getGenericParameterTypes()[1];
Type fType2 = rType2.getActualTypeArguments()[0];
// Type to Class
Field field2 = fType2.getClass().getDeclaredField("name");
field2.setAccessible(true);
String name2 = (String) field2.get(fType2);
Class innerCls2 = Class.forName(name2);
wm.invoke(obj, parseProperties(value, propertyType, innerCls1, innerCls2));
} else if (propertyType.isAssignableFrom(value.getClass())) {
wm.invoke(obj, value);
} else if(ClassUtil.isValueType(propertyType)){
wm.invoke(obj, stringToObject(value.toString(), propertyType));
} else {
wm.invoke(obj, parseProperties(value, propertyType));
}
}
}
} catch (Exception e) {
logger.warn("Can't set property " + propertyName);
logger.warn(e.getMessage());
}
}
}
return obj;
}
} else if (source instanceof List) {
// List from JSON
if (List.class.isAssignableFrom(type)) {
// Data into a List
if (ClassUtil.isValueType(type)) {
return (T) source;
} else {
List rtn = new ArrayList();
for (Object obj : (List) source) {
rtn.add(parseProperties(obj, innerType1));
}
return (T) rtn;
}
} else {
// Data into a Array
List<Object> innerList = (List<Object>) source;
Class arrayType = type.getComponentType();
Object[] array = (Object[]) Array.newInstance(arrayType, innerList.size());
for (int i = 0; i < innerList.size(); i++) {
Object src = innerList.get(i);
Object item = parseProperties(src, arrayType);
array[i] = item;
}
return (T) array;
}
} else {
return (T) source;
}
}
/**
* 將String轉換為type所指定的型別
*
* @param str
* String型元資料
* @param type
* 轉換後的資料型別的class
* @return 轉換後的結果
* @throws NumberFormatException
* 當引數不能轉換為數值型時
*/
private Object stringToObject(String str, Class type) {
try {
if (String.class.isAssignableFrom(type)) {
return str;
}
if (char.class.isAssignableFrom(type)) {
return str.charAt(0);
}
if (Character.class.isAssignableFrom(type)) {
return Character.valueOf(str.charAt(0));
}
if (int.class.isAssignableFrom(type)) {
return Integer.valueOf(str);
}
if (Integer.class.isAssignableFrom(type)) {
return Integer.valueOf(str);
}
if (boolean.class.isAssignableFrom(type)) {
return Boolean.valueOf(str);
}
if (Boolean.class.isAssignableFrom(type)) {
return Boolean.valueOf(str);
}
if (short.class.isAssignableFrom(type)) {
return Short.valueOf(str);
}
if (Short.class.isAssignableFrom(type)) {
return Short.valueOf(str);
}
if (long.class.isAssignableFrom(type)) {
return Long.valueOf(str);
}
if (Long.class.isAssignableFrom(type)) {
return Long.valueOf(str);
}
if (float.class.isAssignableFrom(type)) {
return Float.valueOf(str);
}
if (Float.class.isAssignableFrom(type)) {
return Float.valueOf(str);
}
if (double.class.isAssignableFrom(type)) {
return Double.valueOf(str);
}
if (Double.class.isAssignableFrom(type)) {
return Double.valueOf(str);
}
if (byte.class.isAssignableFrom(type)) {
return Byte.valueOf(str);
}
if (Byte.class.isAssignableFrom(type)) {
return Byte.valueOf(str);
}
if (Date.class.isAssignableFrom(type)) {
return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS").parse(str);
}
if (Calendar.class.isAssignableFrom(type)) {
Date date = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS").parse(str);
Calendar result = Calendar.getInstance();
result.setTime(date);
return result;
}
if (BigInteger.class.isAssignableFrom(type)) {
return new BigInteger(str);
}
if (BigDecimal.class.isAssignableFrom(type)) {
return new BigDecimal(str);
}
return null;
} catch (Exception e) {
return null;
}
}
}
這裡邊用到了一個 ClassUtil.isValueType(Class) 方法用來判定給定的型別是為Java的值型別和基本型:
ClassUtil.java
public class ClassUtil {
/**
* 判定指定的 Class 物件是否表示一個值型別。 八個基本型別及它們的包裝類、五個 Class 和 void。 即
* boolean、Boolean、byte、Byte、char、Character、short、Short、int、Integer、long、Long、float、Float、double、Double、String、BigInteger、BigDecimal、Date、Calendar、void。
*
* @param clazz
* @return 當且僅當指定類表示一個值型別時,才返回 true
*/
public static boolean isValueType(Class clazz) {
if (clazz.isPrimitive()) {
return true;
}
if (String.class.isAssignableFrom(clazz)) {
return true;
}
if (Byte.class.isAssignableFrom(clazz)) {
return true;
}
if (Character.class.isAssignableFrom(clazz)) {
return true;
}
if (Short.class.isAssignableFrom(clazz)) {
return true;
}
if (Integer.class.isAssignableFrom(clazz)) {
return true;
}
if (Long.class.isAssignableFrom(clazz)) {
return true;
}
if (Float.class.isAssignableFrom(clazz)) {
return true;
}
if (Double.class.isAssignableFrom(clazz)) {
return true;
}
if (Date.class.isAssignableFrom(clazz)) {
return true;
}
if (Calendar.class.isAssignableFrom(clazz)) {
return true;
}
if (BigInteger.class.isAssignableFrom(clazz)) {
return true;
}
if (BigDecimal.class.isAssignableFrom(clazz)) {
return true;
}
return false;
}
}
最後,在各個Action中的使用方法是:
XxxxxAction.java
// 前臺提交的request就是stuffs這個物件在前臺的JSON物件
Human stuffs = parseRequestData(Human.class);
// 前臺提交的request中的專案並非某個JavaBean的眾屬性,而是單獨接收它們
String listType = this.parseRequestData(String.class, "listType");
boolean hasBlankItem = this.parseRequestData(Boolean.class, "hasBlankItem");
Object[] params = this.parseRequestData(Object[].class, "params");
就此我們的反序列化工具完成了,複雜的JavaBean可以成功反序列化了。
但是,期間的程式碼(主要是提取泛型中的型別),看上去有些醜陋,如果你知道更好的方法,請一定跟帖!