在我们后端开发的过程中,对于数据的存储和处理一般采用这样的一个套路:首先有一个Bean和数据库中的数据相对应,另外有多个对应的DTO和前端数据进行通信。这样可以保证数据库的字段保持稳定,和前端的数据通信格式可以灵活改变。
一般来说DTO中的字段是Bean中字段的一部分,以达到不同场景下隐藏一些敏感数据的目的。
举例说明:
1 | public class Comment { |
Comment
是我们评论对应的Bean,CommentDTO
是Comment
对应的DTO。可以看到,CommentDTO
中的字段是Comment
字段的子集。
这种情况下,将Bean中的数据复制到DTO中或者将DTO中的数据复制到Bean中是相对机械的操作,我们当然可以将这个操作写成固定的代码用以调用。
初始代码
1 | public class CopyDirect { |
如上所示,这个版本的属性复制功能是我参考了网上的代码写的。
基本逻辑很简单:
- 使用
Introspector
获取bean信息 - 遍历
dest
中的字段,在source
中寻找同名的字段,将source
中字段的值赋值给dest
中的字段
使用如上的代码是可以完成属性复制功能的。但是在实际的项目中,对接口进行压力测试总是不尽如人意、经过定位没想到居然是这样一个简单的属性复制的代码成为了性能的瓶颈。我们需要对其进行优化。
Spring中的属性复制
Spring中其实也提供了一个属性复制的方法:
1 | BeanUtils.copyProperties(Object source, Object target) |
为了说明其功能,简化一下Spring的copyProperties
代码:
1 | public class CopyCache { |
基本的逻辑并不复杂,主要的不同在于CachedIntrospectionResults
类。这是一个缓存类,其中有两个变量:classCache
用于缓存类信息,propertyDescriptorCache
用户字段与字段信息的缓存。
当缓存存在时,类的字段信息都是从缓存中获取的,这样就可以节省了从Bean中获取字段信息的时间。实践证明从Bean中获取字段信息的操作是十分耗时的。
性能对比
为了直观地说明缓存的作用,我们通过下面的测试代码来对比使用缓存和不使用缓存两种情况的耗时:
1 | public class PerformanceTest { |
启动300个线程,每个线程循环执行100次属性复制。执行结果如下:
1 | testDirect |
可以看到,使用缓存和不使用缓存两种情况的性能差距达到上百倍。不使用缓存的情况每次属性复制要将近1ms,如果每个接口属性复制的操作执行的次数比较多,耗时还是非常可观的。
总结
提到缓存,我们一般想到的是memcache
和redis
这类的缓存服务。但是进程内本地缓存的作用在某些情况下的作用也是非常巨大的,对于提升程序的性能有时候响应巨大。