在上一篇文章中我们说到 golang在通过反射赋值时,要赋值的数据类型必须是一个指针, 且我们在对一个指针类型的数据在获取 reflect.Value对象时,要对其数据进行操作还必须要调用.Elem()方法获取指针对应的值后才能进行。详细参见golang中通过反射给对象赋值 reflect.Value,reflect.Type, 自定义异常 最佳实践-CSDN博客文章浏览阅读316次,点赞6次,收藏9次。go里面的自定义异常非常简单, 你直接自定义一个error的类型即可,见上面的示例。在go里面,error是一个内置的接口定义,他就定义了一个方法Error() string, 所以,在go语言里面,其实只要你的对象只要是实现了方法 Error() string 则你的这个对象就可以作为一个自定义的异常来使用。error接口定义参考reflect.Value实现了reflect.Type中定义的所有方法,是Type接口的具体实现。https://blog.csdn.net/tekin_cn/article/details/140455338
今天的这个异常 reflect.Set: value of type *xxx is not assignable to type xxx 他的底层原因和我们的上一篇文章密切相关,核心就是对.Elem()方法的理解和应用。
异常代码示例
要使用反射赋值的对象 res 已经是一个指针了,没有加 & 在使用反射赋值(v.Elem().Set(reflect.ValueOf(val)))的时候 就会抛出异常,原因就是要设置的值的类型和当前的对象类型不匹配。
func (s *SoftwareSvc) List(c *ginx.XContext) (*dto.DlSoftwareListRes, error) {
ckey := "softlist:xxx"
// 初始化返回对象
res := &dto.DlSoftwareListRes{}
// 从缓存中读取数据 注意这里的 res已经是一个指针了,下面res是要使用反射赋值的对象
if err := global.CacheGet(ckey, res); err != nil {
// 重新从数据库获取数据 ......
// 缓存数据 这里的res 就是一个指针类型 *dto.DlSoftwareListRes
if err := global.CacheSet(ckey, res, 0); err != nil {
global.Log.Errorf("写缓存失败! ckey:%s err: %v ", ckey, err.Error())
}
}
return res, nil
}
异常底层原因图解分析
res := &dto.DlSoftwareListRes{} 这里的res虽然是一个指针,满足了反射赋值的类型要求,但是我们要赋值的对象也是一个指针,这个res在反射Value对象后调用.Elem()方法后他的类型就不再是指针,而是指针指向的值,然后我们再将一个指针类型的数据赋给一个非指针类型,故抛Panic异常。
下面我们以gin-cache的 InMemoryStore里面的反射赋值为例图解说明,
上图显示了我们要赋值的对象value和我们将要赋值的值都是一个指针,且类型完全相同。
这里对要赋值的对象调用了.Elem()方法后,在调用Set进行赋值,此时的对象和要赋值的值类型就不一致了。 一个是值,一个是值的指针, 所以就panic异常了
解决方法:
对于要赋值的值是一个指针的, 我们在传递赋值变量的时候需要使用指针的指针, 即对要赋值的指针变量在加一个 & ,让这个变量变成指针的指针,这样在反射Value里面调用.Elem()方法后获取到的对象还是一个指针, 这时在将指针数据Set给指针就没有问题了。
对应上面的异常代码,只需要将 global.CacheGet(ckey, res) 修改为 global.CacheGet(ckey, &res) 即可。
图示
总结
golang中的反射赋值,搞明白.Elem()方法是关键, 出这个问题的原因就是类型不匹配,而根源是.Elem()方法获取了指针指向的值,这时当前的对象的值的类型就发生了改变,所以我们在反射里面调用xx.Elem().Set(reflect.ValueOf(xxx)) 对对象进行赋值时就需要对对象的值做相应的调整。
一句话就是:不管你要赋值的值是什么类型,我们在使用反射传递要赋值的对象的时候都必须要加上&