缘起
在jdbc应用中,我们经常需要有这么样的一个javabean:当我们从数据库里取值时,我们希望把对应的值赋给javabean,而后再操作javabean进行各种业务处理;而我们保存数据的时候,也希望把经过业务处理后的值赋给javabean,再由该javabean与jdbc交互,将数据保存在数据库里。
而在struts应用中,我们经常要跟actionform或dynaactionform打交道,例如从业面取得用户输入的数据,在struts应用中,我们实际上是从actionform中取得数据;而将数据显示给用户的时候,我们实际上是将数据赋给actionform。在实际的应用中,我们经常也有一个中间的javabean,用来和actionform进行打交道,也就是取值、赋值。
以上两种应用,都需要一个中间的javabean,从数据源取得数据,赋给javabean;然后再操作javabean进行业务逻辑的处理;最后通过赋值,将数据从javabean交给数据源。
这其中都有如下的赋值过程:
stmt = (oraclecallablestatement) conn.preparecall("{ call ft_save_fabric.ft_fab_colorway_general_insert(" + "?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?) }");
stmt.setlong(1, fabric.getbom_id());
stmt.setlong(2, general.getcolorway_number());
stmt.setlong(3, general.getcolor_number());
stmt.setfloat(4, general.getcolorway_type_id());
stmt.setstring(5, general.getprint_id());
stmt.setstring(6, general.getprint_name());
stmt.setfloat(7, general.getprint_type_id());
stmt.setfloat(8, general.getprint_repeat_type_id());
stmt.setfloat(9, general.getprint_repeat_height());
stmt.setlong(10, general.getprint_repeat_height_uom_id());
stmt.setfloat(11, general.getprint_repeat_width());
stmt.setlong(12, general.getprint_repeat_width_uom_id());
stmt.setlong(13, general.getprint_status_id());
stmt.setstring(14, general.getyarn_dye_id());
stmt.setstring(15, general.getyarn_dye_name());
stmt.setint(16, general.getyarn_wrap_color_number());
stmt.setint(17, general.getyarn_weft_color_number());
stmt.setfloat(18, general.getyarn_repeat_height());
stmt.setlong(19, general.getyarn_repeat_height_uom_id());
stmt.setfloat(20, general.getyarn_repeat_width());
stmt.setlong(21, general.getyarn_repeat_width_uom_id());
stmt.setlong(22, general.getyarn_status_id());
stmt.setlong(23, general.getprint_process_id());
stmt.execute();
例一
以上是关于针对jdbc的赋值代码片断,让我们再看一个例子:
public int setfabricwebbasedata(dynaactionform form, httpservletrequest request, le_fabricbean bean) {
bean.setfabric_no((string) form.get("txtid"));
bean.setbarcode_id((string) form.get("txtbarcodeid"));
bean.setstatus_id(transformtools.getobjectint((string) form.get("ddlstatus")));
bean.setmaterial_type_id(transformtools.getobjectint((string) form.get("ddlmaterialtype")));
bean.setfabric_type_id(transformtools.getobjectint((string) form.get("ddlfabrictype")));
bean.setfabric_end_use_id(transformtools.getobjectint((string) form.get("ddlenduse")));
bean.setvendor_number((string) form.get("txtarticle"));
bean.setregion_id(transformtools.getobjectint((string) form.get("ddlregion")));
bean.setcountry_id(transformtools.getobjectint((string) form.get("ddlcountry")));
bean.setcolor_range_id(transformtools.getobjectint((string) form.get("ddlcolorrange")));
bean.setpattern_id(transformtools.getobjectint((string) form.get("ddlpattern")));
system.out.println("====================>>"+form.get("txtcomments"));
bean.setdescription((string) form.get("txtdescription"));
bean.setdetail((string) form.get("txtcomments"));
bean.setformset_id(transformtools.getobjectstring(request.getparameter("hidformsetid")));
libimagemanage.setimagedata(request, bean);
return 0;
}
例二
很明显,这个例子是关于actionform与javabean之间的赋值。
由上面两个例子,我们可以看到在jdbc中、或struts中,javabean与数据源、javabean与actionform之间的赋值是十分繁琐的,也带来了大量的代码冗余;特别是在一个实际的b/s构架的应用中,我们经常采用struts+jdbc的开发模式,这其中有大量的javabean在jdbc中与数据源之间来回赋值,javabean与actionform之间的来回赋值,两者相加起来,代码的冗余量大得惊人,对程序员来说,进行这样的赋值,的确是枯燥而乏味的。
这么冗长的代码,我一眼看下去,就发现其实代码的结构都一样,都是从一个对象里面取值,再赋到另外一个对象里头去。这样,我的第一想法是做一个公用的方法,然后我们来进行for循环调用。再往下,我就傻眼了,因为不管是哪个类的取值或者赋值,他们的方法都不是一样的,如例一中,既有stmt.setstring,又有stmt.setlong。这样,我们用常规的方法提取不出一个公用的方法来。
分析到这里,我感到一种进入死胡同的感觉。但不管怎么样,我能确定解决之道是一定要抽取出一个公用的方法来,然后通过for循环来调用该方法。可以确定,问题是怎么构造出这个公用的方法。
对于这个公用的方法,我们手头有的是:对象、对象的方法名、该方法的参数,而我们需要调用该方法。说的明确一些,是在运行期内调用对象的方法。这样,我们就心里有底了,java的反射机制能解决该问题。
反射机制的片断回顾
现在我们来回顾一下java的反射机制:
之所以说是片断回顾,因为我们不打算把整个java的反射机制在这里陈述一遍,这不是本文的目的。本文的目的是让已经学习过java反射机制的读者或者没有学习过java反射机制的读者看一个java反射机制在实际中应用的例子;对于没有学习过java反射机制的读者来说,通过本文,知道java反射机制在实际中是怎么应用的,增加读者学习java反射机制的热情,从而进一步深入的学习java反射机制;对于已经学习过java反射机制的读者来说,可以通过本文,看看java反射机制在实际中是怎么应用的,进一步深入的理解java反射,这一点我深有体会,我在学习了java反射后的很长一段时间内,没有深入的理解它,进而不知道怎么在实际中用到它。
如果读者想深入的学习java反射机制,可以参考本文所列的参考文章学习,这些文章都是我在学习java反射机制的时候用过的,可以说是有一定的代表性。
一句话来说,这里所要陈述的java反射机制,都是在本文中所要用到的java反射机制。读者在看到本文的问题的解决思路时,不妨将实际问题的解决方法和我即将陈述的java反射机制结合起来看,以增加对java反射机制的理解。
好,闲话少说,让我们来看一看本文所要用到的java反射机制:
1.取得对象的class对象
每一个对象都有一个getclass()方法,可以取得该对象的class对象。如取得对象o的class对象的代码如下:
class c = o.getclass();
2.对象的属性
java反射机制可以挖掘对象本身的元数据,比如,对象的父类、对象的属性、对象的方法等等。这里我们来看看java反射机制是怎么来取得对象的属性的。
有了对象的class对象,我们就可以用getdeclaredfields()方法来取得该对象所有的属性;我们也可以通过某一个属性的属性名来取得该属性,方法为:getdeclaredfield(string fieldname)。如,我们想取得o对象里的一个name属性,代码如下:
class c = o.getclass();
field f = c.getdeclaredfield(“name”);
有了属性对象,我们可以进一步获取该属性的信息。如属性的作用域,属性的类型等等。我们来看一下怎么取得属性的类型,代码如下:
class t = f.gettype();
这样,我们可以获得属性类型名:
string typename = t.getname();
3. 取得对象的方法
上面取得了对象的属性,同样我们也可以取得对象的方法和该方法的元数据,如该方法的作用域、输入参数、返回值类型等等。
getdeclaredmethod(string functionname,class[] types)
该方法有两个输入参数,第一个是functionname,我们一看就知道是我们需要获取的方法的方法名;第二个参数types是一个class类型的数组对象,是我们需要获取的方法的输入参数的类型所组成的class数组。
如,我们想取得o对象的public string func(string s, hashtable ht)这样一个方法。
首先我们知道该方法的方法名为:“func”
下面我们来构造getdeclaredmethod方法的第二个输入参数:
class ptypes[] = new class[2];
ptypes[0] = class.forname("java.lang.string");
ptypes[1] = class.forname("java.util.hashtable");
这样,我们就可以取得该方法了:
method m = c.getmethod("func",ptypes);
4.运行期内调用对象的方法
可以说,运行期内调用对象的方法是java反射机制的核心所在。
这里所说的运行期内调用对象的方法,包括运行期内调用类的构造器,即在运行期内生成类的实例,这在实际的应用中也是十分有用的java反射机制。但在本文中的例子由于没有使用到,因此不再陈述,有兴趣的读者可以到本文后面的参考文献中去学习。
这里重点说一说在运行期内调用对象的普通方法
invoke(object o,object[] args)
该方法也有两个输入参数:一个是object对象,就是我们需要在运行期内调用的方法所在的对象,如上面一直提到的o对象;第二个参数是一个由object对象组成的数组,是指我们需要在运行期内调用的方法的实际输入参数。
如果输入参数为”hello,world!”和null,我们想调用o对象的public string func(string s, hashtable ht)这样一个方法,常规的调用方式为:
string s = o.func(“hello,world!”,null);
我们应用java的反射机制在运行期内的调用方式为:
object args[] = new object[2];
arg[0] = new string("hello,world");
arg[1] = null;
object r = m.invoke(obj, arg);
string s = (string)r;
其中,对象m已经通过前面的java反射机制获取。
到这里,在本文中要用到的java反射机制就已经全部陈述了一遍。可以看到,本文真正要用到的关于java反射机制的知识点并不是很多,但java反射机制这样的一个牛刀小试,就让我们的代码优化了很多,可见java反射机制的确是魅力无穷。
问题的解决
我们先看例一的问题的处理过程:
通过前面的分析,可以得出,我们需要这样一个公用的方法:在这个方法里头,我们可以提供的输入参数为:
一. stmt,用来取得table中字段的值。
二. 在数据库table中,我们要取得的字段的位置,如例一中是从1到23,我们可以构造一个int型的数组:int[] ports = new int[]{1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23};
三. javabean的对象,在例一为:general
四. 一个javabean的属性名所组成数组,跟前面的字段位置一一对应。在例一中为:string[] propnames = new string[]{“bom_id”,”colorway_number”,” color_number”,”colorway_type_id”,”print_id”,”print_name”,”print_type_id”,”print_repeat_type_id”,”print_repeat_height”,”print_repeat_height_uom_id”,”print_repeat_width”,”print_repeat_width_uom_id”,”print_status_id”,”yarn_dye_id”,”yarn_dye_name”,”yarn_wrap_color_number”,”yarn_weft_color_number”,”yarn_repeat_height”,”yarn_repeat_height_uom_id”,”yarn_repeat_width”,”yarn_repeat_width_uom_id”,”yarn_status_id”,”print_process_id”};可以看出,这些属性名都是javabean的setxxx()方法对应的属性的名称。
有了上面的四个输入参数,我们希望调用了这样一个公用的方法,stmt中的字段的值就会赋到javabean中相应的属性中去。
可以确定需要构造如下的一个公用的方法:
public static void stmttobean(string[] propnames,object o,int[] ports, oracle.jdbc.oraclecallablestatement stmt)
在这个方法里,我们首先要做一个循环;在循环体内,我们先从stmt中取出值来;再赋给javabean对象o。
我们来看一下实际的代码:
public static void stmttobean(string[] propnames,object o,int[] ports, oraclecallablestatement stmt) throws valuemanagerexception
{
//首先我们判断属性名数组是否和字段位置一一对应,如果不是抛出违例。
if(propnames.length!=ports.length)
{
system.out.println(“input args wrong:the propnames’ length is not the same with the ports’ length!”);
throw new valuemanagerexception(“input args wrong:the propnames’ length is not the same with the ports’ length!”);
}
try
{
//对属性名数组进行循环
for(int i=0;i<fieldnames.length;i++)
{
//在下面的语句中,我们首先取得属性名propnames[i];再对javabean对象o取得它的class对象,o.getclass();最后通过class对象取得属性名对应的属性。
field f=o.getclass().getdeclaredfield(propnames[i])
//我们通过属性取得该属性对应的类型的名称。
string type=f.gettype().getname();
//下面的语句作为重点,我们做一个标记,在后面来陈述。
valuesetter.valuetobean(type,tools.getsettername(propnames[i]),o,valuegetter.getstmtvalue(type,ports[i],stmt)); 语句一
}
}
catch(exception e)
{
e.printstacktrace();
}
在上面的语句一中,
我们先看valuetobean方法的输入参数:
第一个参数,type是我们前面取得的属性的类型;
第二个参数,里面有一个getsettername方法,显然是想取得属性名对应的set方法,我们来看tools类里头的getsettername方法,
public static string getsettername(string propname)
{
return "set"+propname.substring(0,1).touppercase()+propname.substring(1,propname.length());
}
可以看出该方法的确是由属性名取得了该属性对应的set方法名;
第三个参数,o是对应的javabean对象;
第四个参数,里头有一个getstmtvalue方法,可以看出该方法是想取得stmt中对应位置的值,我们来看valuegetter类中的getstmtvalue方法的实现:
public static string getstmtvalue(string type,int port, oraclecallablestatement stmt)
{
try
{
if(type.equals("java.lang.string"))
{
return stmt.getstring(port);
}
else if(type.equals("int"))
{
return string.valueof(stmt.getint(port));
}
else if(type.equals("long"))
{
return string.valueof(stmt.getlong(port));
}
else if(type.equals("float"))
{
return string.valueof(stmt.getfloat(port));
}
else
{
system.out.println("no such type!");
return null;
}
}
catch(exception e)
{
e.printstacktrace();
return null;
}
}
果然是从stmt中取得对应位置的值。注意:读者可以看到,在该方法中列出的几种类型显然是不够的,读者可以自己加上其他的类型的取值。
最后,我们来看valuesetter类的valuetobean方法。我们可以分析出,该方法的功能是将从数据源stmt中取得的值赋给javabean对象o对应的字段。
valuetobean的实现如下:
public static void valuetobean(string type,string methodname,object o,string value)
{
try
{
if(tools.isblank(s)&&!(type.equals(“string”))) return;
//首先还是取得o对象对应的class对象。
class c=o.getclass();
//对于下面的两个语句,我们也作了标记,在后面陈述。
class[] types=tools.gettypes(type); 语句二
object[] args=tools.getargs(type,value); 语句三
//取得javabean的方法对象。
method m=c.getmethod(methodname,types);
//调用该方法。
m.invoke(o,args);
}
catch(exception e)
{
e.printstacktrace();
}
}
我们来看语句二:
显然是用来构造参数类型的class数组。因为在javabean中setxxx方法只有一个参数,该参数的类型名为type,所以tools.gettypes(type)只有一个参数类型type需要构造,gettypes方法的实现如下:
public static class[] gettypes(string type)
{
if(type.equals("java.lang.string"))
{
return new class[]{string.class};
}
else if(type.equals("int"))
{
return new class[]{integer.type};
}
else if(type.equals("long"))
{
return new class[]{long.type};
}
else if(type.equals("float"))
{
return new class[]{float.type};
}
else
{
system.out.println("no such type!");
return null;
}
}
在这个方法里头,同样,没有列举出来的类型读者可以补充。
语句三是根据invoke方法的需要,构造输入参数值的object类型的数组,getargs的实现如下:
public static object[] getargs(string type,string value)
{
if(type.equals("java.lang.string"))
{
return new object[]{new string(value)};
}
else if(type.equals("int"))
{
return new object[]{new integer(integer.parseint(value))};
}
else if(type.equals("long"))
{
return new object[]{new long(long.parselong(value))};
}
else if(type.equals("float"))
{
return new object[]{new float(float.parsefloat(value))};
}
else
{
system.out.println("no such type!");
return null;
}
}
以上我们就完整的将例一的问题的解决思路分析了一遍。
现在我们来看调用该共用方法的例一的代码实现:
stmt = (oraclecallablestatement) conn.preparecall("{ call ft_save_fabric.ft_fab_colorway_general_insert(" + "?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?) }");
int[] ports = new int[]{1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23};
string[] propnames = new string[]{“bom_id”,”colorway_number”,” color_number”,”colorway_type_id”,”print_id”,”print_name”,”print_type_id”,”print_repeat_type_id”,”print_repeat_height”,”print_repeat_height_uom_id”,”print_repeat_width”,”print_repeat_width_uom_id”,”print_status_id”,”yarn_dye_id”,”yarn_dye_name”,”yarn_wrap_color_number”,”yarn_weft_color_number”,”yarn_repeat_height”,”yarn_repeat_height_uom_id”,”yarn_repeat_width”,”yarn_repeat_width_uom_id”,”yarn_status_id”,”print_process_id”};
valuemanager. stmttobean(propnames,general,ports,stmt);
stmt.execute();
同样,我们对例二的调用公用方法的实现如下:
public int setfabricwebbasedata(dynaactionform form, httpservletrequest request, le_fabricbean bean) {
string[] propnames=new string[]{“fabric_no”,”barcode_id”,”status_id”,”material_type_id”,”fabric_type_id”,”fabric_end_use_id”,”vendor_number”,”region_id”,”country_id”,”color_range_id”,”pattern_id”,”detail”,”formset_id”};
string[] fieldnames=new string[]{"txtid","txtbarcodeid","ddlstatus","ddlmaterialtype","ddlfabrictype","ddlenduse","txtarticle","ddlregion","ddlcountry","ddlcolorrange","ddlpattern","txtdescription","txtcomments","hidformsetid"};
valuemanager.formtobean(propnames,bean,fieldnames,form);
libimagemanage.setimagedata(request, bean);
return 0;
}
关于公用方法formtobean(string[] propnames,object o,string[] fieldnames,dynaactionform form)的具体实现,我们可以到附录的代码里找到他们,这里就不再陈述。
后话
至此,我们已经运行java反射机制来解决了在jdbc和struts应用中的一些繁琐的赋值问题。可以看出,应用java反射机制,的确能使程序代码变得更加灵活与方便。正是由于这个原因,java反射机制在一个应用的核心代码里被广泛使用。
在很多时候,我们需要知道一个由客户输入的类的元数据,在运行期内调用类的方法,在运行期内实例化一个类,等等都需要用到java反射机制;还有工厂模式如果和java反射机制结合起来,将更加灵活;java反射机制和代理模式结合起来,形成动态代理模式等等。
一句话,java反射机制让我们的代码更加的灵活与方便,应用的范围十分广泛。
参考文献
1.侯捷观点——java反射机制
http://editblog.csdn.net/programmer/archive/2004/10/27/806.aspx
2. java 编程的动态性
http://www-900.ibm.com/developerworks/cn/java/j-dyn0429/index.shtml
3.java reflection (java反射)
http://dev.csdn.net/article/58/58798.shtm
附录
