对BeanUtils.copyProperties二次封装,支持对null过滤以及自定义过滤逻辑的扩展

发表于:2022-05-03 23:26:13·阅读:120

对BeanUtils.copyProperties二次封装,支持对null过滤以及自定义过滤逻辑的扩展

语言都是相通的,好的设计模式都会相互复制。JavasScript的es6中出现的 箭头函数 以及 链式调用的函数(如map)等,和 Java8 之后出现的 Lambda 表达式 简直神似。Java 8 函数式接口 的出现(@FunctionalInterface), 减少了在代码里写匿名类实现的代码,更是像Javascript中的定义的属性方法。( 2014 年发布的 Java 8 ,现在已经 Java 17 了,我还一直停在 Java 8 阶段。Spring Boot 3 都已经弃用 8 ,升级到 17 了,看来是要找时间重新学习学习了。)

前言

好久没有去写 Java,但对 SpringBeanUtils.copyProperties这个方法印象深刻。很多年前,因为拷贝属性,不想将值为 null 的属性也拷贝进来(spring的util类中默认是null也会复制,但是实际场景中,比如编辑对象时,我们不会去覆盖传进来的值null的属性),故特地改写了一个对 null 的封装处理,最近几天写了点 java 代码,一直用到这方法,但是总觉得功能有限,故想着拓展下自定义处理函数暴露出来给调用方写自己的处理逻辑。

我们先看下spring 提供的原始方法功能

    public static void copyProperties(Object source, Object target) throws BeansException {
        copyProperties(source, target, (Class)null, (String[])null);
    }

    public static void copyProperties(Object source, Object target, Class<?> editable) throws BeansException {
        copyProperties(source, target, editable, (String[])null);
    }
    
    public static void copyProperties(Object source, Object target, String... ignoreProperties) throws BeansException {
        copyProperties(source, target, (Class)null, ignoreProperties);
    }

    // 私有方法不对外
    private static void copyProperties(Object source, Object target, @Nullable Class<?> editable, @Nullable String... ignoreProperties) throws BeansException {
        // ... 此处代码省略
    }

我本次封装是对 copyProperties(Object source, Object target, String... ignoreProperties) 中的ignoreProperties做自定处理扩展。

封装后的使用 Demo

下面的 BeanUtil 是我自己封装的类,调用示例如下(具体实现代码见最后一个小节的 实现代码)。

BeanUtil.copyProperties(user, userVO);
BeanUtil.copyProperties(user, userVO, "password");
BeanUtil.copyProperties(user, userVO, new String[]{"mobile","password"});
// 主要是新增 CopyPropertiesConfig,可以在代码中做很多自己的实现。结合Lambda 表达式可以实现更多的功能,比如做一些特殊字符替换复制等等。
BeanUtil.copyProperties(
                    user,
                    userVO,
                    new CopyPropertiesConfig()
                            // 这个同spring一致
                            .setIgnoreProperties(new String[]{"mobile","password"})
                            // 是否允许null值复制, false表示不允许,spring的util类中默认是null也会复制,但是实际场景中,比如编辑对象时,我们不会去覆盖传进来的值null的属性
                            .setAllowNull(false)
                            // 这里返回了source、target备用,可能不需要
                            .setCopyPropertiesFliter((propertyName, source,target)->{
                                // 这里面可以写自定义逻辑
                                if(propertyName.equals("password")){
                                    // 返回true表示需要过滤掉,不去做复制
                                    return true;
                                }
                                
                                if(propertyName.equals("age") && ((User) source).getName().equals("admin")){
                                    // 返回true表示需要过滤掉,不去做复制
                                    return true;
                                }
                                return false;
                            })
            );

具体实现

主要用到了以下几点:

  • 使用 @FunctionalInterface 实现函数式接口, 然后就可以使用 Lambda 表达式 来表示该接口的一个实现
  • 使用 lombok@Accessors(chain = true) 实现对象方法的链式调用
  • 其他就没啥了,主要是基于spring原来方法的一些封装。

实现代码

总共有3个文件:BeanUtil.javaCopyPropertiesConfig.javaCopyPropertiesFliter.java

BeanUtil.java

package com.tulies.blog.api.utils;

import com.tulies.blog.api.beans.base.CopyPropertiesConfig;
import org.springframework.beans.BeanWrapper;
import org.springframework.beans.BeanWrapperImpl;
import org.springframework.beans.BeansException;

import java.beans.PropertyDescriptor;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;

public class BeanUtil {
    public static void copyProperties(Object source, Object target) {
        copyProperties(source, target, new CopyPropertiesConfig());
    }
    public static void copyProperties(Object source, Object target, String... ignoreProperties) throws BeansException {
        copyProperties(source, target, new CopyPropertiesConfig().setIgnoreProperties(ignoreProperties));
    }
    public static void copyProperties(Object source, Object target, CopyPropertiesConfig copyPropertiesConfig) {
        String[] ignoreProperties = getIgnoreProperties(source, target, copyPropertiesConfig);
        org.springframework.beans.BeanUtils.copyProperties(source, target, ignoreProperties);
    }
    // 根据 CopyPropertiesConfig 获取需要忽略的属性
    private static String[] getIgnoreProperties(Object source, Object target, CopyPropertiesConfig config) {
        Set<String> ignoreSet =new HashSet<String>();
        final BeanWrapper src = new BeanWrapperImpl(source);
        PropertyDescriptor[] pds = src.getPropertyDescriptors();
        // 判断fliter是否为null,不为null,则去逐条判断
        for (PropertyDescriptor pd : pds) {
            if (!config.getAllowNull() && src.getPropertyValue(pd.getName()) == null) {
                // 字段不允许为空
                ignoreSet.add(pd.getName());
            }else if(config.getCopyPropertiesFliter()!=null && config.getCopyPropertiesFliter().fliter(pd.getName(),source,target)){
                // 走自定义过滤方法处理
                ignoreSet.add(pd.getName());
            }
        }
        if (config.getIgnoreProperties() != null) {
            ignoreSet.addAll(new HashSet<String>(Arrays.asList(config.getIgnoreProperties())));
        }
        String[] result = new String[ignoreSet.size()];
        return ignoreSet.toArray(result);
    }
}

CopyPropertiesConfig.java

package com.tulies.blog.api.beans.base;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;

/**
 * @Author: 王嘉炀
 * @Description:
 * @Date: Created in 1:18 2022/02/22
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
@Accessors(chain = true)
public class CopyPropertiesConfig {
    // 哪些字段强制不替换
    private String[] ignoreProperties = null;
    // 是否允许null替换,true表示允许,spring中默认的也是允许。实际场景,比如编辑实体,我们可能会不去替换null的值
    private Boolean allowNull = true;
    // 自定义过滤器,fliter方法返回true标识需要过滤
    private CopyPropertiesFliter copyPropertiesFliter;
}

CopyPropertiesFliter.java

package com.tulies.blog.api.beans.base;

/**
 * @Author: 王嘉炀
 * @Description:
 * @Date: Created in 15:39 2022/05/03
 */
@FunctionalInterface
public interface CopyPropertiesFliter {
    boolean fliter(String propertyName, Object source, Object target);
}
评论
文明评论,理性发言
⌘ + Enter
全部评论
暂无评论数据