您好,登錄后才能下訂單哦!
這期內容當中小編將會給大家帶來有關如何進行Java中對象與表單的自動裝配,文章內容豐富且以專業的角度為大家分析和敘述,閱讀完這篇文章希望大家可以有所收獲。
時下很多 Web 框架 都實現了 Form 表單域與 Java 對象屬性的自動裝配功能,該功能確實非常有用,試想如果沒這功能則勢必到處沖積著 request.getParameter() 系列方法與類型轉換方法的調用。重復代碼量大,容易出錯,同時又不美觀,影響市容。
現在的問題是,這些框架通過什么方法實現自動裝配的?如果不用這些框架我們自己如何去實現呢?尤其對于那些純 JSP/Servlet 應用,要是擁有自動裝配功能該多好啊!本座深知各位之期盼,決定把自動裝配的原理和實現方法娓娓道來。
實現原理其實比較簡單,主要由以下3個步驟構成:
通過 request.getParameterMap() 獲取被提交的 Form 的所有表單域的名稱-值映射,其中名稱和值均為字符串類型。
利用 java.beans.PropertyDescriptor 對象獲取 Java Bean 的所有屬性的名稱及其類型。
把表單域的名稱與 Bean 的屬性的名稱逐個進行匹配,當找到相匹配的屬性時就調用 Bean 的 setter 方法把表單域的值設置給該 Bean 屬性。當然,因為表單域的值總是字符串類型,很可能與 Bean 屬性的類型不一致,所以在設置 Bean 屬性前要進行必要的類型轉換。
上面所表述的3點原理不知大家是否完全理解,沒關系,下面我們通過一個具體的表單提交的例子來看一看實際的效果,首先看看待提交的表單頁面及其代碼:
<form action="checkbean.action" method="post"> First Name: <input type="text" name="firstName" value="丑"> <br> Last Name: <input type="text" name="lastName" value="怪獸"> <br> Birthday: <input type="text" name="birthday" value="1978-11-03"> <br> Gender: 男 <input type="radio"" name="gender" value="false"> 女 <input type="radio"" name="gender" value="true" checked="checked"> <br> Working age: <select name="working-Age"> <option value="-1">-請選擇-</option> <option value="3">三年</option> <option value="5" selected="selected">五年</option> <option value="10">十年</option> <option value="20">二十年</option> </select> <br> Interest: 游泳 <input type="checkbox" name="its" value="1" checked="checked"> 打球 <input type="checkbox" name="its" value="2" checked="checked"> 下棋 <input type="checkbox" name="its" value="3"> 打麻將 <input type="checkbox" name="its" value="4"> 看書 <input type="checkbox" name="its" value="5" checked="checked"> <br> <br> <input type="submit" value="確 定"> <input type="reset" value="重 置"> </form>
從上圖可以看出,總共有6個表單域,其名稱-值分別是:{"firstName" - "丑","lastName" - "怪獸","birthday" - "1978-11-03","gender" - "女","working-Age" - "5","its" - "1,2,5"},該表單需要提交給 checkbean.action 進行處理(請注意:不要一看到 .aciton 就以為是 struts2,騎白馬的不一定都是唐僧!),下面來看看 CheckBean Action 的處理代碼和 Bean 的定義:
import java.util.HashMap; import java.util.Map; import vo.Persion; import com.bruce.mvc.ActionSupport; public class CheckBean extends ActionSupport { @Override public String execute() { // 如果表單元素的名稱和 Form Bean 屬性名不一致則使用 keyMap 進行映射 // key: 表單元素名稱, value: Form Bean 屬性名 Map<String, String> keyMap = new HashMap<String, String>(); keyMap.put("working-Age", "workingAge"); keyMap.put("its", "interest"); /* 自動裝配方法一 */ // 使用表單元素創建 Form Bean // 如果表單元素的名稱和 Form Bean 屬性名完全一致則不需使用 keyMap 進行映射 Persion p = createFormBean(Persion.class, keyMap); /* 自動裝配方法二 */ // 先創建 Form Bean 對象, 然后再填充它的屬性 Persion p2 = new Persion(); fillFormBeanProperties(p2, keyMap); // 可以獲取 Form Bean 的所有屬性值 //Map<String, Object> result = BeanHelper.getProperties(p); // 把 p 設置為 request 屬性,并最終在結果頁面展示 setRequestAttribute("persion", p); return SUCCESS; } }
import java.util.Date; import java.util.List; public class Persion { private String firstName; private String lastName; private Date birthday; private boolean gender; private int workingAge; private int[] interest; private List<String> photos; // getter 和 setter 方法 // (略)。。。。。。 }
從 CheckBean 的代碼可以看出,它是通過 createFormBean() 或 fillFormBeanProperties() 方法來自動裝配 Persion 的,它們之間的區別是:前者會直接創建新的 Persion 對象,而后者填充原有 Persion 對象的屬性。請注意,如果表單域的名稱與 Persion 對應的屬性名不一致則用 keyMap 進行映射,如表單域 "working-Age" 是對應 Persion 的 workingAge 屬性的,但它們的名稱不一致,所以要進行映射。另外,Persion 有一個 photos 屬性,而我們的表單域卻沒有,自動裝配時會忽略該屬性。最后看一下輸出的結果頁面及其代碼:
<body> <br> <div align="right"> <a href="index.action"><u:msg key="jsp-set_locale.back-to-index"/></a> </div> <br><br><br><br> <div align="center"> <table border="1"> <caption>Persion Attributs</caption> <tr><td>Name</td><td><c:out value="${persion.firstName} ${persion.lastName}"/> </td></tr> <tr><td>Brithday</td><td><c:out value="${persion.birthday}"/> </td></tr> <tr><td>Gender</td><td><c:out value="${persion.gender}"/> </td></tr> <tr><td>Working Age</td><td><c:out value="${persion.workingAge}"/> </td></tr> <tr><td>Interest</td><td><c:forEach var="its" items="${persion.interest}"> <c:out value="${its}" /> </c:forEach> </td></tr> <tr><td>Photos</td><td><c:forEach var="p" items="${persion.photos}"> <c:out value="${p}" /><br> </c:forEach> </td></tr> </table> </div> </body>
通過上面的例子可以看到,通過自動裝配 Bean,我們獲得了非常大的便利。現在我們就從createFormBean() 和 fillFormBeanProperties() 開始,逐步揭開自動裝配的神秘面紗,先看看下面兩個類及其方法的定義:
package com.bruce.mvc; import com.bruce.util.http.HttpHelper; /** {@link Action} 對象公共基類 */ public class ActionSupport implements Action { private ServletContext servletContext; private HttpServletRequest request; private HttpServletResponse response; private HttpSession session; /** 默認 {@link Action} 入口方法(返回 {@link Action#SUCCESS}) */ public String execute() { return SUCCESS; } /** 使用表單元素創建 Form Bean (表單元素的名稱和 Form Bean 屬性名完全一致) */ public final <T> T createFormBean(Class<T> clazz) { return HttpHelper.createFormBean(request, clazz); } /** 使用表單元素創建 Form Bean (用 keyMap 映射與表單元素名稱不對應的 Form Bean 屬性) */ public final <T> T createFormBean(Class<T> clazz, Map<String, String> keyMap) { return HttpHelper.createFormBean(request, clazz, keyMap); } /** 使用表單元素填充 Form Bean (表單元素的名稱和 Form Bean 屬性名完全一致) */ public final <T> void fillFormBeanProperties(T bean) { HttpHelper.fillFormBeanProperties(request, bean); } /** 使用表單元素填充 Form Bean (用 keyMap 映射與表單元素名稱不對應的 Form Bean 屬性) */ public final <T> void fillFormBeanProperties(T bean, Map<String, String> keyMap) { HttpHelper.fillFormBeanProperties(request, bean, keyMap); } // 其它方法 // (略)。。。 }
package com.bruce.util.http; import com.bruce.util.BeanHelper; /** HTTP 幫助類 */ public class HttpHelper { /** 使用表單元素創建 Form Bean (表單元素的名稱和 Form Bean 屬性名完全一致) */ public final static <T> T createFormBean(HttpServletRequest request, Class<T> clazz) { return createFormBean(request, clazz, null); } /** 使用表單元素創建 Form Bean (用 keyMap 映射與表單元素名稱不對應的 Form Bean 屬性) */ public final static <T> T createFormBean(HttpServletRequest request, Class<T> clazz, Map<String, String> keyMap) { Map<String, String[]> properties = getParamMap(request); return BeanHelper.createBean(clazz, properties, keyMap); } /** 使用表單元素填充 Form Bean (表單元素的名稱和 Form Bean 屬性名完全一致) */ public final static <T> void fillFormBeanProperties(HttpServletRequest request, T bean) { fillFormBeanProperties(request, bean, null); } /** 使用表單元素填充 Form Bean (用 keyMap 映射與表單元素名稱不對應的 Form Bean 屬性) */ public final static <T> void fillFormBeanProperties(HttpServletRequest request, T bean, Map<String, String> keyMap) { Map<String, String[]> properties = getParamMap(request); BeanHelper.setProperties(bean, properties, keyMap); } /** 獲取 {@link HttpServletRequest} 的所有參數名稱和值 */ public final static Map<String, String[]> getParamMap(HttpServletRequest request) { return request.getParameterMap(); } // 其它方法 // (略)。。。 }
哈哈,大家看到了吧,我們迂回了那么久,但 ActionSupport 類和 HttpHelper 類并沒有并沒有做多少事情,他們只是獲取請求參數 Map,并傳遞給 BeanHelper 類的 createBean() 和 setProperties() 方法進行裝配,實際負責裝配工作的是 BeanHelper 。這里解析一下為何要寫得那么迂回,其實這些代碼是從本座自己寫的 “Portal Web 開發框架” 中摘錄出來的,總之一句話:是因為框架的需要而寫得那么迂回的,并非本座有意而為之。順便做下廣告:“Portal Web 開發框架”是一套功能完備的 Web 服務端開發框架,內置 MVC Web 基礎架構,支持可擴展的數據訪問接口(已內置 Hibernate、MyBaits 和 JDBC 支持),集成攔截器、國際化、文件上傳下載和緩存等基礎 Web 服務,基于純 Jsp/Servlet API 實現,非常容易學習和使用。尤其適合那些希望使用純 Jsp/Servlet API 進行開發或對 SSH 等主流框架的復雜性感到繁瑣與無奈的人士使用。該框架已通過多個商業項目考驗,并不是寫來玩的哦。如果各位有興趣,本座以后再找個機會開個專貼詳細介紹下這個框架。
不扯遠了,回到我們的正題,我們再來看看 BeanHelper 的裝配工裝是如何實現的:
/** Java Bean 幫助類,執行 Java Bean 屬性的 get / set 相關操作 */ public class BeanHelper { /** 創建指定類型的 Java Bean,并設置相關屬性 * * @param clazz : Bean 類型 * @param properties : 屬性名 / 值映射<br> * 其中名稱為 {@link String} 類型,與屬性名稱可能一直也可能不一致<br> * 屬性值可能為以下 3 中類型:<br> * 1) 屬性的實際類型:直接對屬性賦值<br> * 2) {@link String} 類型:先執行自動類型轉換再對屬賦值<br> * 3) {@link String}[] 類型:先執行自動類型轉換再對屬賦值<br> * @param keyMap : properties.key / Bean 屬性名映射,當 properties 的 key 與屬性名不對應時, * 用 keyMap 把它們關聯起來 * @return 生成的 Bean實例 */ public static final <B, T> B createBean(Class<B> clazz, Map<String, T> properties, Map<String, String> keyMap) { B bean = null; try { // 創建 Bean 實例 bean = clazz.newInstance(); // 設置 Bean 屬性 setProperties(bean, properties, keyMap); } catch(Exception e) { throw new RuntimeException(e); } return bean; } public static final <B, T> B createBean(Class<B> clazz, Map<String, T> properties) { return createBean(clazz, properties, null); } /** 設置 Java Bean 的屬性 * * @param bean : Bean 實例 * @param properties : 屬性名 / 值映射<br> * 其中名稱為 {@link String} 類型,與屬性名稱可能一直也可能不一致<br> * 屬性值可能為以下 3 中類型:<br> * 1) 屬性的實際類型:直接對屬性賦值<br> * 2) {@link String} 類型:先執行自動類型轉換再對屬賦值<br> * 3) {@link String}[] 類型:先執行自動類型轉換再對屬賦值<br> * @param keyMap : properties.key / Bean 屬性名映射,當 properties 的 key 與屬性名不對應時, * 用 keyMap 把它們關聯起來 */ public static final <T> void setProperties(Object bean, Map<String, T> properties, Map<String, String> keyMap) { // 獲取所有 Bean 屬性 Map<String, PropertyDescriptor> pps = getPropDescMap(bean.getClass()); Set<Map.Entry<String, T>> set = properties.entrySet(); // 根據屬性名稱設置 Bean 的每個屬性值 for(Map.Entry<String, T> o : set) { String name = null; // 屬性名稱 String key = o.getKey(); if(keyMap != null) name = keyMap.get(key); if(name == null) name = key; T value = o.getValue(); PropertyDescriptor pd = pps.get(name); // 名稱對應的 PropertyDescriptor if(pd != null && value != null) // 設置指定屬性值 setProperty(bean, pd, value); } } public static final <T> void setProperties(Object bean, Map<String, T> properties) { setProperties(bean, properties, null); } // 設置指定屬性值 private static final <T> boolean setProperty(Object bean, PropertyDescriptor pd, T value) { // 獲取屬性的 setter 方法 Method method = pd.getWriteMethod(); // 只處理 public 的實例 setter 方法 if(method != null && isPublicInstanceMethod(method)) { method.setAccessible(true); Class<?> clazz = pd.getPropertyType(); // 設置具體屬性值 setProperty(bean, value, method, clazz); return true; } return false; } // 設置具體屬性值 private static <T> void setProperty(Object bean, T value, Method method, Class<?> clazz) { Object param = null; Class<?> valueType = value.getClass(); Class<?> valueComType = valueType.getComponentType(); Class<?> clazzComType = clazz.getComponentType(); // 檢查是否需要作類型轉換 if( !clazz.isAssignableFrom(valueType) && ( (valueType.equals(String.class)) || (valueType.isArray() && valueComType.equals(String.class)) ) && ( (GeneralHelper.isSimpleType(clazz)) || (clazz.isArray() && GeneralHelper.isSimpleType(clazzComType)) ) ) // 轉換為目標類型的屬性值 param = parseParameter(clazz, value); else param = value; // 調研 setter 方法設置屬性值 invokeMethod(bean, method, param); } // 執行類型轉換 (不解釋了,看官們自己參詳吧 ^_^) private static final <T> Object parseParameter(Class<?> clazz, T obj) { Object param = null; Class<?> valueType = obj.getClass(); if(clazz.isArray()) { String[] value = null; if(valueType.isArray()) value = (String[])obj; else { String str = (String)obj; StringTokenizer st = new StringTokenizer(str, " ,;\t\n\r\f"); value = new String[st.countTokens()]; for(int i = 0; st.hasMoreTokens(); i++) value[i] = st.nextToken(); } int length = value.length; Class<?> type = clazz.getComponentType(); param = Array.newInstance(type, length); for(int i = 0; i < length; i++) { String v = value[i]; Object p = GeneralHelper.str2Object(type, v); Array.set(param, i, p); } } else { String value = null; if(valueType.isArray()) { String[] array = (String[])obj; if(array.length > 0) value = array[0]; } else value = (String)obj; param = GeneralHelper.str2Object(clazz, value); } return param; } // 其他方法 // (略)。。。 }
public class GeneralHelper { /** 簡單數據類型集合 */ public static final Set<Class<?>> SMIPLE_CLASS_SET = new HashSet<Class<?>>(18); static { SMIPLE_CLASS_SET.add(int.class); SMIPLE_CLASS_SET.add(long.class); SMIPLE_CLASS_SET.add(float.class); SMIPLE_CLASS_SET.add(double.class); SMIPLE_CLASS_SET.add(byte.class); SMIPLE_CLASS_SET.add(char.class); SMIPLE_CLASS_SET.add(short.class); SMIPLE_CLASS_SET.add(boolean.class); SMIPLE_CLASS_SET.add(Integer.class); SMIPLE_CLASS_SET.add(Long.class); SMIPLE_CLASS_SET.add(Float.class); SMIPLE_CLASS_SET.add(Double.class); SMIPLE_CLASS_SET.add(Byte.class); SMIPLE_CLASS_SET.add(Character.class); SMIPLE_CLASS_SET.add(Short.class); SMIPLE_CLASS_SET.add(Boolean.class); SMIPLE_CLASS_SET.add(String.class); SMIPLE_CLASS_SET.add(Date.class); } /** 檢查 clazz 是否為簡單數據類型 */ public final static boolean isSimpleType(Class<?> clazz) { return SMIPLE_CLASS_SET.contains(clazz); } /** String -> Any,如果 handler 為 null 則把字符串轉換為 8 種基礎數據類型、及其包裝類、 {@link Date} 或 {@link String}, * 如果 handler 不為 null 則由 handler 執行轉換 * * @param type : 目標類型的 {@link Class} 對象 * @param v : 要轉換的字符串 * @param handler : 類型轉換處理器 * @return : 轉換結果,如果轉換不成功返回 null * @throws : 如果目標類型不支持拋出 {@link IllegalArgumentException} * */ @SuppressWarnings("unchecked") public static final <T> T str2Object(Class<T> type, String v, TypeHandler<T> handler) { Object param = null; if(handler != null) return handler.handle(v); if(type == String.class) param = safeTrimString(v); else if(type == int.class) param = str2Int_0(v); else if(type == long.class) param = str2Long_0(v); else if(type == byte.class) param = str2Byte_0(v); else if(type == char.class) param = str2Char_0(v); else if(type == float.class) param = str2Float_0(v); else if(type == double.class) param = str2Double_0(v); else if(type == short.class) param = str2Short_0(v); else if(type == boolean.class) param = str2Boolean_False(v); else if(type == Integer.class) param = str2Int(v); else if(type == Long.class) param = str2Long(v); else if(type == Byte.class) param = str2Byte(v); else if(type == Character.class) param = str2Char(v); else if(type == Float.class) param = str2Float(v); else if(type == Double.class) param = str2Double(v); else if(type == Short.class) param = str2Short(v); else if(type == Boolean.class) param = str2Boolean(v); else if(Date.class.isAssignableFrom(type)) param = str2Date(v); else throw new IllegalArgumentException(String.format("object type '%s' not valid", type)); return (T)param; } public static final <T> T str2Object(Class<T> type, String v) { return str2Object(type, v, null); } // 其他方法 // (略)。。。 }
從上面的代碼可以看出,BeanHelper 支持8種簡單數據類型及其包裝類、String 和 Date 類型以及它們的數組類型的自動裝配,最后強調一下:BeanHelper 和 GeneralHelper 其實是兩個用途非常廣泛的類,其作用不單是為了協助 Form 表單域自動裝配 Bean 。下面列出一些使用例子,幫助大家進一步了解 BeanHelper 的使用方法:
package test; import java.beans.IntrospectionException; import java.util.Date; import java.util.HashMap; import java.util.Map; import java.util.StringTokenizer; import com.bruce.util.BeanHelper; import com.bruce.util.GeneralHelper; @SuppressWarnings("unused") public class TestBeanHelper extends Object { public static void main(String[] args) throws Exception { test(); testStr2Object(); test_setProperty(); test_setProperties_1(); test_setProperties_2(); } private static void test() { //System.out.println(GeneralHelper.str2Date(" 1978-11-03 ")); //System.out.println(GeneralHelper.str2Date(" 1978-11-03 ")); //GeneralHelper.str2Byte(null); //GeneralHelper.str2Char_0(null); //GeneralHelper.str2Boolean(null); //GeneralHelper.str2Boolean_False(null); } private static void testStr2Object() throws IntrospectionException { int i = GeneralHelper.str2Object(int.class, "123"); Date dt = GeneralHelper.str2Object(Date.class, "1978-11-03"); String[] arr = GeneralHelper.str2Object(String[].class, "12, 34, 56, 78", new GeneralHelper.TypeHandler<String[]>() { @Override public String[] handle(String v) { StringTokenizer st = new StringTokenizer(v, " ,;\t\r\n\f"); String[] result = new String[st.countTokens()]; for(int i = 0; st.hasMoreTokens(); i++) result[i] = st.nextToken(); return result; } }); // !! error !! // String[] arr2 = GeneralHelper.str2Object(String[].class, "12, 34, 56, 78"); } private static void test_setProperty() { C c = new C(); BeanHelper.setProperty(c, "dt", "2010-10-10"); BeanHelper.setProperty(c, "i", 456); BeanHelper.setProperty(c, "l", "999"); int i = BeanHelper.getProperty(c, "i"); double l = BeanHelper.getProperty(c, "l"); boolean b = BeanHelper.getProperty(c, "b"); Date dt = BeanHelper.getProperty(c, "dt"); System.out.println(c); } private static void test_setProperties_1() throws Exception { Map<String, String[]> objs = new HashMap<String, String[]>(); objs.put("si", new String[] {"888"}); objs.put("fi", new String[] {"999"}); objs.put("b", new String[] {"true"}); objs.put("i", new String[] {"1"}); objs.put("l", new String[] {"2.3"}); objs.put("dt", new String[] {"2011-09-17"}); objs.put("__str", new String[] {"我是怪獸"}); objs.put("__ia", new String[] {"12", "34", "56"}); objs.put("__sa", new String[] {"ab", "cd", "ef"}); Map<String, String> keyMap = new HashMap<String, String>(); keyMap.put("__str", "str"); keyMap.put("__ia", "ia"); keyMap.put("__sa", "sa"); C c = BeanHelper.createBean(C.class, objs, keyMap); Map<String, Object> result = BeanHelper.getProperties(c); System.out.println(result); } private static void test_setProperties_2() throws Exception { java.sql.Date dt = new java.sql.Date(new java.util.Date().getTime()); Map<String, Object> objs = new HashMap<String, Object>(); objs.put("si", 888); objs.put("fi", 999); objs.put("b", "True"); objs.put("i", 123); objs.put("l", "2.3"); //objs.put("dt", new String[] {"2011-09-17"}); objs.put("dt", dt); objs.put("str", "我是怪獸"); objs.put("ia", new int[] {12, 34, 56}); objs.put("sa", "ab, cd, ef"); C c = new C(); BeanHelper.setProperties(c, objs); Map<String, Object> result = BeanHelper.getProperties(c); System.out.println(result); } }
package test; import java.util.Date; class A { private boolean b; public boolean isB() { return b; } public void setB(boolean b) { this.b = b; } } public class C extends A { static int si; final int fi = 100; private int i; private Double l; private Date dt; private String str; private int[] ia; private String[] sa; public String[] getSa() { return sa; } public void setSa(String[] sa) { this.sa = sa; } public static int getSi() { return si; } public static void setSi(int si) { C.si = si; } public int getFi() { return fi; } public String getStr() { return str; } public void setStr(String str) { this.str = str; } public int[] getIa() { return ia; } public void setIa(int[] ia) { this.ia = ia; } public int getI() { return i; } public void setI(int i) { this.i = i; } public Double getL() { return l; } public void setL(Double l) { this.l = l; } public Date getDt() { return dt; } public void setDt(Date dt) { this.dt = dt; } }
上述就是小編為大家分享的如何進行Java中對象與表單的自動裝配了,如果剛好有類似的疑惑,不妨參照上述分析進行理解。如果想知道更多相關知識,歡迎關注億速云行業資訊頻道。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。