玩转Go反射
反射
反射常用于各种框架类当中,它可以做到十分优雅的帮我们读取值、设置值
Go语言当中感觉很多反射的工具类,比如Java中的hutool,并没有很好的支持
我总结下来反射可以分为两块,一块是TypeOf,一块是ValueOf.
TypeOf可以做到获取类型,名字,别名等,ValueOf用来获取值,设置值
由于我想去读取ini文件中的属性去设置到我的struct里面,我觉得反射处理的太过于麻烦,得一个个的去判断值是什么类型,再去把值设置进去,所以我选用了go-ini这个包,但是也不能无脑使用,所以取读一下其中的核心源码。
我是怎么使用它的
type MyInit struct {
UserName string `ini:"username"`
Password string `ini:"password"`
}
func main() {
config, err := ini.Load("myInit.ini") //读取出文件成一个ini.file对象
if err != nil { // 如果没出问题
log.Fatalln("Fail to read file: ", err)
}
fmt.Println(config.Section("myInit").Key("username").String()) // 试试功能
myInit := MyInit{} // 初始化一下接收的struct
v := reflect.ValueOf(&myInit) // 尝试获取一下反射值
v = v.Elem() // v是指针,把它变成值
t := v.Type() // 对应的typeOf
config.Section("myInit").MapTo(&myInit) // 重点使用,这里注意一定要传递&指针
for i := 0; i < v.NumField(); i++ {
field := v.Field(i)
fieldTag := t.Field(i).Tag.Get("ini")
config.Section("myInit").Key(fieldTag).Value()
fmt.Println(field, fieldTag)
}
fmt.Println(myInit)
}
源码部分
源码的逻辑,把这个config.Section("myInit")
传递进去的&myInit其实是接受一个空接口,所以要做类型判断
// MapTo maps section to given struct.
func (s *Section) MapTo(v interface{}) error {
return s.mapTo(v, false)
}
// mapTo maps a section to object v.
func (s *Section) mapTo(v interface{}, isStrict bool) error {
typ := reflect.TypeOf(v) // 获取type和value
val := reflect.ValueOf(v)
if typ.Kind() == reflect.Ptr { // 这里就是要注意,一定是要传进来指针,不然的话值传递反射根本没意义
typ = typ.Elem() // 获取真正的值
val = val.Elem() // 获取真正的值
} else {
return errors.New("not a pointer to a struct")
}
if typ.Kind() == reflect.Slice { // 如果是slice使用一个逻辑
newField, err := s.mapToSlice(s.name, val, isStrict)
if err != nil {
return err
}
// 这里是直接使用了valueof反射对象的set方法,直接set了一个value值,到时候接着看源码吧
val.Set(newField)
return nil
}
// 其他都使用同样的逻辑
return s.mapToField(val, isStrict, 0, s.name)
}
mapToField的逻辑
func (s *Section) mapToField(val reflect.Value, isStrict bool, sectionIndex int, sectionName string) error {
if val.Kind() == reflect.Ptr {
val = val.Elem()
}
typ := val.Type()
// 这里是循环处理结构体中的每一个field
for i := 0; i < typ.NumField(); i++ {
field := val.Field(i)
tpField := typ.Field(i)
// 处理别名,在结构体中都有设置`ini:xxx`
tag := tpField.Tag.Get("ini")
if tag == "-" {
continue
}
rawName, _, allowShadow, allowNonUnique, extends := parseTagOptions(tag)
fieldName := s.parseFieldName(tpField.Name, rawName)
if len(fieldName) == 0 || !field.CanSet() {
continue
}
// 一些特别的判断,是否是结构体,是否是结构体指针,是否为匿名指针
isStruct := tpField.Type.Kind() == reflect.Struct
isStructPtr := tpField.Type.Kind() == reflect.Ptr && tpField.Type.Elem().Kind() == reflect.Struct
isAnonymousPtr := tpField.Type.Kind() == reflect.Ptr && tpField.Anonymous
if isAnonymousPtr {
// 如果是匿名指针直接就设置进去,匿名指针的意思是它是嵌入进去的,不是本身自带
field.Set(reflect.New(tpField.Type.Elem()))
}
// 这里主要还是区分出是不是嵌入指针,或者是结构体,那就要单独去处理
if extends && (isAnonymousPtr || (isStruct && tpField.Anonymous)) {
if isStructPtr && field.IsNil() {
field.Set(reflect.New(tpField.Type.Elem()))
}
fieldSection := s
if rawName != "" {
sectionName = s.name + s.f.options.ChildSectionDelimiter + rawName
if secs, err := s.f.SectionsByName(sectionName); err == nil && sectionIndex < len(secs) {
fieldSection = secs[sectionIndex]
}
}
if err := fieldSection.mapToField(field, isStrict, sectionIndex, sectionName); err != nil {
return fmt.Errorf("map to field %q: %v", fieldName, err)
}
} else if isAnonymousPtr || isStruct || isStructPtr {
if secs, err := s.f.SectionsByName(fieldName); err == nil {
if len(secs) <= sectionIndex {
return fmt.Errorf("there are not enough sections (%d <= %d) for the field %q", len(secs), sectionIndex, fieldName)
}
// Only set the field to non-nil struct value if we have a section for it.
// Otherwise, we end up with a non-nil struct ptr even though there is no data.
if isStructPtr && field.IsNil() {
field.Set(reflect.New(tpField.Type.Elem()))
}
if err = secs[sectionIndex].mapToField(field, isStrict, sectionIndex, fieldName); err != nil {
return fmt.Errorf("map to field %q: %v", fieldName, err)
}
continue
}
}
// Map non-unique sections
if allowNonUnique && tpField.Type.Kind() == reflect.Slice {
newField, err := s.mapToSlice(fieldName, field, isStrict)
if err != nil {
return fmt.Errorf("map to slice %q: %v", fieldName, err)
}
field.Set(newField)
continue
}
if key, err := s.GetKey(fieldName); err == nil {
delim := parseDelim(tpField.Tag.Get("delim"))
if err = setWithProperType(tpField.Type, key, field, delim, allowShadow, isStrict); err != nil {
return fmt.Errorf("set field %q: %v", fieldName, err)
}
}
}
return nil
}