共计 6700 个字符,预计需要花费 17 分钟才能阅读完成。
这个代码库次要用于加载和解析配置文件,反对 JSON、TOML 和 YAML 格局。次要性能包含从文件或字节数据中加载配置、填充默认值以及解决配置数据的键大小写。代码的次要构造和函数如下:
- fieldInfo 构造体:用于示意字段信息,包含子字段和映射字段。
- 从文件或字节数据加载配置的函数:Load, LoadConfig, LoadFromJsonBytes, LoadConfigFromJsonBytes, LoadFromTomlBytes, LoadFromYamlBytes, LoadConfigFromYamlBytes 和 MustLoad。
- 构建和解决字段信息的函数:buildFieldsInfo, buildNamedFieldInfo, buildAnonymousFieldInfo, buildStructFieldsInfo, addOrMergeFields 和 mergeFields。
- 解决字符串、映射和数组数据的辅助函数:toLowerCase, toLowerCaseInterface, toLowerCaseKeyMap,以及示意键反复谬误的自定义类型 dupKeyError 和相干函数。
整个库的性能是通过反射和递归地解决构造体字段信息来实现的。在加载配置时,首先将 TOML 和 YAML 格局的数据转换为 JSON 格局,而后对立解决 JSON 数据。配置数据加载后,库会确保数据的键与构造体字段的名称匹配,以便将数据正确地填充到构造体中。
开始
起因是在浏览疾速开始 go-zero 服务时,主函数调用了这两个包,为了不便了解主函数和 go-zero 框架,同时也为了学习优质源码,进步代码能力,疾速浏览了这两个包的内容。
go-zero-demo
https://go-zero.dev/cn/docs/quick-start/monolithic-service
其中主函数如下
package main
import (
"flag"
"fmt"
"go-zero-demo/greet/internal/config"
"go-zero-demo/greet/internal/handler"
"go-zero-demo/greet/internal/svc"
"github.com/zeromicro/go-zero/core/conf"
"github.com/zeromicro/go-zero/rest"
)
var configFile = flag.String("f", "etc/greet-api.yaml", "the config file")
func main() {flag.Parse()
var c config.Config
conf.MustLoad(*configFile, &c)
ctx := svc.NewServiceContext(c)
server := rest.MustNewServer(c.RestConf)
defer server.Stop()
handler.RegisterHandlers(server, ctx)
fmt.Printf("Starting server at %s:%d...\n", c.Host, c.Port)
server.Start()}
其中
在主函数中,咱们见到了这个语句
var configFile = flag.String("f", "etc/greet-api.yaml", "the config file")
var c config.Config
conf.MustLoad(*configFile, &c)
它调用了 core/conf 包对外的接口,对默认的配置文件进行了剖析
core/conf 包
调用
// MustLoad loads config into v from path, exits on error.
func MustLoad(path string, v any, opts ...Option) {if err := Load(path, v, opts...); err != nil {log.Fatalf("error: config file %s, %s", path, err.Error())
}
}
执行了 Load,接下来由 Load 函数开始性能实现
- Load 函数用于依据指定的配置文件门路加载配置数据。它首先依据文件扩展名调用 loaders 中相应的加载函数读取文件内容,而后调用 Unmarshal 办法解析文件内容并将其映射到提供的构造体实例中。
- loaders 变量定义了一个映射,它蕴含了不同文件扩展名(如 .json, .toml, .yaml, .yml)和相应的加载函数。这些加载函数负责从不同格局的配置文件中读取数据并解析为字节序列。
- FillDefault 函数用于为提供的构造体实例填充默认值。它应用全局变量 fillDefaultUnmarshaler 调用 Unmarshal 办法来实现这一性能。
- Unmarshaler 构造体及其相干办法。Unmarshaler 构造体负责解析从配置文件中读取到的数据,将其解析为对应的 Go 构造体。Unmarshal 办法是实现这一性能的要害,它依据输出数据的类型(如 map、slice 等)调用相应的解析函数。
var (fillDefaultUnmarshaler = mapping.NewUnmarshaler(jsonTagKey, mapping.WithDefault())
loaders = map[string]func([]byte, any) error{
".json": LoadFromJsonBytes,
".toml": LoadFromTomlBytes,
".yaml": LoadFromYamlBytes,
".yml": LoadFromYamlBytes,
}
)
// children and mapField should not be both filled.
// named fields and map cannot be bound to the same field name.
type fieldInfo struct {children map[string]*fieldInfo
mapField *fieldInfo
}
// FillDefault fills the default values for the given v,
// and the premise is that the value of v must be guaranteed to be empty.
func FillDefault(v any) error {return fillDefaultUnmarshaler.Unmarshal(map[string]any{}, v)
}
// Load loads config into v from file, .json, .yaml and .yml are acceptable.
func Load(file string, v any, opts ...Option) error {content, err := os.ReadFile(file)
if err != nil {return err}
loader, ok := loaders[strings.ToLower(path.Ext(file))]
if !ok {return fmt.Errorf("unrecognized file type: %s", file)
}
var opt options
for _, o := range opts {o(&opt)
}
if opt.env {return loader([]byte(os.ExpandEnv(string(content))), v)
}
return loader(content, v)
}
负责解析的函数 Unmarshal
其中负责解析的函数 Unmarshal 位于 core/mapping/unmarshaler.go 中,因为这个库文件有一千多行,在此不过多理解,只理解这个次要的函数的实现
具体代码如下
// Unmarshal unmarshals m into v.
func (u *Unmarshaler) Unmarshal(i any, v any) error {valueType := reflect.TypeOf(v)
if valueType.Kind() != reflect.Ptr {return errValueNotSettable}
elemType := Deref(valueType)
switch iv := i.(type) {case map[string]any:
if elemType.Kind() != reflect.Struct {return errTypeMismatch}
return u.UnmarshalValuer(mapValuer(iv), v)
case []any:
if elemType.Kind() != reflect.Slice {return errTypeMismatch}
return u.fillSlice(elemType, reflect.ValueOf(v).Elem(), iv)
default:
return errUnsupportedType
}
}
它将参数 i(一个 any 类型,即任意类型)解析并赋值到参数 v(也是一个 any 类型)。此办法次要用于解析配置文件并将其内容填充到给定的构造体中。
函数首先查看 v 是否为指针类型,因为只有指针类型能力进行赋值。接下来,依据 i 的类型(map[string]any 或 []any),执行不同的操作:
- 如果 i 是一个 map[string]any 类型,函数首先查看 elemType 是否为构造体类型。如果不是,返回 errTypeMismatch 谬误。否则,应用 mapValuer 函数将 i 转换为 mapValuer 类型,而后调用 UnmarshalValuer 办法。
- 如果 i 是一个 []any 类型,函数首先查看 elemType 是否为切片类型。如果不是,返回 errTypeMismatch 谬误。否则,调用 fillSlice 办法将 i 的内容填充到 v 对应的切片中。
- 如果 i 既不是 map[string]any 类型,也不是 []any 类型,函数返回 errUnsupportedType 谬误,示意不反对的类型。
通过 Unmarshal 办法,能够不便地将配置文件内容解析并填充到指定的构造体中。
对于两种状况,别离调用了两个办法
fillSlice 办法
func (u *Unmarshaler) fillSlice(fieldType reflect.Type, value reflect.Value, mapValue any) error {if !value.CanSet() {return errValueNotSettable}
baseType := fieldType.Elem()
dereffedBaseType := Deref(baseType)
dereffedBaseKind := dereffedBaseType.Kind()
refValue := reflect.ValueOf(mapValue)
if refValue.IsNil() {return nil}
conv := reflect.MakeSlice(reflect.SliceOf(baseType), refValue.Len(), refValue.Cap())
if refValue.Len() == 0 {value.Set(conv)
return nil
}
var valid bool
for i := 0; i < refValue.Len(); i++ {ithValue := refValue.Index(i).Interface()
if ithValue == nil {continue}
valid = true
switch dereffedBaseKind {
case reflect.Struct:
target := reflect.New(dereffedBaseType)
if err := u.Unmarshal(ithValue.(map[string]any), target.Interface()); err != nil {return err}
SetValue(fieldType.Elem(), conv.Index(i), target.Elem())
case reflect.Slice:
if err := u.fillSlice(dereffedBaseType, conv.Index(i), ithValue); err != nil {return err}
default:
if err := u.fillSliceValue(conv, i, dereffedBaseKind, ithValue); err != nil {return err}
}
}
if valid {value.Set(conv)
}
return nil
}
fillSlice 办法用于将解析后的配置数据填充到一个切片(slice)类型的字段中。
该办法执行以下操作:
- 首先,查看 value 是否可设置(可赋值),如果不可设置则返回谬误。
- 获取切片的元素类型 baseType,同时获取去除指针层级后的根本类型 dereffedBaseType 和根本类型的 Kind。
- 依据输出的 mapValue 创立一个新的切片 conv。
- 如果输出切片的长度为 0,则设置 value 为新创建的空切片并返回。
-
遍历输出的 mapValue,针对每个元素,依据 dereffedBaseKind 的类型执行相应的操作:
如果是构造体类型,应用 u.Unmarshal() 办法将数据解析到一个新的构造体实例中,而后设置新实例的值到指标切片的对应地位。如果是切片类型,递归调用 fillSlice() 办法解决嵌套的切片。其余状况下,应用 fillSliceValue() 办法填充切片元素的值。如果存在无效元素,将指标切片 conv 设置为 value。
- 返回 nil,示意胜利执行。
UnmarshalValuer 办法
,它承受一个 Valuer 类型的参数 m,将解析后的配置数据填充到指标构造体实例 v 中,提供了从数据源获取值的办法。
// UnmarshalValuer unmarshals m into v.
func (u *Unmarshaler) UnmarshalValuer(m Valuer, v any) error {return u.unmarshalWithFullName(simpleValuer{current: m}, v, "")
}
func (u *Unmarshaler) unmarshalWithFullName(m valuerWithParent, v any, fullName string) error {rv := reflect.ValueOf(v)
if err := ValidatePtr(&rv); err != nil {return err}
valueType := reflect.TypeOf(v)
baseType := Deref(valueType)
if baseType.Kind() != reflect.Struct {return errValueNotStruct}
valElem := rv.Elem()
if valElem.Kind() == reflect.Ptr {target := reflect.New(baseType).Elem()
SetValue(valueType.Elem(), valElem, target)
valElem = target
}
numFields := baseType.NumField()
for i := 0; i < numFields; i++ {field := baseType.Field(i)
if !field.IsExported() {continue}
if err := u.processField(field, valElem.Field(i), m, fullName); err != nil {return err}
}
return nil
}
该办法首先调用 unmarshalWithFullName() 办法,传入一个 valuerWithParent 类型的参数 simpleValuer{current: m},指标实例 v 和一个空字符串示意全名。
unmarshalWithFullName() 办法执行以下操作:
- 验证输出的 v 是否为一个无效的指针,如果不是则返回谬误。
- 获取输出参数的类型和去除指针层级后的根本类型。如果根本类型不是构造体,返回谬误。
- 获取 v 的反射值的元素 valElem。如果元素是指针类型,创立一个新的构造体实例并设置为 valElem。
-
遍历构造体的每个字段:
如果字段不是导出的(非公开),则跳过。调用 processField() 办法解决每个字段,将解析的值设置到指标构造体实例的对应字段中。如果呈现谬误,返回谬误。
- 返回 nil,示意胜利执行。