博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
深度剖析:Java POJO Bean 对象与 Web Form 表单的自动装配
阅读量:6208 次
发布时间:2019-06-21

本文共 19975 字,大约阅读时间需要 66 分钟。

  时下很多 Web 框架 都实现了 Form 表单域与 Java 对象属性的自动装配功能,该功能确实非常有用,试想如果没这功能则势必到处冲积着 request.getParameter() 系列方法与类型转换方法的调用。重复代码量大,容易出错,同时又不美观,影响市容。

  现在的问题是,这些框架通过什么方法实现自动装配的?如果不用这些框架我们自己如何去实现呢?尤其对于那些纯 JSP/Servlet 应用,要是拥有自动装配功能该多好啊!本座深知各位之期盼,决定把自动装配的原理和实现方法娓娓道来。

  实现原理其实比较简单,主要由以下3个步骤构成:

    1. 通过 request.getParameterMap() 获取被提交的 Form 的所有表单域的名称-值映射,其中名称和值均为字符串类型。
    2. 利用 java.beans.PropertyDescriptor 对象获取 Java Bean 的所有属性的名称及其类型。
    3. 把表单域的名称与 Bean 的属性的名称逐个进行匹配,当找到相匹配的属性时就调用 Bean 的 setter 方法把表单域的值设置给该 Bean 属性。当然,因为表单域的值总是字符串类型,很可能与 Bean 属性的类型不一致,所以在设置 Bean 属性前要进行必要的类型转换。

  上面所表述的3点原理不知大家是否完全理解,没关系,下面我们通过一个具体的表单提交的例子来看一看实际的效果,首先看看待提交的表单页面及其代码:

First Name:
Last Name:
Birthday:
Gender: 男
 女
Working age:
Interest: 游泳
 打球
 下棋
 打麻将
 看书
  

 

  从上图可以看出,总共有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
keyMap = new HashMap
(); 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
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
photos; // getter 和 setter 方法 // (略)。。。。。。 }

   从 CheckBean 的代码可以看出,它是通过 createFormBean() 或 fillFormBeanProperties() 方法来自动装配 Persion 的,它们之间的区别是:前者会直接创建新的 Persion 对象,而后者填充原有 Persion 对象的属性。请注意,如果表单域的名称与 Persion 对应的属性名不一致则用 keyMap 进行映射,如表单域 "working-Age" 是对应 Persion 的 workingAge 属性的,但它们的名称不一致,所以要进行映射。另外,Persion 有一个 photos 属性,而我们的表单域却没有,自动装配时会忽略该属性。最后看一下输出的结果页面及其代码:

 
Persion Attributs
Name
 
Brithday
 
Gender
 
Working Age
 
Interest
 
 
Photos
 

  

  通过上面的例子可以看到,通过自动装配 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 createFormBean(Class
clazz) {
return HttpHelper.createFormBean(request, clazz); } /** 使用表单元素创建 Form Bean (用 keyMap 映射与表单元素名称不对应的 Form Bean 属性) */ public final
T createFormBean(Class
clazz, Map
keyMap) {
return HttpHelper.createFormBean(request, clazz, keyMap); } /** 使用表单元素填充 Form Bean (表单元素的名称和 Form Bean 属性名完全一致) */ public final
void fillFormBeanProperties(T bean) { HttpHelper.fillFormBeanProperties(request, bean); } /** 使用表单元素填充 Form Bean (用 keyMap 映射与表单元素名称不对应的 Form Bean 属性) */ public final
void fillFormBeanProperties(T bean, Map
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 createFormBean(HttpServletRequest request, Class
clazz) {
return createFormBean(request, clazz, null); } /** 使用表单元素创建 Form Bean (用 keyMap 映射与表单元素名称不对应的 Form Bean 属性) */ public final static
T createFormBean(HttpServletRequest request, Class
clazz, Map
keyMap) {
Map
properties = getParamMap(request); return BeanHelper.createBean(clazz, properties, keyMap); } /** 使用表单元素填充 Form Bean (表单元素的名称和 Form Bean 属性名完全一致) */ public final static
void fillFormBeanProperties(HttpServletRequest request, T bean) { fillFormBeanProperties(request, bean, null); } /** 使用表单元素填充 Form Bean (用 keyMap 映射与表单元素名称不对应的 Form Bean 属性) */ public final static
void fillFormBeanProperties(HttpServletRequest request, T bean, Map
keyMap) { Map
properties = getParamMap(request); BeanHelper.setProperties(bean, properties, keyMap); } /** 获取 { @link HttpServletRequest} 的所有参数名称和值 */ public final static Map
getParamMap(HttpServletRequest request) { return request.getParameterMap(); } // 其它方法 // (略)。。。 }

  哈哈,大家看到了吧,我们迂回了那么久,但 ActionSupport 类和 HttpHelper 类并没有并没有做多少事情,他们只是获取请求参数 Map,并传递给 BeanHelper 类的 createBean() 和 setProperties() 方法进行装配,实际负责装配工作的是 BeanHelper 。这里解析一下为何要写得那么迂回,其实这些代码是从本座自己写的 “” 中摘录出来的,总之一句话:是因为框架的需要而写得那么迂回的,并非本座有意而为之。顺便做下广告:“”是一套功能完备的 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 : 属性名 / 值映射
* 其中名称为 {
@link String} 类型,与属性名称可能一直也可能不一致
* 属性值可能为以下 3 中类型:
*     1) 属性的实际类型:直接对属性赋值
*     2) {
@link String} 类型:先执行自动类型转换再对属赋值
*     3) {
@link String}[] 类型:先执行自动类型转换再对属赋值
* @param keyMap : properties.key / Bean 属性名映射,当 properties 的 key 与属性名不对应时, * 用 keyMap 把它们关联起来 * @return 生成的 Bean实例 */ public static final
B createBean(Class
clazz, Map
properties, Map
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 createBean(Class
clazz, Map
properties) { return createBean(clazz, properties, null); } /** 设置 Java Bean 的属性 * * @param bean : Bean 实例 * @param properties : 属性名 / 值映射
* 其中名称为 { @link String} 类型,与属性名称可能一直也可能不一致
* 属性值可能为以下 3 中类型:
*     1) 属性的实际类型:直接对属性赋值
*     2) { @link String} 类型:先执行自动类型转换再对属赋值
*     3) { @link String}[] 类型:先执行自动类型转换再对属赋值
* @param keyMap : properties.key / Bean 属性名映射,当 properties 的 key 与属性名不对应时, * 用 keyMap 把它们关联起来 */ public static final
void setProperties(Object bean, Map
properties, Map
keyMap) { // 获取所有 Bean 属性 Map
pps = getPropDescMap(bean.getClass()); Set
> set = properties.entrySet(); // 根据属性名称设置 Bean 的每个属性值 for(Map.Entry
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
void setProperties(Object bean, Map
properties) { setProperties(bean, properties, null); } // 设置指定属性值 private static final
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
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
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
> SMIPLE_CLASS_SET = new HashSet
>(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 str2Object(Class
type, String v, TypeHandler
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 str2Object(Class
type, String v) { return str2Object(type, v, null); } // 其他方法 // (略)。。。 }

 

  从上面的代码可以看出,BeanHelper 支持8种简单数据类型及其包装类、String 和 Date 类型以及它们的数组类型的自动装配,最后强调一下:BeanHelper 和 GeneralHelper 其实是两个用途非常广泛的类,其作用不单是为了协助 Form 表单域自动装配 Bean 。下面列出一些使用例子,帮助大家进一步了解 BeanHelper 的使用方法:

 

View Code
1 package test;   2   3 import java.beans.IntrospectionException;   4 import java.util.Date;   5 import java.util.HashMap;   6 import java.util.Map;   7 import java.util.StringTokenizer;   8   9 import com.bruce.util.BeanHelper;  10 import com.bruce.util.GeneralHelper;  11  12 @SuppressWarnings("unused")  13 public class TestBeanHelper extends Object  14 {
15 public static void main(String[] args) throws Exception 16 {
17 test(); 18 testStr2Object(); 19 test_setProperty(); 20 test_setProperties_1(); 21 test_setProperties_2(); 22 } 23 24 private static void test() 25 {
26 //System.out.println(GeneralHelper.str2Date(" 1978-11-03 ")); 27 //System.out.println(GeneralHelper.str2Date(" 1978-11-03 ")); 28 //GeneralHelper.str2Byte(null); 29 //GeneralHelper.str2Char_0(null); 30 //GeneralHelper.str2Boolean(null); 31 //GeneralHelper.str2Boolean_False(null); 32 } 33 34 private static void testStr2Object() throws IntrospectionException 35 {
36 int i = GeneralHelper.str2Object(int.class, "123"); 37 Date dt = GeneralHelper.str2Object(Date.class, "1978-11-03"); 38 String[] arr = GeneralHelper.str2Object(String[].class, "12, 34, 56, 78", new GeneralHelper.TypeHandler
() 39 {
40 @Override 41 public String[] handle(String v) 42 {
43 StringTokenizer st = new StringTokenizer(v, " ,;\t\r\n\f"); 44 String[] result = new String[st.countTokens()]; 45 46 for(int i = 0; st.hasMoreTokens(); i++) 47 result[i] = st.nextToken(); 48 49 return result; 50 } 51 }); 52 53 // !! error !! 54 // String[] arr2 = GeneralHelper.str2Object(String[].class, "12, 34, 56, 78"); 55 } 56 57 private static void test_setProperty() 58 {
59 C c = new C(); 60 BeanHelper.setProperty(c, "dt", "2010-10-10"); 61 BeanHelper.setProperty(c, "i", 456); 62 BeanHelper.setProperty(c, "l", "999"); 63 int i = BeanHelper.getProperty(c, "i"); 64 double l = BeanHelper.getProperty(c, "l"); 65 boolean b = BeanHelper.getProperty(c, "b"); 66 Date dt = BeanHelper.getProperty(c, "dt"); 67 System.out.println(c); 68 } 69 70 private static void test_setProperties_1() throws Exception 71 {
72 Map
objs = new HashMap
(); 73 objs.put("si", new String[] {
"888"}); 74 objs.put("fi", new String[] {
"999"}); 75 objs.put("b", new String[] {
"true"}); 76 objs.put("i", new String[] {
"1"}); 77 objs.put("l", new String[] {
"2.3"}); 78 objs.put("dt", new String[] {
"2011-09-17"}); 79 objs.put("__str", new String[] {
"我是怪兽"}); 80 objs.put("__ia", new String[] {
"12", "34", "56"}); 81 objs.put("__sa", new String[] {
"ab", "cd", "ef"}); 82 83 Map
keyMap = new HashMap
(); 84 keyMap.put("__str", "str"); 85 keyMap.put("__ia", "ia"); 86 keyMap.put("__sa", "sa"); 87 88 C c = BeanHelper.createBean(C.class, objs, keyMap); 89 Map
result = BeanHelper.getProperties(c); 90 System.out.println(result); 91 } 92 93 private static void test_setProperties_2() throws Exception 94 { 95 java.sql.Date dt = new java.sql.Date(new java.util.Date().getTime()); 96 97 Map
objs = new HashMap
(); 98 objs.put("si", 888); 99 objs.put("fi", 999); 100 objs.put("b", "True"); 101 objs.put("i", 123); 102 objs.put("l", "2.3"); 103 //objs.put("dt", new String[] {"2011-09-17"}); 104 objs.put("dt", dt); 105 objs.put("str", "我是怪兽"); 106 objs.put("ia", new int[] { 12, 34, 56}); 107 objs.put("sa", "ab, cd, ef"); 108 109 C c = new C(); 110 BeanHelper.setProperties(c, objs); 111 Map
result = BeanHelper.getProperties(c); 112 System.out.println(result); 113 } 114 115 116 }
View Code
1 package test;  2  3 import java.util.Date;  4  5  6 class A  7 {
8 private boolean b; 9 10 public boolean isB() 11 {
12 return b; 13 } 14 15 public void setB(boolean b) 16 {
17 this.b = b; 18 } 19 20 } 21 22 public class C extends A 23 {
24 static int si; 25 final int fi = 100; 26 27 private int i; 28 private Double l; 29 private Date dt; 30 private String str; 31 private int[] ia; 32 private String[] sa; 33 34 public String[] getSa() 35 {
36 return sa; 37 } 38 public void setSa(String[] sa) 39 {
40 this.sa = sa; 41 } 42 public static int getSi() 43 {
44 return si; 45 } 46 public static void setSi(int si) 47 {
48 C.si = si; 49 } 50 public int getFi() 51 {
52 return fi; 53 } 54 public String getStr() 55 {
56 return str; 57 } 58 public void setStr(String str) 59 {
60 this.str = str; 61 } 62 public int[] getIa() 63 {
64 return ia; 65 } 66 public void setIa(int[] ia) 67 {
68 this.ia = ia; 69 } 70 public int getI() 71 {
72 return i; 73 } 74 public void setI(int i) 75 {
76 this.i = i; 77 } 78 public Double getL() 79 {
80 return l; 81 } 82 public void setL(Double l) 83 {
84 this.l = l; 85 } 86 public Date getDt() 87 {
88 return dt; 89 } 90 public void setDt(Date dt) 91 {
92 this.dt = dt; 93 } 94 }

 

  老规矩,想看完整代码者请轻轻 Touch 一下:  

 

转载于:https://www.cnblogs.com/ldcsaa/archive/2012/02/16/2353030.html

你可能感兴趣的文章
Linux特殊符号及基础正则表达式
查看>>
页面广告飘窗
查看>>
MySQL性能优化的最佳20+条经验 【转】
查看>>
自适应滤波:矩阵求逆
查看>>
SVN--从本地检出项目至服务器报错--禁止访问
查看>>
[LeetCode] Remove Invalid Parentheses
查看>>
3年外包码农近期烦心事
查看>>
如何用Fritzing实现元器件自定义接线图
查看>>
Educational Codeforces Round 37-E.Connected Components?题解
查看>>
4.13.2
查看>>
移动端图片处理
查看>>
使用FIDDER 抓包构建请求
查看>>
Linux误删文件挽救
查看>>
MongoDB整理笔记の体系架构
查看>>
HTML5 Web 客户端五种离线存储方式汇总
查看>>
石子博弈
查看>>
select Option(增加,删除,清空)
查看>>
centos7 mysql数据库的安装与使用
查看>>
四: 使用vue搭建网站前端页面
查看>>
四:DRF项目开发的准备
查看>>