背景
入职了新公司,接触到了一些新技术,可能第一家公司是安防行业的吧,用的互联网技术可能有点偏,发现到了新公司有些东西之前没用过,有许多地方值得学习。大家做过互联网开发都知道 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