Java form表单原理_Form 表单域与 Java 对象属性的自动装配功能

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

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

实现原理其实比较简单,主要由以下3个步骤构成: 通过 request.getParameterMap() 获取被提交的 Form 的所有表单域的名称-值映射,其中名称和值均为字符串类型。

利用 java.beans.PropertyDescriptor 对象获取 Java Bean 的所有属性的名称及其类型。

把表单域的名称与 Bean 的属性的名称逐个进行匹配,当找到相匹配的属性时就调用 Bean 的 setter 方法把表单域的值设置给该 Bean 属性。当然,因为表单域的值总是字符串类型,很可能与 Bean 属性的类型不一致,所以在设置 Bean 属性前要进行必要的类型转换。

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

2012021610062532.jpg

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 属性,而我们的表单域却没有,自动装配时会忽略该属性。最后看一下输出的结果页面及其代码:

2012021610092274.jpg

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 。这里解析一下为何要写得那么迂回,其实这些代码是从本座自己写的 “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    : 属性名 / 值映射

*                        其中名称为 {@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

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()

{

@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 objs = new HashMap();

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 keyMap = new HashMap();

keyMap.put("__str", "str");

keyMap.put("__ia", "ia");

keyMap.put("__sa", "sa");

C c = BeanHelper.createBean(C.class, objs, keyMap);

Map 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 objs = new HashMap();

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 result = BeanHelper.getProperties(c);

System.out.println(result);

}

}

View Code

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;

}

}

posted on 2013-01-07 09:40 杨军威 阅读(4250) 评论(1)  编辑  收藏

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值