本文是在介绍一个go的不同类型结构体的复制工具函数,能减少重复劳动,提高开发效率,让代码可读性强,减少出错几率
本篇包含背景的介绍,工具函数源码的展示,性能测试等部分
问题背景:
故事的起因是看到了业务代码里有些字段比较多的部分,比如获取订单详情大概涉及到大几十个字段的复制,会有几十行甚至上百行的x.a = y.a这样的代码
即问题的根源是dto和vo是不同类型的结构题,但是又有很多同类型同名的公共字段需要复制(因结构体类型不同,无法直接copy出一份,只能挨个字段手动赋值)
由此想到了java里这种问题可以用beanUtils.copy方法解决,可以自动复制同名同类型字段的值
beanUtil是利用反射实现的,既然go也有反射,那么理论上也可以实现,就在网上找了下,果然有人写过了,很短:
代码:
func SimpleCopyProperties(dst, src interface{}) (err error) {
// 防止意外panic
defer func() {
if e := recover(); e != nil {
err = errors.New(fmt.Sprintf("%v", e))
}
}()
dstType, dstValue := reflect.TypeOf(dst), reflect.ValueOf(dst)
srcType, srcValue := reflect.TypeOf(src), reflect.ValueOf(src)
// dst必须结构体指针类型
if dstType.Kind() != reflect.Ptr || dstType.Elem().Kind() != reflect.Struct {
return errors.New("dst type should be a struct pointer")
}
// src必须为结构体或者结构体指针,.Elem()类似于*ptr的操作返回指针指向的地址反射类型
if srcType.Kind() == reflect.Ptr {
srcType, srcValue = srcType.Elem(), srcValue.Elem()
}
if srcType.Kind() != reflect.Struct {
return errors.New("src type should be a struct or a struct pointer")
}
// 取具体内容
dstType, dstValue = dstType.Elem(), dstValue.Elem()
// 属性个数
propertyNums := dstType.NumField()
for i := 0; i < propertyNums; i++ {
// 属性
property := dstType.Field(i)
// 待填充属性值
propertyValue := srcValue.FieldByName(property.Name)
// 无效,说明src没有这个属性 || 属性同名但类型不同
if !propertyValue.IsValid() || property.Type != propertyValue.Type() {
continue
}
if dstValue.Field(i).CanSet() {
dstValue.Field(i).Set(propertyValue)
}
}
return nil
}
试了一下是可以的,但是有些问题需要注意:
参数传递时,第二个参数使用指针还是实例请自行斟酌,第一个参数必须是指针,涉及的字段必须是对外的,也就是大写开头,因为反射无法获取非对外字段
需要注意的是,该拷贝方法为浅拷贝,换句话说,如果说对象内嵌套有其他的引用类型如Slice,Map等,用此方法完成拷贝后,源对象中的引用类型属性内容发生了改变,该对象对应的属性中内容也会改变。
除此之外,关于go反射的性能问题,做了个简单的测试,测试代码放在demo项目里了
众所周知大部分语言的反射都有些性能问题,所以想测试一下性能怎么样,会不会使用后耗时明显变长
反射工具执行一次自动复制和直接字段赋值(即上边问题背景的方式赋值) 时间花费循环一万次取平均值
对于1个int的结构体两种操作的时间比是6倍:
反射平均时间256ns, 直接set平均时间41ns, 反射时间是直接set的6倍
7个int的结构提是10倍:
反射平均时间434ns, 直接set平均时间43ns, 反射时间是直接set的10倍
7个int的结构体是33倍
反射平均时间1.452µs, 直接set平均时间43ns, 反射时间是直接set的33倍
7个string的结构体是35倍(为了看一下不同的基本类型之间知否有差别)
反射平均时间1.468µs, 直接set平均时间40ns, 反射时间是直接set的35倍
16个基本类型:
反射平均时间3.479µs, 直接set平均时间42ns, 反射时间是直接set的82倍
28个基本类型字段:
反射平均时间7.353µs, 直接set平均时间56ns, 反射时间是直接set的129倍
33个基本类型字段测试结果:
反射平均时间9.071µs, 直接set平均时间56ns, 反射时间是直接set的160倍
对于几十个字段的结构体之间复制字段,耗时大概在几微秒到几十微秒(即千分之一毫秒到百分之一毫秒)的样子,复制字段的操作经常出现在model、dto、vo之间的复制,通常一个请求中的复制不会超过3次,一个结构体几十个字段已经很多了,虽然是线性增长但是总耗时其实是可以忽略的,这种场景下我觉得是值得一用的,可以提高开发效率,避免开发手动操作失误
工具函数的来源:https://github.com/fishyxin/simple-copy-properties