N项目的前身定位是业务中台项目,在进行分层和微服务模式下的开发时,经常会遇到DTO、DO等之间的转换,除此之外基于系统定位,我们需要对接多家不同公司,屏蔽掉不同业务方之间的差异化数据结构,包装为一套自有结构供合作部门及内部进行使用。
在这种情况下如何从最基础的数据转换出发,优化系统性能和提升开发效率呢?下面从两个基本场景出发,介绍下我的思考和改造过程:
同名字段下Bean拷贝的优化
项目开发过程中如无必要,最好减少对反射的使用,但是手动对属性get和set代码臃肿容易漏掉,也实在和优雅编程背道而驰。在这种场景下相信最常使用的就是BeanUtils.copyProperties来实现数据拷贝。主要从以下几点进行了优化:
1、避免使用Apache工具类的同名方法
commons-beanutils和spring-beans包中有两个同名的方法,除了source和target位置相反意外,两者的性能差异更是大过开飞机和步行。通过阅读源码可以发现Apache的工具类中做了非常多检查、校验以及兼容。通过属性拷贝测试可以清晰反应两者的效率差距:
在《阿里巴巴Java开发手册》中也明确提到了要避免使用Apache BeanUtils进行属性的拷贝。
2、引入高性能的反射框架
Spring BeanUtils毕竟还是通过反射实现的,下一步首先想到的就是引入高性能的反射框架提高反射的处理效率。ReflectASM是一个基于asm进一步开发的jar包,比仅仅使用缓存来提升Spring反射它的效率更高。简单来说它会利用反射构建一个新的代理类,并在其中重写原有类的get和set方法,后续属性拷贝过程直接调用新类的方法,而不涉及反射。也就是在使用过程中效率已经接近直接调用方法。这个和基于CGLib动态代理的BeanCopier比较类似。经过改造后拷贝效率对比如下:
结果基本符合预期,虽然相比Spring BeanCopier引入ReflectASM没有显著提升性能,但在开发过程中用起来会更加方便。
3、注意缓存
通过对ReflectASM原理的学习,它的性能提升主要是在用方法调用也即进行属性拷贝的阶段,在准备阶段依然需要使用反射构建新的类并进行加载,这个过程还是会消耗很多时间,所以要避免反复生成代理类,一次生成后注意缓存起来,可以进一步提升效率。
经过以上三个步骤,同名DTO间的数据转换基本改造完成,在更多的业务场景下需要处理合作保险公司的不同名数据结构的转换。
不同名字段数据转换工具的选型和优化
有不少数据映射框架可以提供这个功能:MapStruct、Dozer、ModelMapper、Orika、JMapper等等,在进行选型的时候主要从以下几个方面进行了对比和考量:
1、开源及社区活跃度
最简单的从GitHub上星星数量上可以大致判断出来这些项目目前的流行程度,也可以进一步观察版本更新情况判断社区活跃度。在这里推荐一个网站(https://java.libhunt.com),可以在两个或多个项目之间进行直观比较。在Java Bean Mapping分类下:
其实在这一步看出来不同工具的流行、活跃情况了。也可以进一步两两之间进行对比,更直观的看到趋势、代码质量等指标。
2、原理及性能
引入新的开源工具到项目中对其原理的理解十分必要,在后续使用中遇到问题可以更快的解决,首推官方文档,如果项目不大通过源码入手也是一个不错的选择。
以第一步中锁定前三名的工具简单说明:
MapStruct:基于JSR 269的Java注解处理器,在运行完成后会生成一个新的Mapper接口实现类,如下图所示它自动生成了对应的get、set方法。到这一步基本已经可以预期MapStructs会是这几个当中性能最好的。
Dozer:应该是这几个当中最为年长的工具了,提供了属性映射、复杂的类型映射、双向映射、递归映射等等丰富的功能,然而它本质上还是通过Java反射实现的,这些功能意味着它更大更重也更慢,性能会成为严重的瓶颈。
ModelMapper:依然是基于反射实现的,提供了类型转换安全的api,相比与性能似乎更加关注数据一致性。
关于这三个的性能,参考(https://www.baeldung.com/java-performance-mapping-frameworks)已经有了非常详细的对比和结论,以吞吐量为例:
3、易用度
最后一步对工具的易用性进行进行评估,在性能上MapStruct已经大幅领先,在使用过程中:
基于注解,可以很非常方便处理数据间的映射关系;
可以在注解的基础上扩展,在转换过程中自定义函数,基于此可以直接处理变量枚举值的差异;
支持双向转换,也即一次配置可以搞定ADTO -> BDTO,和BDTO -> ADTO两种转换。这在保险中台的业务场景下可以减少大量的重复代码;
在编译期间就生成了代码,所以如果有任何问题,编译期间就可以提前暴露。
总体来说接入和学习成本都比较低,可维护性也很好。到此基本完成了对项目的一点优化和重构工作,希望能抛砖引玉给大家带来一点帮助。