DTO、PO、VO 相互转换的工具
这里我使用一个 Builder 将要赋值的属性映射添加进去,然后使用的时候直接传入对应的类对象即可
这个工具需要创建两个类,一个 FunctionMap
用于记录“被赋值的类”对应的“获取值的类”的方法映射
import java.util.HashMap;
import java.util.Map;
import java.util.function.BiConsumer;
import java.util.function.Function;
/**
* Function 映射方法
*
* 这个类的 Get(Function) 方法对应的 Set(BiConsumer)方法 的映射
*
* @param 设置属性的目标对象的类型
* @param 获取属性的来源对象的类型
* @author zhangxuetu
* @date 2022-11-24
* @since 1.8
*/
public class FunctionMap<T, S> {
/**
* 当前所属构建器
*/
private final PropertyMapBuilder<T> owner;
/**
* Get 方法对应要设置的 Set 方法的映射
*/
private final Map<Function, BiConsumer> functionMap = new HashMap<>();
/**
* 对 Get 数据进行处理为 Set 所需的数据的方法
*/
private final Map<Function, Function> handlerMap = new HashMap<>();
protected FunctionMap(PropertyMapBuilder<T> owner) {
this.owner = owner;
}
/**
* 添加方法映射
*
* @param consumer Set 方法
* @param function Get 方法
* @return 返回当前对象以便链式调用
*/
public <R> FunctionMap<T, S> add(BiConsumer<T, R> consumer, Function<S, R> function) {
functionMap.put(function, consumer);
return this;
}
/**
* 添加方法映射
*
* @param consumer Set 方法
* @param function Get 方法
* @param handler 对 Get 数据进行处理为 Set 所需的数据的方法
* @param 数据结果类型
* @param 数据来源类型
*/
public <R, SourceType> FunctionMap<T, S> add(BiConsumer<T, R> consumer, Function<S, SourceType> function, Function<SourceType, R> handler) {
functionMap.put(function, consumer);
handlerMap.put(function, handler);
return this;
}
/**
* 获取这个类的方法映射
*
* @param clazz 数据来源的类的类型
* @param 获取数据的来源对象的类型
*/
public <S> FunctionMap<T, S> from(Class<S> clazz) {
return owner.from(clazz);
}
/**
* 对属性进行赋值
*
* @param target 设置属性的目标对象
* @param source 获取属性的来源对象
*/
public void copy(T target, S source) {
BiConsumer<T, Object> consumer;
Function<S, Object> function;
Function<Object, Object> convert;
for (Map.Entry<Function, BiConsumer> entry : functionMap.entrySet()) {
function = entry.getKey();
consumer = entry.getValue();
if (handlerMap.containsKey(function)) {
// 这个方法存在有转换方法,则进行转换
convert = handlerMap.get(function);
consumer.accept(target, convert.apply(function.apply(source)));
} else {
// 要注意值类型的问题,比如如果是值为 null 的 Integer 类型向 int 类型赋值会报 NullPoint 异常
consumer.accept(target, function.apply(source));
}
}
}
}
第二个类用于构建类的所有的映射的类的方法
PropertyMapBuilder
import java.lang.reflect.InvocationTargetException;
import java.util.*;
import java.util.function.BiConsumer;
import java.util.function.Function;
/**
* 数据属性的映射构建器
*
* 创建一个构造器实例,添加每个类对应的赋值到对应的属性的方法。使用构造器的{@link FunctionMap#from(Class)}方法获取数据来源类的映射
* 对象,然后调用{@link FunctionMap#add(BiConsumer, Function)}方法添加映射的属性方法。
*
* 使用{@link PropertyMapBuilder}对象的{@link PropertyMapBuilder#copy(Object, Object...)}方法将数据赋值到目标对象上,
* 或使用{@link PropertyMapBuilder#create(Object...)}方法创建出来实例对象,将属性赋值到这个对象身上。
*
* 示例:
*
* {@code
* // 这个 builder 一般在一个 Config 类中配置好,然后使用 PropertyMapBuilder.to(UserDTO.class).create 方法或 PropertyMapBuilder.to(UserDTO.class).copy 方法进行使用
* final PropertyMapCopyBuilder builder = PropertyMapBuilder.to(UserDTO.class)
* .from(UserPO.class)
* .add(UserDTO::setId, UserPO::getId)
* .add(UserDTO::setUsername, UserPO::getUsername)
* .from(RolePO.class)
* .add(UserDTO::setRolename, RolePO::getName)
* .add(UserDTO::setPermission, RolePO::getPermission);
* final UserPO userPO = new UserPO(2, "zhangsan", "123456", "1567778889");
* final RolePO rolePO = new RolePO(1, "admin", "/user,/log");
* final UserDTO userDTO = builder.create(userPO, rolePO);
* System.out.println(userDTO);
* }
*
* 可以预先创建一个PropertyMapConfig
属性映射配置类,用以预先设置好要映射的类的属性
*
* @param 要赋值到的目标对象的类型
* @author zhangxuetu
* @date 2022-11-24
* @since 1.8
*/
public class PropertyMapBuilder<T> {
/**
* 项目中的类对应的{@link PropertyMapBuilder},这个 map 已经包含有这个 Class,则不重新创建新的{@link PropertyMapBuilder}
*/
private final static Map<Class, PropertyMapBuilder> BUILDER_MAP = new HashMap<>();
/**
* 转换到的类
*/
private final Class<T> clazz;
/**
* 不同的类的方法可以设置的值
*/
private final Map<Class, FunctionMap> classToFunctionMap = new HashMap<>();
private PropertyMapBuilder(Class<T> clazz) {
this.clazz = clazz;
}
/**
* 转换到这个类型。
*
* 实例化一个构建器单例,如果已经创建过这个类的 {@link PropertyMapBuilder},则直接返回创建过的 {@link PropertyMapBuilder}
*
* @param clazz 最终要转换赋值的类
* @param 这个类的类型
*/
public static <T> PropertyMapBuilder<T> to(Class<T> clazz) {
if (BUILDER_MAP.containsKey(clazz)) {
return BUILDER_MAP.get(clazz);
}
final PropertyMapBuilder<T> builder = new PropertyMapBuilder<>(clazz);
BUILDER_MAP.put(clazz, builder);
return builder;
}
/**
*
* 调用这个方法开始添加Set方法映射的Get方法的那个类
*
* @param clazz 类的类型
* @param 获取数据的对象的类型
*/
public <S> FunctionMap<T, S> from(Class<S> clazz) {
if (classToFunctionMap.containsKey(clazz)) {
return classToFunctionMap.get(clazz);
} else {
final FunctionMap<T, S> functionMap = new FunctionMap<>(this);
classToFunctionMap.put(clazz, functionMap);
return functionMap;
}
}
/**
* 赋值属性到这个对象上
*
* @param target 设置属性的目标对象
* @param getters 获取数据的对象
*/
public void copy(T target, Object... getters) {
for (Object getter : getters) {
if (getter != null) {
// 获取这个类的 FunctionMap 对象记录着映射的方法
final FunctionMap<T, Object> functionMap = classToFunctionMap.get(getter.getClass());
functionMap.copy(target, getter);
}
}
}
/**
* 根据这个几个对象创建出来目标类型的对象
*
* @param getters 获取数据的对象列表
*/
public T create(Object... getters) {
T target = null;
try {
target = clazz.getDeclaredConstructor().newInstance();
copy(target, getters);
} catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
e.printStackTrace();
}
return target;
}
}
通过上面对象我们可以利用他们做属性映射工具类
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONUtil;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.Date;
import java.util.List;
import java.util.function.Function;
import java.util.stream.Collectors;
/**
* 实体类转换器
*
* 通过{@link PropertyMapBuilder}映射Setter和Getter方法之后进行使用下面的方法转换创建对应类型的对象
*
* 例如转换为一个{@link UserVo}类型的对象:
*
*
* UserPO userPo = userMapper.getUserById(userId);
* RolePO rolePo = roleMapper.getRoleByUserId(userId);
* return EntityCreator.convertUserVo(userPo, rolePo)
*
*
* @author zhangxuetu
* @date 2022-12-07
* @since 1.8
*/
@Component
public class EntityConvertor {
static {
// AssetsPO + AssetsTypePO = AssetsVO
PropertyMapBuilder.to(AssetsVo.class)
.from(AssetsPo.class)
.add(AssetsVo::setId, AssetsPo::getId)
.add(AssetsVo::setAssetsId, AssetsPo::getAssetsId)
.add(AssetsVo::setAssetsName, AssetsPo::getAssetsName)
.add(AssetsVo::setAssetsModel, AssetsPo::getAssetsModel)
.add(AssetsVo::setAssetsDesc, AssetsPo::getAssetsDesc)
.add(AssetsVo::setTagCode, AssetsPo::getTagCode, (v) -> "".equals(v) ? null : v)
.add(AssetsVo::setAssetsId, AssetsPo::getAssetsId)
.from(AssetsTypePo.class)
.add(AssetsVo::setAssetsTypeId, AssetsTypePo::getAssetsTypeId)
.add(AssetsVo::setAssetsTypeName, AssetsTypePo::getAssetsTypeName);
// Dto 转 Po
PropertyMapBuilder.to(AssetsPo.class)
.from(AssetsDto.class)
.add(AssetsPo::setId, AssetsDto::getId)
.add(AssetsPo::setAssetsId, AssetsDto::getAssetsId)
.add(AssetsPo::setAssetsName, AssetsDto::getAssetsName)
.add(AssetsPo::setAssetsTypeId, AssetsDto::getAssetsTypeId)
.add(AssetsPo::setAssetsModel, AssetsDto::getAssetsModel)
.add(AssetsPo::setTagCode, AssetsDto::getTagCode, (v) -> "".equals(v) ? null : v)
.add(AssetsPo::setAssetsWorkRoomId, AssetsDto::getRoomId)
.add(AssetsPo::setAssetsDesc, AssetsDto::getAssetsDesc);
// AssetsTypePO => AssetsTypeVO
PropertyMapBuilder.to(AssetsTypeVo.class)
.from(AssetsTypePo.class)
.add(AssetsTypeVo::setAssetsTypeId, AssetsTypePo::getAssetsTypeId)
.add(AssetsTypeVo::setAssetsTypeName, AssetsTypePo::getAssetsTypeName)
.add(AssetsTypeVo::setAssetsTypeIndoorImage, AssetsTypePo::getAssetsTypeIndoorImage, FileConfig::getImageUrl)
.add(AssetsTypeVo::setAssetsTypeOutdoorImage, AssetsTypePo::getAssetsTypeOutdoorImage, FileConfig::getImageUrl)
.add(AssetsTypeVo::setAssetsTypeDesc, AssetsTypePo::getAssetsTypeDesc)
.add(AssetsTypeVo::setAssetsTypeCreateTime, AssetsTypePo::getAssetsTypeCreateTime);
// TagPO + AssetsPO = TagVO
PropertyMapBuilder.to(TagVo.class)
.from(TagPo.class)
.add(TagVo::setTagId, TagPo::getTagId)
.add(TagVo::setTagTypeId, TagPo::getTagTypeId)
.add(TagVo::setTagCode, TagPo::getTagCode, (v) -> "".equals(v) ? null : v)
.add(TagVo::setTagQrCode, TagPo::getTagQrCode)
.add(TagVo::setTagName, TagPo::getTagName)
.add(TagVo::setSignalFrequency, (tagPo) -> {
if (tagPo == null) {
return "";
}
String indoor = tagPo.getTagIndoorSignalFreq();
String outdoor = tagPo.getTagOutdoorSignalFreq();
if (indoor == null || outdoor == null) {
return "";
}
indoor = indoor.replace("/", "");
outdoor = outdoor.replace("/", "");
return indoor + "/" + outdoor;
})
.from(AssetsPo.class)
.add(TagVo::setAssetsIncrId, AssetsPo::getId)
.add(TagVo::setAssetsId, AssetsPo::getAssetsId)
.add(TagVo::setAssetsName, AssetsPo::getAssetsName);
}
/**
* 将这个列表项的类型转换成另一种类型
*
* @param list 数据列表
* @param convert 转换的引用的方法。一般都是使用当前类的其他方法配合使用,也可以对数据额外处理,例如返回 second 字段值的末尾追加一个 s
* 字符后的值:(v) -> v.getSecond() + "s"。
* @param 目标类型
* @param 转换成的类型
* @return 返回转换后的类型的列表
*/
public static <T, S> List<S> convertList(List<T> list, Function<T, S> convert) {
return list.stream().map(convert).collect(Collectors.toList());
}
/**
* 转为这个类型的对象
*
* @param tClass 转换到的对象类型
* @param froms 从这些来源对象中获取对应映射方法的数据
* @return 返回转换的类型
*/
public static <T> T to(Class<T> tClass, Object... froms) {
return PropertyMapBuilder.to(tClass).create(froms);
}
public static AssetsVo convertAssetsVo(AssetsPo assetsPo, AssetsTypePo assetsTypePo, String bindArea, String bindAreaId) {
AssetsVo assetsVo = to(AssetsVo.class, assetsPo, assetsTypePo);
assetsVo.setAssetsBindArea(bindArea);
assetsVo.setAssetsBindAreaId(bindAreaId);
return assetsVo;
}
public static AssetsPo convertAssetsPo(AssetsExcelData assetsExcelData, int assetsTypeId, String roomId) {
AssetsPo assetsPo = to(AssetsPo.class, assetsExcelData);
assetsPo.setAssetsWorkRoomId(roomId);
assetsPo.setAssetsTypeId(assetsTypeId);
assetsPo.setAssetsWorkStatus(0);
return assetsPo;
}
public static AssetsTypeVo convertAssetsTypeVo(AssetsTypePo assetsTypePo) {
return to(AssetsTypeVo.class, assetsTypePo);
}
public static TagVo convertTagVo(TagPo tagPo, AssetsPo assetsPo) {
TagVo tagVo = to(TagVo.class, tagPo, assetsPo);
tagVo.setTagIsBind(assetsPo != null ? 1 : 0);
return tagVo;
}
}
直接通过上面的 EntityConvertor
对象里的方法传入对应所需参数即可创建出转换后的对象