1、背景
目前使用org.springframework.beans.BeanUtils类可使用copy()等方法进行对象实体的属性值映射。但是目前有些是需要不用对象之间属性及属性值的映射。
2、demo
注解类ConvertField.java
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 适用不同属性拷贝(可设置属性别名)
*
* @Description:
* @date: 2021/6/17 11:36
*/
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface ConvertField {
/**
* 在将被拷贝的属性上面,设置目标属性名
*/
String targetName() default "";
/**
* 在将拷贝至改属性上面,设置源属性名
*/
String sourceName() default "";
}
映射属性工具类BeanConvertUtils.java
import cn.hutool.core.collection.CollUtil;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.zhkj.ims.annotation.ConvertField;
import lombok.extern.slf4j.Slf4j;
import javax.annotation.Nullable;
import java.beans.IntrospectionException;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.*;
/**
* 处理不同对象的不同属性映射
*
* @Description:
* @date: 2021/6/17 11:49
*/
@Slf4j
public class BeanConvertUtils {
public BeanConvertUtils() {
}
/**
* 拷贝一个对象的属性至另一个对象。支持两个对象之间不同属性名称进行拷贝,使用注解{@link ConvertField}
*
* @param source 源对象 使用注解{@link ConvertField#targetName()}
* @param targetClazz 目标对象 使用注解{@link ConvertField#sourceName()}
*/
public static <T> T convert(@Nullable Object source, Class<T> targetClazz) {
if (Objects.isNull(source)) {
return null;
}
Map<String, Object> sourceFieldKeyAndValueMap = Maps.newHashMap();
// 生成目标类对象
T instance = org.springframework.beans.BeanUtils.instantiateClass(targetClazz);
//生成源bean的属性及其值的字典
createSourceFieldAndValue(source, sourceFieldKeyAndValueMap, source.getClass());
//设置目标bean的属性值
setTargetFieldOfValue(instance, sourceFieldKeyAndValueMap, instance.getClass());
return instance;
}
/**
* 拷贝一个对象集合的属性至另一个对象集合。支持两个对象之间不同属性名称进行拷贝,使用注解{@link ConvertField}
*
* @param source 源对象 使用注解{@link ConvertField#targetName()}
* @param targetClazz 目标对象 使用注解{@link ConvertField#sourceName()}
*/
public static <T> List<T> converts(@Nullable Collection<?> sourceList, Class<T> targetClazz) {
if (null != sourceList && !sourceList.isEmpty()) {
List<T> outList = Lists.newArrayList();
for (Object source : sourceList) {
T convert = convert(source, targetClazz);
if (!Objects.isNull(convert)) {
outList.add(convert);
}
}
return outList;
}
return CollUtil.newArrayList();
}
/**
* 生成需要被拷贝的属性字典 属性-属性值(递归取父类属性值)
*
* @param source 待拷贝的bean
* @param sourceFieldKeyAndValueMap 存放待拷贝的属性和属性值
* @param beanClass 待拷贝的class[可能是超类的class]
*/
private static void createSourceFieldAndValue(Object source, Map<String, Object> sourceFieldKeyAndValueMap, Class<?> beanClass) {
// 如果不存在超类,那么跳出循环
if (Objects.isNull(beanClass.getSuperclass())) {
return;
}
Field[] originFieldList = beanClass.getDeclaredFields();
for (Field field : originFieldList) {
try {
ConvertField annotation = field.getAnnotation(ConvertField.class);
// 获取属性上的注解。如果不存在,使用属性名;存在使用注解名
String targetName;
if (Objects.isNull(annotation)) {
targetName = field.getName();
} else {
targetName = annotation.targetName();
}
// 初始化当前属性的描述器,获取当前传入属性的(getter/setter)方法
PropertyDescriptor propertyDescriptor = new PropertyDescriptor(field.getName(), beanClass);
// 获取当前属性的get方法
Method method = propertyDescriptor.getReadMethod();
//设置值
Object value = method.invoke(source);
//设置值
sourceFieldKeyAndValueMap.put(targetName, value);
} catch (IntrospectionException e) {
log.warn("【源对象】异常:" + field.getName() + "不存在对应的get方法,无法参与拷贝!");
} catch (IllegalAccessException | InvocationTargetException e) {
log.warn("【源对象】异常:" + field.getName() + "的get方法执行失败!");
}
}
// 递归,遍历设置超类
createSourceFieldAndValue(source, sourceFieldKeyAndValueMap, beanClass.getSuperclass());
}
/**
* 设置目标对象属性
*
* @param propertyDescriptor 属性描述器,获取当前传入属性的(getter/setter)方法
* @param target 目标容器bean
* @param sourceFieldKeyAndValueMap 待拷贝的属性和属性值
* @param beanClass 待设置的class[可能是超类的class]
*/
private static void setTargetFieldOfValue(Object target, Map<String, Object> sourceFieldKeyAndValueMap, Class<?> beanClass) {
// 如果不存在超类,那么跳出循环
if (Objects.isNull(beanClass.getSuperclass())) {
return;
}
Field[] targetFieldList = beanClass.getDeclaredFields();
for (Field field : targetFieldList) {
try {
ConvertField annotation = field.getAnnotation(ConvertField.class);
// 获取属性上的注解。如果不存在,使用属性名;存在使用注解名
String originName;
if (Objects.isNull(annotation)) {
originName = field.getName();
} else {
originName = annotation.sourceName();
}
// 初始化当前属性的描述器,获取当前传入属性的(getter/setter)方法
PropertyDescriptor propertyDescriptor = new PropertyDescriptor(field.getName(), beanClass);
// 获取当前属性的set方法
Method method = propertyDescriptor.getWriteMethod();
method.invoke(target, sourceFieldKeyAndValueMap.get(originName));
} catch (IntrospectionException e) {
log.warn("【目标对象】异常:" + field.getName() + "不存在对应的set方法,无法参与拷贝!");
} catch (IllegalAccessException | InvocationTargetException e) {
log.warn("【目标对象】异常:" + field.getName() + "的set方法执行失败!");
}
}
// 递归,遍历设置超类
setTargetFieldOfValue(target, sourceFieldKeyAndValueMap, beanClass.getSuperclass());
}
}
需要被映射的用户实体类(需要返回的数据)
@Data
@ApiModel(value = "UserConvertVO对象", description = "用户对象")
public class UserConvertVO implements Serializable {
private static final long serialVersionUID = 1L;
@JsonSerialize(
using = ToStringSerializer.class
)
@ApiModelProperty("主键id")
@ConvertField(sourceName = "createUser")
private Long id;
@ApiModelProperty("岗位ID")
private Long postId;
@ApiModelProperty("组织ID")
private Long organizationId;
@ApiModelProperty("账号")
private String account;
@ConvertField(sourceName = "createUserName")
@ApiModelProperty("姓名")
private String name;
@ApiModelProperty("手机")
private String phone;
@ApiModelProperty("性别")
private Integer sex;
@ApiModelProperty("工号")
private String jobNumber;
}
映射实体类
@Data
public class InfoVO{
@ApiModelProperty("创建人姓名")
private String createUserName;
@ApiModelProperty("更新人姓名")
private String updateUserName;
@ApiModelProperty("创建人id")
private Long createUser;
@ApiModelProperty("更新人id")
private Long updateUser;
// ... 其他属性
}
目前需要映射的属性:
createUser --> id
updateUserName --> userName
经过测试,映射后的输出结果为:
[
{
"id": "5641616",
"postId": -1,
"organizationId": -1,
"account": "",
"name": "超级管理员",
"phone": "",
"sex": -1,
"jobNumber": ""
}
]