BeanUtils.copyProperties() 在 Java 开发中极大地简化了对象属性的拷贝工作。尽管它无法实现深拷贝,但对于 PO、VO、DTO 等对象间的浅层拷贝已经足够高效。然而,在实际应用场景中,它仍存在一些不尽如人意的地方。
其主要不足体现在以下几个方面:
①. 无法直接拷贝List集合
实际开发中,需要对整个列表进行数据转换的情况非常普遍,使用原生方法会导致大量重复的循环代码。
for (S source : sources) {
T target = new T();
copyProperties(source, target);
list.add(target);
}
②. 简单查询转换不够简洁
对于一些仅需转换对象类型的简单查询,代码中仍需要显式地实例化目标对象。
public Vo findById(Integer id) {
Vo vo = new Vo();
Po po = dao.findById(id);
copyProperties(po, vo);
return vo;
}
③. 对函数式编程支持不友好
原生的拷贝方法没有返回值,在 Java 8 引入 Stream 和 Lambda 表达式后,这种设计不利于进行流畅的链式调用。为此,我们决定通过扩展 BeanUtils 类,封装一个更便捷的工具类。
使用新的转换工具
我们将创建一个新的工具类 BeanConvertUtils。使用时,对象转换将变得非常简洁:
// 使用前
public Vo findById(Integer id) {
Vo vo = new Vo();
Po po = dao.findById(id);
copyProperties(po, vo);
return vo;
}
// 使用后
public Vo findById(Integer id) {
return BeanConvertUtils.converTo(dao.findById(id), Vo::new);
}
// 使用后,通过lambda表达式特殊处理个别字段
public Vo findById(Integer id) {
return BeanConvertUtils.converTo(dao.findById(id), Vo::new,
(s, t) -> t.setName(s.getName))
);
}
当需要拷贝整个列表时,操作同样简单:
// 使用前
public List<Vo> findAll() {
List<Vo> vos = new ArrayList();
List<Po> pos = dao.findAll();
for (Po po : Pos) {
Vo vo = new Vo();
BeanUtis.copyProperties(po, vo);
vos.add(vo);
}
return vos;
}
// 使用后
public List<Vo> findAll() {
return BeanConvertUtils.converToList(dao.findAll(), Vo::new)
}
// 同样支持自定义lambda处理
public List<Vo> findAll() {
return BeanConvertUtils.converToList(dao.findAll(), Vo::new,
(s, t) -> t.setName(s.getName))
)
}
核心实现代码如下:
/**
* 转换对象工具
*
* @author bugpool
*/
public class BeanConvertUtils extends BeanUtils {
public static <S, T> T convertTo(S source, Supplier<T> targetSupplier) {
return convertTo(source, targetSupplier, null);
}
/**
* 转换对象
*
* @param source 源对象
* @param targetSupplier 目标对象供应方
* @param callBack 回调方法
* @param <S> 源对象类型
* @param <T> 目标对象类型
* @return 目标对象
*/
public static <S, T> T convertTo(S source, Supplier<T> targetSupplier, ConvertCallBack<S, T> callBack) {
if (null == source || null == targetSupplier) {
return null;
}
T target = targetSupplier.get();
copyProperties(source, target);
if (callBack != null) {
callBack.callBack(source, target);
}
return target;
}
public static <S, T> List<T> convertListTo(List<S> sources, Supplier<T> targetSupplier) {
return convertListTo(sources, targetSupplier, null);
}
/**
* 转换对象
*
* @param sources 源对象list
* @param targetSupplier 目标对象供应方
* @param callBack 回调方法
* @param <S> 源对象类型
* @param <T> 目标对象类型
* @return 目标对象list
*/
public static <S, T> List<T> convertListTo(List<S> sources, Supplier<T> targetSupplier, ConvertCallBack<S, T> callBack) {
if (null == sources || null == targetSupplier) {
return null;
}
List<T> list = new ArrayList<>(sources.size());
for (S source : sources) {
T target = targetSupplier.get();
copyProperties(source, target);
if (callBack != null) {
callBack.callBack(source, target);
}
list.add(target);
}
return list;
}
/**
* 回调接口
*
* @param <S> 源对象类型
* @param <T> 目标对象类型
*/
@FunctionalInterface
public interface ConvertCallBack<S, T> {
void callBack(S t, T s);
}
}
性能考量
由于 BeanConvertUtils 本质上是对 BeanUtils 的一层封装,其性能与原方法几乎一致。理论上可能因为多了一层方法调用栈而有微小损耗,但这完全可以忽略不计。主要的性能开销依然取决于底层 BeanUtils.copyProperties() 的实现。
注意事项
这个工具类在项目中能显著提升编码效率,但有两点需要特别注意:
- 深拷贝问题:此方法依然无法解决嵌套对象的深拷贝问题,关于
BeanUtils的深拷贝限制需要另行了解。
- 空值处理策略:当源对象
source 或目标对象供应方 targetSupplier 任一为 null 时,本工具类不会抛出异常,而是直接返回 null。这基于一种设计理念:是否允许转换空值应由调用方决定,工具类仅提供转换服务。