文章目录
- 前言
- 一、拷贝普通对象Bean
- 1.1 基础的Bean拷贝
- 1.2 支持忽略某些属性
- 1.3 支持忽略字段值为null的属性
- 1.4 通用的Bean拷贝
- 1.4.1 拷贝时可指定忽略属性
- 1.4.2 拷贝时外加忽略null属性
- 二、拷贝集合对象List
- 2.1 拷贝时可指定忽略属性
- 2.2 拷贝时外加忽略null属性
- 三、拷贝分页对象Page
- 四、整个CopyUtils类源码
- 五、应用到现有代码
- 六、测试+Git提交
- 最后
前言
为什么要实现拷贝工具类?
本文主要解决在VO、BO、PO之间对象转换时的代码冗余问题,扩展BeanUtils,提升我们使用BeanUtils.copyProperties的用户体验。
本文主要实现3种常见场景的拷贝:拷贝普通对象Bean、拷贝集合对象List、拷贝分页对象Page,内容不多但都是项目必备,并且涵盖了几个关键的基础知识:泛型方法、函数式接口、可变参数、重载!最关键的是有完整的拷贝工具类源码~ 还等什么,Let’s Go~
一、拷贝普通对象Bean
例如,我们将PO对象(student)转换成BO对象(studentBO),前面的代码是这样写的:
StudentBO studentBO = new StudentBO();
BeanUtils.copyProperties(student, studentBO);
需要先new出来BO,再通过BeanUtils.copyProperties将PO属性拷贝到BO,
是不是有点繁琐?能不能一步到位?
当然!!!
1.1 基础的Bean拷贝
只需要定义一个泛型方法,稍微封装即可!
public static <S, T> T copy(S source, Supplier<T> target) {
if (source == null) {
return null;
}
T t = target.get();
copyProperties(source, t);
return t;
}
这里顺便提一下泛型方法 基本语法 :
public <类型参数> 返回类型 方法名(类型参数 变量名) {
...
}
对应copy方法定义一一解读:
<S,T>
是类型参数,这里是用到两个泛型参数,多个以逗号分隔:S代表源对象类型,T代表目标对象类型- 之后的
T
是返回类型,即最后return 的对象类型必须是T类型,这里是通过Supplier<T>
由调用方提供返回对象 S source
是定义参数source,它的类型是S类型
那么最终调用就变成了:
StudentBO studentBO = CopyUtils.copy(student, StudentBO::new);
一行代码搞定,并且通用!!!
1.2 支持忽略某些属性
在某些场景下,我们在拷贝的时侯会忽略某些属性,刚好这也是BeanUtils.copyProperties
支持的重载,所以,我们也需要只增加一个可变参数:ignoreProperties
,就完成了重载增强:
public static <S, T> T copy(S source, Supplier<T> target, String... ignoreProperties) {
if (source == null) {
return null;
}
T t = target.get();
copyProperties(source, t, ignoreProperties);
return t;
}
…是可变参数,像上面这样定义,可以传入任意个String参数,或一个String[]数组,也可以不传入参数!
例如,在拷贝时我想忽略verifyTime
属性,就可以这样调用:
StudentBO studentBO = CopyUtils.copy(student, StudentBO::new, "verifyTime");
同样,如果不需要忽略,依然可以像下面这样调用,非常的方便!
StudentBO studentBO = CopyUtils.copy(student, StudentBO::new);
1.3 支持忽略字段值为null的属性
在某些场景下,我们在拷贝的时侯也会忽略字段值为null的属性,这里有了【1.2】的支持,这里实现了获取null属性值的方法,如下:
/**
* 获取bean中字段值=null的字段
*/
private static String[] getNullPropertyNames(Object source, String... ignoreProperties) {
final BeanWrapper src = new BeanWrapperImpl(source);
java.beans.PropertyDescriptor[] pds = src.getPropertyDescriptors();
Set<String> ignoreList = new HashSet<String>();
for (java.beans.PropertyDescriptor pd : pds) {
Object srcValue = src.getPropertyValue(pd.getName());
if (srcValue == null) {
ignoreList.add(pd.getName());
}
}
if (ignoreProperties != null) {
ignoreList.addAll(Arrays.asList(ignoreProperties));
}
String[] result = new String[ignoreList.size()];
return ignoreList.toArray(result);
}
然后,基于此,就可以实现copyPropertiesIgnoreNull
忽略null字段
/**
* 拷贝属性, 支持忽略属性,并自动忽略null字段
*/
public static void copyPropertiesIgnoreNull(Object src, Object target, String... ignoreProperties) {
BeanUtils.copyProperties(src, target, getNullPropertyNames(src, ignoreProperties));
}
1.4 通用的Bean拷贝
最终,就在内部实现了通用的Bean拷贝~
/**
* bean拷贝, 支持忽略属性 + 忽略null字段
*/
private static <S, T> T copyInternal(S source, Supplier<T> target, boolean isIgnoreNull, String... ignoreProperties) {
if (source == null) {
return null;
}
T t = target.get();
if (isIgnoreNull) {
copyPropertiesIgnoreNull(source, t, ignoreProperties);
} else {
copyProperties(source, t, ignoreProperties);
}
return t;
}
根据是否忽略null字段,我这里对外开放了两个重载方法:
1.4.1 拷贝时可指定忽略属性
/**
* bean拷贝, 支持忽略属性
*/
public static <S, T> T copy(S source, Supplier<T> target, String... ignoreProperties) {
return copyInternal(source, target, false, ignoreProperties);
}
1.4.2 拷贝时外加忽略null属性
/**
* bean拷贝, 支持忽略属性, 并自动忽略null字段
*/
public static <S, T> T copyIgnoreNull(S source, Supplier<T> target, String... ignoreProperties) {
return copyInternal(source, target, true, ignoreProperties);
}
二、拷贝集合对象List
有了拷贝普通对象Bean的基础,拷贝List实际就是循环拷贝!
所以,看到这里,你要不要自己先试一下?
OK,希望咱们想到一块了~
同样封装了一个通用List拷贝,参数与copyInternal
同样4个参数,只是将source修改为List<S>
类型,返回类型修改为List<T>
类型,然后循环~
/**
* bean list拷贝, 支持忽略属性 + 忽略null字段
*/
private static <S, T> List<T> copyListInternal(List<S> sourceList, Supplier<T> target, boolean isIgnoreNull, String... ignoreProperties) {
if (sourceList == null) {
return null;
}
List<T> targetList = new ArrayList<>();
for (S s : sourceList) {
targetList.add(copyInternal(s, target, isIgnoreNull, ignoreProperties));
}
return targetList;
}
同样根据是否忽略null字段,我这里对外开放了两个重载方法:
2.1 拷贝时可指定忽略属性
/**
* bean list拷贝, 支持忽略属性
*/
public static <S, T> List<T> copyList(List<S> sourceList, Supplier<T> target, String... ignoreProperties) {
return copyListInternal(sourceList, target, false, ignoreProperties);
}
2.2 拷贝时外加忽略null属性
/**
* bean list拷贝, 支持忽略属性, 并自动忽略null字段
*/
public static <S, T> List<T> copyListIgnoreNull(List<S> sourceList, Supplier<T> target, String... ignoreProperties) {
return copyListInternal(sourceList, target, true, ignoreProperties);
}
三、拷贝分页对象Page
Page对象稍微特殊一点,它本身继承了
ArrayList
所以,你知道怎么实现吗?
OK,我是这样实现的:
/**
* bean page拷贝, 支持忽略属性
*/
public static <S, T> Page<T> copyPage(Page<S> sourcePage, Supplier<T> target, String... ignoreProperties) {
Page<T> targetPage = copy(sourcePage, Page::new);
for (S s : sourcePage.getResult()) {
targetPage.add(copy(s, target, ignoreProperties));
}
return targetPage;
}
四、整个CopyUtils类源码
虽然在上面已经贴了全部源码,但还是给出完整源码!
package org.tg.book.common.utils;
import com.github.pagehelper.Page;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.BeanWrapper;
import org.springframework.beans.BeanWrapperImpl;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.function.Supplier;
public class CopyUtils extends BeanUtils {
/**
* bean page拷贝, 支持忽略属性
*/
public static <S, T> Page<T> copyPage(Page<S> sourcePage, Supplier<T> target, String... ignoreProperties) {
Page<T> targetPage = copy(sourcePage, Page::new);
for (S s : sourcePage.getResult()) {
targetPage.add(copy(s, target, ignoreProperties));
}
return targetPage;
}
/**
* bean list拷贝, 支持忽略属性
*/
public static <S, T> List<T> copyList(List<S> sourceList, Supplier<T> target, String... ignoreProperties) {
return copyListInternal(sourceList, target, false, ignoreProperties);
}
/**
* bean list拷贝, 支持忽略属性, 并自动忽略null字段
*/
public static <S, T> List<T> copyListIgnoreNull(List<S> sourceList, Supplier<T> target, String... ignoreProperties) {
return copyListInternal(sourceList, target, true, ignoreProperties);
}
/**
* bean拷贝, 支持忽略属性
*/
public static <S, T> T copy(S source, Supplier<T> target, String... ignoreProperties) {
return copyInternal(source, target, false, ignoreProperties);
}
/**
* bean拷贝, 支持忽略属性, 并自动忽略null字段
*/
public static <S, T> T copyIgnoreNull(S source, Supplier<T> target, String... ignoreProperties) {
return copyInternal(source, target, true, ignoreProperties);
}
/**
* bean list拷贝, 支持忽略属性 + 忽略null字段
*/
private static <S, T> List<T> copyListInternal(List<S> sourceList, Supplier<T> target, boolean isIgnoreNull, String... ignoreProperties) {
if (sourceList == null) {
return null;
}
List<T> targetList = new ArrayList<>();
for (S s : sourceList) {
targetList.add(copyInternal(s, target, isIgnoreNull, ignoreProperties));
}
return targetList;
}
/**
* bean拷贝, 支持忽略属性 + 忽略null字段
*/
private static <S, T> T copyInternal(S source, Supplier<T> target, boolean isIgnoreNull, String... ignoreProperties) {
if (source == null) {
return null;
}
T t = target.get();
if (isIgnoreNull) {
copyPropertiesIgnoreNull(source, t, ignoreProperties);
} else {
copyProperties(source, t, ignoreProperties);
}
return t;
}
/**
* 拷贝属性, 支持忽略属性,并自动忽略null字段
*/
public static void copyPropertiesIgnoreNull(Object src, Object target, String... ignoreProperties) {
BeanUtils.copyProperties(src, target, getNullPropertyNames(src, ignoreProperties));
}
/**
* 获取bean中字段值=null的字段
*/
private static String[] getNullPropertyNames(Object source, String... ignoreProperties) {
final BeanWrapper src = new BeanWrapperImpl(source);
java.beans.PropertyDescriptor[] pds = src.getPropertyDescriptors();
Set<String> ignoreList = new HashSet<String>();
for (java.beans.PropertyDescriptor pd : pds) {
Object srcValue = src.getPropertyValue(pd.getName());
if (srcValue == null) {
ignoreList.add(pd.getName());
}
}
if (ignoreProperties != null) {
ignoreList.addAll(Arrays.asList(ignoreProperties));
}
String[] result = new String[ignoreList.size()];
return ignoreList.toArray(result);
}
}
五、应用到现有代码
这里主要是搜索
BeanUtils.copyProperties
,然后将对象代码,换成CopyUtils
中的相关实现!
例如,普通的拷贝就是将上面的3行替换成1行:
return CopyUtils.copy(this, BookBO::new);
再比如还用了拷贝Page,原来是这样写的:
有了工具类就简化成这样了(实际也是一行代码):
如果没有下面的业务逻辑,只是拷贝绝对是一行代码,看一个明显的:
这里我就不一一展示了~
六、测试+Git提交
测试就留给大家了,做好自测有助于保证质量!
另外,养成好习惯,Git一步一提交!
最后
想要看更多实战好文章,还是给大家推荐我的实战专栏–>《基于SpringBoot+SpringCloud+Vue前后端分离项目实战》,由我和 前端狗哥 合力打造的一款专栏,可以让你从0到1快速拥有企业级规范的项目实战经验!
具体的优势、规划、技术选型都可以在《开篇》试读!
订阅专栏后可以添加我的微信,我会为每一位用户进行针对性指导!
另外,别忘了关注我:天罡gg ,发布新文不容易错过: https://blog.csdn.net/scm_2008