找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

2034

积分

0

好友

268

主题
发表于 昨天 01:23 | 查看: 10| 回复: 0

一个表示“连接”或“关注”的插头图标梗图

BeanUtils.copyProperties() 在 Java 开发中确实帮我们省去了大量属性赋值的重复代码,尽管它无法实现深拷贝,但在处理 PO、VO、DTO 等对象间的浅拷贝时已经足够好用。然而,在日常开发实践中,我们仍然会发现它存在一些不够便利的地方。

那么,它具体有哪些不足呢?

  1. 无法直接拷贝 List。这是最常见的痛点,业务中需要转换对象列表的场景比比皆是,这导致我们不得不编写大量重复的循环代码。

    for (S source : sources) {
        T target = new T();
        copyProperties(source, target);
        list.add(target);
    }
  2. 简单的查询转换也需要显式创建对象。即便是一个简单的根据ID查询并转换为VO的操作,代码也显得有些冗余。

    public Vo findById(Integer id){
     Vo vo = new Vo();
     Po po = dao.findById(id);
     copyProperties(po, vo);
     return vo;
    }
  3. 不支持函数式返回值。在 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 的轻量级封装思路,旨在提升日常开发中的编码体验。如果你有更复杂的需求,例如高性能映射或深度拷贝,可以进一步探索其他第三方库。技术交流与资源分享,欢迎访问 云栈社区




上一篇:Go服务协程数爆炸但CPU利用率低?深度解析GMP调度模型
下一篇:TypeScript基础知识精讲:类型注解、类型擦除与严格模式配置
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-1-14 15:41 , Processed in 0.260986 second(s), 40 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2025 云栈社区.

快速回复 返回顶部 返回列表