简化繁琐的赋值——反射在Jdbc和Struts中的应用

80酷酷网    80kuku.com

  缘起
在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






附录



分享到
  • 微信分享
  • 新浪微博
  • QQ好友
  • QQ空间
点击: