之前的项目中,遇到一个问题,描述如下:
1.接口A请求结果转换后的EntityA与控件有高度耦合,控件大部分功能由EntityA的各属性来控制(这个看起来没有问题,虽然控件和业务逻辑最好能够分开,但是大部分情况下仍然很难做到)。
2.有一个新需求,服务端提供了一个接口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进去。
代码如下
/**
* Created by puff on 15/7/31.
*/
public class AnnotationUtils {
/**
* *
* 将源entity转为目标entity依赖于MapProperty
*
* @param obj 源entity
* @param c 目标entity类
* @return 目标entity
* @throws Exception 反射处理annotation的异常
*/
public static Object transformEntity(Object obj, Class c) throws Exception {
if (obj == null || c == null) {
return null;
}
if (obj instanceof List) {
//列表单独处理
List src = (List) obj;
List result = new LinkedList();
for (Object item : src) {
result.add(transformEntity(item, c));
}
return result;
} else {
//创建目标entity,待填充
Object result = c.newInstance();
Field[] declaredFields = obj.getClass().getDeclaredFields();
for (Field field : declaredFields) {
//源属性开放属性权限
field.setAccessible(true);
if (field.isAnnotationPresent(MapProperty.class)) {
//获取源属性的映射注解
MapProperty annotation = field.getAnnotation(MapProperty.class);
Object value = field.get(obj);
if (!TextUtils.isEmpty(annotation.mapClass())) {
//复杂属性,递归处理
value = transformEntity(value, Class.forName(annotation.mapClass()));
}
Field resultField = c.getDeclaredField(annotation.mapProperty());
resultField.setAccessible(true);
resultField.set(result, value);
}
}
return result;
}
}
}
例子(将EntityA转为EntityB)
/**
*
* @author puff
*/
public class EntityA {
@MapProperty(mapProperty = "value_1")
private String value1;
@MapProperty(mapProperty = "value_2", mapClass = "javaannotation.EntityB2")
private List<List<EntityA2>> value2;
public String getValue1() {
return value1;
}
public void setValue1(String value1) {
this.value1 = value1;
}
public List<List<EntityA2>> getValue2() {
return value2;
}
public void setValue2(List<List<EntityA2>> value2) {
this.value2 = value2;
}
}
/**
*
* @author puff
*/
public class EntityB {
private String value_1;
private List<List<EntityB2>> value_2;
public String getValue_1() {
return value_1;
}
public void setValue_1(String value_1) {
this.value_1 = value_1;
}
public List<List<EntityB2>> getValue_2() {
return value_2;
}
public void setValue_2(List<List<EntityB2>> value_2) {
this.value_2 = value_2;
}
}
public static void main(String[] args) throws Exception {
EntityA entityA = getEntityA();
Object object = transformEntity(entityA, EntityB.class);
if (!(object instanceof EntityB)) {
System.out.println("error1");
}
EntityB entityB = (EntityB) object;
System.out.println("succ1");
}
局限性
最后说一下这种做法的局限性,这样实现确实解决了我的问题,因为后端的A,B接口基本上是一致的。
但是对于多层次Entity来说,有可能层级不对称,譬如要把一个boolean转化为自定义类的某个变量上或一个自定义类的某个变量转化为一个boolean,这种不对称的情况是处理不了的。
如果真的遇到了这种情况,情况较少的话,可以考虑单独取值然后set。