BeanUtils 拷贝,封装让使用更简单

背景

入职了新公司,接触到了一些新技术,可能第一家公司是安防行业的吧,用的互联网技术可能有点偏,发现到了新公司有些东西之前没用过,有许多地方值得学习。大家做过互联网开发都知道 VO,DTO,DO 这三个玩意肯定有共同点,就是属性名称一样或者表达的意思一样,于是大家为了方便开发一些对象拷贝的工具,这些工具还是蛮多的,性能、稳定性、支持的功能更是各有所长,但都不是今天的focus

What is focus of today?

到了新公司熟悉代码的时候就发现从VO 拷贝到DTO ,然后从DTO 拷贝到DO,公司使用的Spring 的BeanUtils 进行拷贝的。如果一个DO列表复制到DTO 列表,需要for循环,我就看到很多的个for 循环,本着闲来无事的态度我觉得肯定能封装下减少代码量。当时我就写了第一版,通过泛型和反射封装了一层,参数直接传目标对象的类型(通过反射构建对象),以及源对象列表。但是大家都知道反射慢啊,newInstance 这个方法是比new 关键字慢,细节我先不说,至少newInstance 先去加载类信息。毕竟你要想让人家用你封装的工具类,你要没毛病啊,人家用new 我也得用或者更好的方式这是最起码的。

灵感的来源

和朋友讨论了一下,推荐我用另外一个框架mapstruct 之前没听说过,我本着学习的态度上网搜了下,觉得蛮好的,但是我觉得我还没有那个分量让人家使用,因为用这个项目跑得很好没啥问题,你用新技术还不太懂,有坑你知道吗?不知道。重构有风险,所以还是要在Spring的BeanUtils下功夫,本着匠心的精神,查了点资料,发现java 8 的Supplier 接口可以帮我解决这个问题,这个接口我在Java 多线程之Semaphore (限流Java 版)还用过,当时就是想不起来,这就是不熟,需要自我检讨,java 8 的一些语法和新特性还是需要熟练地去使用实战,至少会减少代码量,代码越少你的bug 越少,这是一定的。

第一版

/**
 * 封装BeanUtils
 *
 * @author liangziqiang
 * @date 2019.10.19
 */
public class BeanHelper {
    /**
     * 拷贝指定源列表 到 指定目标bean类型,并返回目标bean列表
     *
     * @param targetClazz 目标bean 类型
     * @param sourceList  源bean 列表
     * @param <T>         指目标bean类型
     * @param <D>         指代源bean类型
     * @return 返回指定目标bean类型的列表
     */
    public static <T, D> List<T>  copyForList(Class<T> targetClazz, List<D> sourceList) {
        if (Objects.isNull(sourceList) || Objects.isNull(targetClazz)) {
            return null;
        }
        return sourceList.stream().filter(Objects::nonNull).map(d -> {
            T t = null;
            try {
                //使用反射构建对象
                t = targetClazz.newInstance();
            } catch (InstantiationException | IllegalAccessException e) {
                e.printStackTrace();
            }
            if (Objects.nonNull(t)) {
                BeanUtils.copyProperties(d, t);
            }
            return t;
        }).filter(Objects::nonNull).collect(Collectors.toList());
    }
}

拷贝list 定义的是一个泛型方法,T 代表目标bean 的类型,D 代表 源bean 的类型,定义了泛型的好处大家都明白吧,就是不需要从Object 强制转换到某个具体类型,强制转换编译时不检查,代码执行时若转换失败直接抛出异常;如果你的每行代码都需要到运行时才知道正确与否——我觉得累人,python就是;泛型的最大优点就是编译时检查类型是否正确。多说一嘴,泛型这个东西算是Java 比较高级的用法(用好难啊)。
函数两个参数:targetClazz 代表目标bean 的类型,
       sourceList 代表源bean 的列表
实现上呢就比较中规中矩了。

Sencond Version ?

    /**
     * 拷贝指定源列表 到 指定目标bean类型,并返回目标bean列表
     *
     * @param targetSupplier 目标bean对象提供者
     * @param sourceList     源bean 列表
     * @param <T>            指目标bean类型
     * @param <D>            指代源bean类型
     * @return 返回指定目标bean类型的列表
     */
    public static <T, D> List<T> copyForList(Supplier<T> targetSupplier, List<D> sourceList) {
        if (Objects.isNull(sourceList) || Objects.isNull(targetSupplier)) {
            return null;
        }
        return sourceList.stream().filter(Objects::nonNull).map(d -> {
            T t = null;
            t = targetSupplier.get();
            if (Objects.nonNull(t)) {
                BeanUtils.copyProperties(d, t);
            }
            return t;
        }).filter(Objects::nonNull).collect(Collectors.toList());
    }

乍一看没啥变化是不是?往往优化就在一个很小的点上。第一个参数不是Class 类型了,有木有发现,它是一个函数式接口Supplier,你没有看错,你可以传进去一个函数get,然后调用这个get 方法就会执行方法体,如果方法体里写着return new XXXDto() 不就是使用new 了吗,就告别那令人头疼的反射了,如果觉得有点蒙,来看看几个例子

 private List<UserDo> list = new ArrayList<>();
    @BeforeEach
    public void init() {
        UserDo userDo1 = new UserDo();
        userDo1.setUsername("lzq");
        userDo1.setAddress("hube");
        userDo1.setPassword("fafafdafasfaasgagggagagagde");
        UserDo userDo2 = new UserDo();
        userDo2.setUsername("zym");
        userDo2.setAddress("fos");
        userDo2.setPassword("fafafdafasfaaggagagagde");
        list.add(userDo1);
        list.add(userDo2);
    }

    @Test
    public void copyForList() {
        List<UserDto> userDtos = BeanHelper.copyForList(UserDto.class, list);
        if (Objects.nonNull(userDtos)) {
            userDtos.forEach(System.out::println);
        }
    }

    @Test
    public void testCopyForList() {
        //传入UserDto::new 便可 这个是方法引用,
        //当实现一个函数式接口若返现只有一行即可使用但是分情况,在这里就不赘述了
        List<UserDto> userDtos = BeanHelper.copyForList(UserDto::new, list);
        if (Objects.nonNull(userDtos)) {
            userDtos.forEach(System.out::println);
        }
    }

是不是感觉到了java 8 的魅力,有时候语言特性这个东西可以省我们很多事。
然后接着java 8 的威力我又简洁了代码:

 /**
     * 拷贝指定源列表 到 指定目标bean类型,并返回目标bean列表
     * List<UserDto> userDtos = BeanHelper.copyForList(UserDto::new, userDos);
     * @param targetSupplier 目标bean对象提供者
     * @param sourceList     源bean 列表
     * @param <T>            指目标bean类型
     * @param <D>            指代源bean类型
     * @return 返回指定目标bean类型的列表
     */
    public static <T, D> List<T> copyForList(Supplier<T> targetSupplier, List<D> sourceList) {
        if (Objects.isNull(sourceList) || Objects.isNull(targetSupplier)) {
            return null;
        }
        return sourceList.stream().filter(Objects::nonNull).map(d ->
                BeanHelper.copyForBean(targetSupplier,d))
                .filter(Objects::nonNull).collect(Collectors.toList());
    }

    /**
     * 拷贝指定bean 到目标bean
     * 用法:
     * UserDto userDto=BeanHelper.copyForBean(UserDto::new, useDo);
     * @param targetSupplier
     * @param d
     * @param <T>
     * @param <D>
     * @return
     */
    public  static <T, D> T  copyForBean(Supplier<T> targetSupplier, D d) {
        if (Objects.isNull(targetSupplier) || Objects.isNull(d)) {
            return null;
        }
        T t = null;
        t = targetSupplier.get();
        if (Objects.nonNull(t)) {
            BeanUtils.copyProperties(d, t);
        }
        return t;
    }

后记

周六8点多跑完步回来,和家人聊会天,本来想睡觉,但是预测过完这周会很忙就补了篇博客,写到了凌晨1点,有点累,大家看完点个赞和关个注,谢谢。另外大家可以思考下看完本篇文章觉得学会了什么,可以反思一下。
源码地址
https://github.com/241600489/homeworks/blob/master/src/main/java/zym/spring/utils/BeanHelper.java

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,324评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,356评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,328评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,147评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,160评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,115评论 1 296
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,025评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,867评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,307评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,528评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,688评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,409评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,001评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,657评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,811评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,685评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,573评论 2 353

推荐阅读更多精彩内容