
BeanUtils.copyProperties() 在 Java 开发中确实帮我们省去了大量属性赋值的重复代码,尽管它无法实现深拷贝,但在处理 PO、VO、DTO 等对象间的浅拷贝时已经足够好用。然而,在日常开发实践中,我们仍然会发现它存在一些不够便利的地方。
那么,它具体有哪些不足呢?
-
无法直接拷贝 List。这是最常见的痛点,业务中需要转换对象列表的场景比比皆是,这导致我们不得不编写大量重复的循环代码。
for (S source : sources) {
T target = new T();
copyProperties(source, target);
list.add(target);
}
-
简单的查询转换也需要显式创建对象。即便是一个简单的根据ID查询并转换为VO的操作,代码也显得有些冗余。
public Vo findById(Integer id){
Vo vo = new Vo();
Po po = dao.findById(id);
copyProperties(po, vo);
return vo;
}
-
不支持函数式返回值。在 JDK8 引入 Stream API 和 Lambda 表达式后,这种需要先创建对象再作为参数传入的用法,与函数式编程的风格显得有些格格不入,不够友好。
因此,我们决定基于 BeanUtils 进行一层封装,打造一个更符合现代 Java 开发习惯的工具类。
1 使用方式
我们将创建一个新的工具类 BeanConvertUtils。改造之后,当我们需要进行 PO 到 VO 的单对象转换时,代码可以简化如下:
// 使用前
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))
)
}
工具类完整代码
/**
* 转换对象工具
*
*/
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);
}
}
2 性能考量
这个工具类本质上只是对 BeanUtils 的一层薄封装,因此其性能几乎与原生的 BeanUtils.copyProperties() 一致。如果非要深究,那无非是多了一次方法调用带来的极微小开销,这在绝大多数业务场景下完全可以忽略不计。工具的性能瓶颈依然取决于底层 BeanUtils 的实现。
3 注意事项
这个 BeanConvertUtils 工具类在笔者的项目中得到了广泛使用,确实带来了不少便利。不过,有两点需要特别提醒开发者注意:
- 深拷贝问题仍未解决:和
BeanUtils 一样,这个工具无法处理对象内部嵌套引用类型的深度拷贝。如果需要深拷贝,需要寻找其他方案或自行处理。
- 空值处理策略不同:当源对象 (
source) 或目标对象供应方 (targetSupplier) 为 null 时,本工具类不会像 BeanUtils 那样抛出异常,而是直接返回 null。这是因为笔者认为,调用方传入 null 本身可能就期望得到 null 结果,参数的有效性理应由调用方自己保证。
本文介绍的是一种基于 BeanUtils 的轻量级封装思路,旨在提升日常开发中的编码体验。如果你有更复杂的需求,例如高性能映射或深度拷贝,可以进一步探索其他第三方库。技术交流与资源分享,欢迎访问 云栈社区。