宋黎晓的空间

我们一直在努力....

从OECP实体结构浅谈java反射

标签: java反射 reflect equals

  Java反射(reflect)是开发过程中一种常用的手段,常常被用在处理一些感觉有共性但看起来又好像没法抽象的功能上.在OECP项目开发当中,我们在很多地方用到了反射,其中对实体类进行了分层抽象封装的过程中就有一些使用.本文将以重写equals方法为例,简单介绍一下java反射的应用.

  先大体介绍一下我们的实体封装的结构.从UML图中我们可以看到顶级SuperEO类作为整个实体层次的顶端,它包含了所有实体类所共有的方法和属性,比如:克隆方法.我们知道克隆方法的默认实现是Object中的,并且为浅克隆.但浅克隆通常对于我们来说没什么意义,所以我们需要实现一个深克隆的方法(本文主要是说java反射的应用.深克隆和浅克隆的区别不了解的朋友可以去google一下),这个方法为所有实体类所共有.像这样需要所有实体共有的方法还有equals(比较),getAttributeValue(根据字段名得到字段值)等等.SuperEO下有两个分支,分别为BaseEO和BaseVO.BaseEO为可持久化实体的父类,包括跟持久化有关的一些属性和方法.BaseVO为非持久化实体父类,此类暂时没有自己的方法和属性,其存在的目的是预留有备扩充.由此,系统中所有的实体类都从这两个分支派生而来.下面几个类就不详细介绍了,毕竟此文的目的不是介绍实体抽象方案的.以后我们在其他地方再对这个实体结构做详细分析说明.

   我们看到在实体继承关系之外,存在一个叫做EOUtility的类,此类是个EO的辅助工具类,SuperEO依赖这个工具类,来实现一些通用方法.我们所要说的java反射的应用,也就集中在这个工具类中.

   这个类中,我们使用java反射实现了好多通用的功能.在这里我就不每个方法都说明了,只简单介绍其中一个来说一下java反射的用法吧.equals方法是我们java开发人员平时比较常用的一个方法,用来比较两个对象是否相等.此方法的默认实现为Object类中的实现,而默认实现的原理呢,则是按内存地址进行比较,也就是说,如果我们创建了两个内部值完全相同的对象,在我们使用equals对它们进行比较时返回的结果会是false,因为他们的内存地址是不同的.显然这不是我们想要的结果,我们需要的是,将两个对象的内部属性值进行逐一对比,全部相同则返回true,否则为false.为了实现这个目的,我们应该怎么去做呢?下面我们分析一下.

   为了使所有的子类都拥有这个方法,我们必须将这个方法定义在顶级超类SuperEO中.在两个对象进行比较时,我们需要获取对象中所有的属性字段,以及这些字段的get方法,而后使用get方法得到属性值,对这些值进行比较.可是equals方法我们要在SuperEO中实现,而SuperEO的子类是由其他的开发人员定义的,其内部有多少属性字段,都叫什么名字,在我们的SuperEO中是不知道的.既然这些都不知道,我们又怎么能比较这些值呢? 看起来这是个问题.

   但是这个问题能不能解决呢? 能!这就要用到我们所说的java反射机制了.反射机制简单的说是java提供给开发人员用来对类和方法属性进行操作的工具.利用反射机制,我们是可以获取我们想要的属性字段,以及他们的get方法的,并且我们可以调用这些方法.这样我们的问题就解决了,下面可以开工了.我实现的步骤是这样的.

  首先,使用Class.getDeclaredFields方法得到当前类所有的属性字段,而后使用PropertyDescriptor.getReadMethod()得到他们的get方法,并将他们存入一个HashMap中,以字段名为Key,get方法作为Value.在进行字段比较时使用Method.invoke方法,循环调用这些字段的get方法取值而后比较.
代码片段:

Java代码
  1. PropertyDescriptor[] propertyDescriptors = null;   
  2. private void buildGetterANDSetters(Class beanclass){   
  3.     // 得到当前类字段名称   
  4.     Field[] fields = beanclass.getDeclaredFields();   
  5.     String fieldname = null;   
  6.     // 拼接字段对应的方法名   
  7.     for( Field field : fields ){   
  8.         // 一对多字段不要参加toString,hashcode和equals方法,不需要加载,一旦加载反而会引起数据级联更新时出错!!   
  9.         OneToMany onetomany = field.getAnnotation(OneToMany.class);   
  10.         Transient t = field.getAnnotation(Transient.class);   
  11.         if(onetomany != null || t != null){   
  12.             continue;   
  13.         }   
  14.         fieldname = field.getName();   
  15.         for(PropertyDescriptor property : propertyDescriptors){   
  16.             if(fieldname.equals(property.getName())){   
  17.                 Method reader = property.getReadMethod();   
  18.                 Method writer = property.getWriteMethod();   
  19.                 if(reader!=null    
  20.                         && !(reader.isAnnotationPresent(OneToMany.class)||   
  21.                         reader.isAnnotationPresent(Transient.class)))   
  22.                     hm_Geters.put(fieldname, reader);   
  23.                 if(writer!=null  
  24.                         &&!(writer.isAnnotationPresent(OneToMany.class)||   
  25.                         writer.isAnnotationPresent(Transient.class)))   
  26.                     hm_Seters.put(fieldname, writer);   
  27.             }   
  28.         }   
  29.     }   
  30.     // 当前类不是 BaseEntityBean时,递归调用   
  31.     if(!beanclass.equals(SuperEO.class)){   
  32.         buildGetterANDSetters((Class<? extends SuperEO>)beanclass.getSuperclass());   
  33.     }   
  34. }   
  35.   

 而后在equals方法循环调用下面这个方法进行比较:

Java代码
  1. /**  
  2.  * 比较两个对象,指定的字段值是否相同  
  3.  * @author slx  
  4.  * @date 2009-7-17 上午09:51:58  
  5.  * @modifyNote  
  6.  * @param fieldName  
  7.  *      需要比较的字段  
  8.  * @param obj1  
  9.  *      对象1  
  10.  * @param obj2  
  11.  *      对象2  
  12.  * @return  
  13.  *      值相同则为true  
  14.  */  
  15. private boolean equalsField(String fieldName , Object obj1 ,Object obj2){   
  16.     try {   
  17.         Object obj_value = null;   
  18.         Object current_value = null;   
  19.         Method getter = hm_Geters.get(fieldName);   
  20.         current_value = getter.invoke(obj1, null);   
  21.         obj_value = getter.invoke(obj2, null);   
  22.            
  23.         if(current_value == null && obj_value ==null ){   
  24.             return true ;   
  25.         }else if(current_value!=null){   
  26.             return current_value.equals(obj_value);   
  27.         }else if(obj_value!=null){   
  28.             return obj_value.equals(current_value);   
  29.         }   
  30.            
  31.     } catch (Exception e) {   
  32.         throw new RuntimeException(e);   
  33.     }    
  34.     return false ;   
  35. }  

   除equals之外通过类似这样的方式,在我们的EOUtility中我们还实现了根据字段名取值,根据字段名赋值,对象的toString方法打印出所有字段值,得到对象所有属性的名称等方法.这些方法为我们程序其他的一些功能封装时提供了可能性和方便性.比如我们需要封装一个界面表格的输出,我们只要知道当前的实体是SuperEO就可以了,而不需要知道具体是那个子类.通过getAttributeNames就可以得到所有的字段名称,通过getAttributeValue(字段名)就可以得到字段的值,而后使用循环就可以把我们想要的表格画出来了.而这一切的便捷是我们封装的父类中提供的,提供这些方法的前提则是java反射 — reflect.
   当然,java反射绝不仅仅是这么一点点功能,连同动态代理,类加载器等也都应该是属于java反射的范畴.在我们需要封装针对类的通用功能时,是很有用的.

附件:


    评分: 请先登录再投票,同一篇博客一月只能投票一次!
    无人投票

相关博客:


评论


发表评论

关注此文的人们还关注