问题描述:
之前的项目中,遇到一个问题,描述如下:
- 接口A请求结果转换后的EntityA与控件有高度耦合,控件大部分功能由EntityA的各属性来控制(这个看起来没有问题,虽然控件和业务逻辑最好能够分开,但是大部分情况下仍然很难做到);
- 有一个新需求,服务端提供了一个接口B,与接口A十分相似,并且希望能够复用原有的控件。
在大部分情况下不会遇到这种情况,但是当有多个后端团队提供支持的时候,便有可能出现(譬如参数规范到底是驼峰式还是下划线?)。
在java开发中,有很多Bean(Entity)间转换的工具,大部分采用了XML映射的方式。这种方式在服务端开发可能会更方便一些,但是在android开发中,对XML的属性文件支持不够(对XML属性文件的处理并不是很在行)。那么有没有对android更方便的Entity间转换方案呢?
实现方案:
基于java中注解和反射的使用,笔者编写如下一个工具类,可以实现android平台上entity间的转换,下面介绍这个工具。
编写annotation与annotationHandler
1. 首先,声明一个annotation。若EntityA要映射到EntityB的话,那么对EntityA中需要映射的属性,进行mapClass和mapProperty注解。
对于单层Entity(Entity中属性均为基本类型或者String),只需要使用mapProperty即可。
若为多层Entity(Entity中含有自定义的Entity属性),不仅需要使用mapProperty来设置对应的属性名,也需要mapClass来设置对应的类名了。
代码如下所示:
/** * 属性间的1:1映射关系 * * Created by puff on 15/7/31. */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface MapProperty { public String mapClass() default ""; public String mapProperty() default ""; }
2. 然后,我们再写一个annotation-handler,如果不对annotation做处理的话,那就没什么意义了。
工具类中第一个参数为源entity,第二个参数为目标entity类,换句话说我们把一个srcObj转到desObj,就需要有源obj和目标obj类两个参数。
首先若是列表类型,则对列表做处理(json转换过来的entity很多都是有列表的)。因为列表(和数组类似)这种属性比较特殊,不是自定义Entity也不属于基本类型,我们需要做的就是遍历列表,把item挨个进行转换。
其次,我们需要转换的无非就是一些基本类型和自定义类型了。备注写的比较详细,生成一个目标obj对象,通过反射检测源obj属性是否需要映射。不需要映射则直接pass(目标obj根本不需要这个属性)。若需要映射且是基本类型则直接取值并set进目标obj,若需要映射却是自定义类型则先递归处理再将结果set进去。
代码如下:
1 /** 2 * Created by puff on 15/7/31. 3 */ 4 public class AnnotationUtils { 5 /** 6 * * 7 * 将源entity转为目标entity依赖于MapProperty 8 * 9 * @param obj 源entity 10 * @param c 目标entity类 11 * @return 目标entity 12 * @throws Exception 反射处理annotation的异常 13 */ 14 public static Object transformEntity(Object obj, Class c) throws Exception { 15 if (obj == null || c == null) { 16 return null; 17 } 18 if (obj instanceof List) { 19 //列表单独处理 20 List src = (List) obj; 21 List result = new LinkedList(); 22 for (Object item : src) { 23 result.add(transformEntity(item, c)); 24 } 25 return result; 26 } else { 27 //创建目标entity,待填充 28 Object result = c.newInstance(); 29 Field[] declaredFields = obj.getClass().getDeclaredFields(); 30 for (Field field : declaredFields) { 31 //源属性开放属性权限 32 field.setAccessible(true); 33 if (field.isAnnotationPresent(MapProperty.class)) { 34 //获取源属性的映射注解 35 MapProperty annotation = field.getAnnotation(MapProperty.class); 36 Object value = field.get(obj); 37 if (!TextUtils.isEmpty(annotation.mapClass())) { 38 //复杂属性,递归处理 39 value = transformEntity(value, Class.forName(annotation.mapClass())); 40 } 41 Field resultField = c.getDeclaredField(annotation.mapProperty()); 42 resultField.setAccessible(true); 43 resultField.set(result, value); 44 } 45 } 46 return result; 47 } 48 } 49 50 }
例子(将EntityA转为EntityB)
1 /** 2 * 3 * @author puff 4 */ 5 public class EntityA { 6 7 @MapProperty(mapProperty = "value_1") 8 private String value1; 9 @MapProperty(mapProperty = "value_2", mapClass = "javaannotation.EntityB2") 10 private List<List<EntityA2>> value2; 11 12 public String getValue1() { 13 return value1; 14 } 15 16 public void setValue1(String value1) { 17 this.value1 = value1; 18 } 19 20 public List<List<EntityA2>> getValue2() { 21 return value2; 22 } 23 24 public void setValue2(List<List<EntityA2>> value2) { 25 this.value2 = value2; 26 } 27 28 } 29 30 /** 31 * 32 * @author puff 33 */ 34 public class EntityB { 35 36 private String value_1; 37 private List<List<EntityB2>> value_2; 38 39 public String getValue_1() { 40 return value_1; 41 } 42 43 public void setValue_1(String value_1) { 44 this.value_1 = value_1; 45 } 46 47 public List<List<EntityB2>> getValue_2() { 48 return value_2; 49 } 50 51 public void setValue_2(List<List<EntityB2>> value_2) { 52 this.value_2 = value_2; 53 } 54 55 } 56 57 public static void main(String[] args) throws Exception { 58 EntityA entityA = getEntityA(); 59 Object object = transformEntity(entityA, EntityB.class); 60 if (!(object instanceof EntityB)) { 61 System.out.println("error1"); 62 } 63 EntityB entityB = (EntityB) object; 64 System.out.println("succ1"); 65 }
局限性:
因为后端的A,B接口基本上是一致的,如上的实现方法确实解决了笔者遇到的问题。
但对于多层次Entity的情况来说,有可能出现层级不对称的问题:譬如要把一个boolean转化为自定义类的某个变量上,或当一个自定义类的某个变量转化为一个boolean的情况下,使用如上实现方法并不能解决问题。如果真的遇到了这种情况,并且该情况出现的比较少,则可以考虑单独取值后set。