对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
,但对 Spring
的 BeanUtils.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.java
、CopyPropertiesConfig.java
、CopyPropertiesFliter.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);
}