本文将对Go的env包进行源码分析。
3 源码分析
3.1 预定义变量
defaultBuiltInParsers 这个变量是 map[reflect.Kind]ParserFunc 类型,定义了内建的变量类型的转换方法。
defaultTypeParsers 这个变量是 map[reflect.Type]ParserFunc 类型,定义了 url 和 time 的转换方法。
var (
// ErrNotAStructPtr is returned if you pass something that is not a pointer to a
// Struct to Parse
ErrNotAStructPtr = errors.New("env: expected a pointer to a Struct")
defaultBuiltInParsers = map[reflect.Kind]ParserFunc{
reflect.Bool: func(v string) (interface{}, error) {
return strconv.ParseBool(v)
},
reflect.String: func(v string) (interface{}, error) {
return v, nil
},
reflect.Int: func(v string) (interface{}, error) {
i, err := strconv.ParseInt(v, 10, 32)
return int(i), err
},
reflect.Int16: func(v string) (interface{}, error) {
i, err := strconv.ParseInt(v, 10, 16)
return int16(i), err
},
reflect.Int32: func(v string) (interface{}, error) {
i, err := strconv.ParseInt(v, 10, 32)
return int32(i), err
},
reflect.Int64: func(v string) (interface{}, error) {
return strconv.ParseInt(v, 10, 64)
},
reflect.Int8: func(v string) (interface{}, error) {
i, err := strconv.ParseInt(v, 10, 8)
return int8(i), err
},
reflect.Uint: func(v string) (interface{}, error) {
i, err := strconv.ParseUint(v, 10, 32)
return uint(i), err
},
reflect.Uint16: func(v string) (interface{}, error) {
i, err := strconv.ParseUint(v, 10, 16)
return uint16(i), err
},
reflect.Uint32: func(v string) (interface{}, error) {
i, err := strconv.ParseUint(v, 10, 32)
return uint32(i), err
},
reflect.Uint64: func(v string) (interface{}, error) {
i, err := strconv.ParseUint(v, 10, 64)
return i, err
},
reflect.Uint8: func(v string) (interface{}, error) {
i, err := strconv.ParseUint(v, 10, 8)
return uint8(i), err
},
reflect.Float64: func(v string) (interface{}, error) {
return strconv.ParseFloat(v, 64)
},
reflect.Float32: func(v string) (interface{}, error) {
f, err := strconv.ParseFloat(v, 32)
return float32(f), err
},
}
defaultTypeParsers = map[reflect.Type]ParserFunc{
reflect.TypeOf(url.URL{}): func(v string) (interface{}, error) {
u, err := url.Parse(v)
if err != nil {
return nil, fmt.Errorf("unable to parse URL: %v", err)
}
return *u, nil
},
reflect.TypeOf(time.Nanosecond): func(v string) (interface{}, error) {
s, err := time.ParseDuration(v)
if err != nil {
return nil, fmt.Errorf("unable to parse duration: %v", err)
}
return s, err
},
}
)
3.2 ParserFunc类型
ParserFunc 定义了一个方法类型,实现这种方法类型,即可实现自定义的解析方法。
type ParserFunc func(v string) (interface{}, error)
3.3 Parse方法
Parse 方法其实就是 ParseWithFuncs 方法的封装,给 ParseWithFuncs 方法传入了一个空的类型对应解析方法的map.
func Parse(v interface{}) error {
return ParseWithFuncs(v, map[reflect.Type]ParserFunc{})
}
3.4 ParseWithFuncs方法
ParseWithFuncs 方法接收一个interface类型的v变量,和类型对应解析方法的map。
func ParseWithFuncs(v interface{}, funcMap map[reflect.Type]ParserFunc) error {
// 它会取得变量v对应的值,判断是否是一个指针类型,因为它最终的目的是将获取到的环境变量赋值到结构体的字段上,所以需要传入指针才能成功修改对应的值,不然就只在这里面局部有效
ptrRef := reflect.ValueOf(v)
if ptrRef.Kind() != reflect.Ptr {
return ErrNotAStructPtr
}
// 判断传入的变量是否是一个结构体,因为结构体才能获取到Tag标签的值
ref := ptrRef.Elem()
if ref.Kind() != reflect.Struct {
return ErrNotAStructPtr
}
// 将默认的类型对应解析方法与自定义的合并
var parsers = defaultTypeParsers
for k, v := range funcMap {
parsers[k] = v
}
// 执行解析操作
return doParse(ref, parsers)
}
3.5 doParse方法
doParse 方法是主要的执行方法,会遍历结构体的字段进行相应的判断和调用解析方法。
func doParse(ref reflect.Value, funcMap map[reflect.Type]ParserFunc) error {
var refType = ref.Type()
// 获取到结构体的字段数量,进行遍历
for i := 0; i < refType.NumField(); i++ {
// 通过反射获取到第i个字段
refField := ref.Field(i)
// 判断当前字段的值能否修改,不能修改就跳过
if !refField.CanSet() {
continue
}
// 当前字段是指针类型,且不为空的话
if reflect.Ptr == refField.Kind() && !refField.IsNil() {
// 继续调用ParseWithFuncs方法解析这个字段
err := ParseWithFuncs(refField.Interface(), funcMap)
if err != nil {
return err
}
continue
}
// 如果这个字段是结构体类型,且能被寻址
if reflect.Struct == refField.Kind() && refField.CanAddr() && refField.Type().Name() == "" {
// 获取到这个字段的地址,调用Parse方法继续解析
err := Parse(refField.Addr().Interface())
if err != nil {
return err
}
continue
}
refTypeField := refType.Field(i)
// 调用get方法获取到这个字段设置的环境变量的值
value, err := get(refTypeField)
if err != nil {
return err
}
// 如果值为空的话,并且是结构体类型,会调用doParse方法继续解析
if value == "" {
if reflect.Struct == refField.Kind() {
if err := doParse(refField, funcMap); err != nil {
return err
}
}
continue
}
// 调用 set 方法执行类型转换和赋值操作
if err := set(refField, refTypeField, value, funcMap); err != nil {
return err
}
}
return nil
}
3.6 get方法
本方法主要是从结构体的tag标签中获取如何解析,并从环境变量中获取值。
func get(field reflect.StructField) (val string, err error) {
var required bool
var exists bool
var loadFile bool
// 通过反射获取envExpand这个标签,如果值为true则expand设置为true,否则false
var expand = strings.EqualFold(field.Tag.Get("envExpand"), "true")
// 调用parseKeyForOption方法获取到env的key和选项,以,分割,第一个为key,后面是选项
key, opts := parseKeyForOption(field.Tag.Get("env"))
// 遍历选项
for _, opt := range opts {
switch opt {
case "":
break
// 如果设置了file,则标记loadFile为true,后面要加载文件
case "file":
loadFile = true
// 如果使者了required,则标记required为true,后面要做环境变量是否存在的判断
case "required":
required = true
default:
return "", fmt.Errorf("env: tag option %q not supported", opt)
}
}
// 根据envDefault标签获取设置的默认值
defaultValue := field.Tag.Get("envDefault")
// 调用getOr方法获取值,没有设置该变量会返回设置的默认值,并返回是否存在
val, exists = getOr(key, defaultValue)
// 判断是否需要扩张,如需的话则调用os.ExpandEnv方法来扩张
if expand {
val = os.ExpandEnv(val)
}
// 判断是否设置了必须,如果设置了,且环境变量不存在的话会返回错误
if required && !exists {
return "", fmt.Errorf(`env: required environment variable %q is not set`, key)
}
// 判断是否设置了需要加载文件,并且值不为空,是的话就会加载环境变量的值配置的文件
if loadFile && val != "" {
filename := val
val, err = getFromFile(filename)
if err != nil {
return "", fmt.Errorf(`env: could not load content of file "%s" from variable %s: %v`, filename, key, err)
}
}
return val, err
}
3.7 parseKeyForOption方法
根据,符号分割env标签的值,切割后的第一个值作为环境变量的key,后面的作为选项。
func parseKeyForOption(key string) (string, []string) {
opts := strings.Split(key, ",")
return opts[0], opts[1:]
}
3.8 getFromFile方法
读取文件的值。
func getFromFile(filename string) (value string, err error) {
b, err := ioutil.ReadFile(filename)
return string(b), err
}
3.9 getOr方法
该方法会首先调用 os.LookupEnv 方法尝试获取环境变量,如果没有获取到,则将值赋为设置的默认值。
func getOr(key, defaultValue string) (value string, exists bool) {
value, exists = os.LookupEnv(key)
if !exists {
value = defaultValue
}
return value, exists
}
3.10 set方法
进行调用类型转换和赋值的主逻辑。
func set(field reflect.Value, sf reflect.StructField, value string, funcMap map[reflect.Type]ParserFunc) error {
// 如果这个字段是切片类型,则调用handleSlice来处理
if field.Kind() == reflect.Slice {
return handleSlice(field, value, sf, funcMap)
}
// 判断这个字段是否实现了 encoding.TextUnmarshaler 接口,如果实现了,则调用UnmarshalText方法进行解析
var tm = asTextUnmarshaler(field)
if tm != nil {
var err = tm.UnmarshalText([]byte(value))
return newParseError(sf, err)
}
var typee = sf.Type
var fieldee = field
// 判断是否是一个指针,如果是的话获取到对应的值
if typee.Kind() == reflect.Ptr {
typee = typee.Elem()
fieldee = field.Elem()
}
// 判断这个类型是否在自定义的类型对应解析方法map中,如果在则执行对应的自定义解析方法
parserFunc, ok := funcMap[typee]
if ok {
val, err := parserFunc(value)
if err != nil {
return newParseError(sf, err)
}
fieldee.Set(reflect.ValueOf(val))
return nil
}
// 判断对应的类型是否在内建的解析方法中,在则执行解析
parserFunc, ok = defaultBuiltInParsers[typee.Kind()]
if ok {
val, err := parserFunc(value)
if err != nil {
return newParseError(sf, err)
}
fieldee.Set(reflect.ValueOf(val).Convert(typee))
return nil
}
// 没有找到对应类型的解析方法则返回错误
return newNoParserError(sf)
}
3.11 handleSlice方法
处理切片的对应方法。
func handleSlice(field reflect.Value, value string, sf reflect.StructField, funcMap map[reflect.Type]ParserFunc) error {
// 获取字段配置的分割标签,默认为,
var separator = sf.Tag.Get("envSeparator")
if separator == "" {
separator = ","
}
// 把获取到的值根据分割标签进行分割
var parts = strings.Split(value, separator)
// 获取到对应的值
var typee = sf.Type.Elem()
if typee.Kind() == reflect.Ptr {
typee = typee.Elem()
}
// 看是否实现了encoding.TextUnmarshaler接口,实现了则调用parseTextUnmarshalers方法解析
if _, ok := reflect.New(typee).Interface().(encoding.TextUnmarshaler); ok {
return parseTextUnmarshalers(field, parts, sf)
}
// 尝试从自定义和内建类型获取对应的解析方法
parserFunc, ok := funcMap[typee]
if !ok {
parserFunc, ok = defaultBuiltInParsers[typee.Kind()]
if !ok {
return newNoParserError(sf)
}
}
// 先生成一个对应类型的切片,然后遍历所有部分,进行类型转换
var result = reflect.MakeSlice(sf.Type, 0, len(parts))
for _, part := range parts {
r, err := parserFunc(part)
if err != nil {
return newParseError(sf, err)
}
var v = reflect.ValueOf(r).Convert(typee)
if sf.Type.Elem().Kind() == reflect.Ptr {
v = reflect.New(typee)
v.Elem().Set(reflect.ValueOf(r).Convert(typee))
}
result = reflect.Append(result, v)
}
field.Set(result)
return nil
}
3.12 asTextUnmarshaler方法
判断是否实现了 encoding.TextUnmarshaler 接口。
func asTextUnmarshaler(field reflect.Value) encoding.TextUnmarshaler {
if reflect.Ptr == field.Kind() {
if field.IsNil() {
field.Set(reflect.New(field.Type().Elem()))
}
} else if field.CanAddr() {
field = field.Addr()
}
tm, ok := field.Interface().(encoding.TextUnmarshaler)
if !ok {
return nil
}
return tm
}
3.13 parseTextUnmarshalers方法
对于实现了 encoding.TextUnmarshaler 接口的切片类型字段的处理方法。
func parseTextUnmarshalers(field reflect.Value, data []string, sf reflect.StructField) error {
s := len(data)
elemType := field.Type().Elem()
slice := reflect.MakeSlice(reflect.SliceOf(elemType), s, s)
for i, v := range data {
sv := slice.Index(i)
kind := sv.Kind()
if kind == reflect.Ptr {
sv = reflect.New(elemType.Elem())
} else {
sv = sv.Addr()
}
tm := sv.Interface().(encoding.TextUnmarshaler)
if err := tm.UnmarshalText([]byte(v)); err != nil {
return newParseError(sf, err)
}
if kind == reflect.Ptr {
slice.Index(i).Set(sv)
}
}
field.Set(slice)
return nil
}
4 不足
env库虽然为获取环境变量与转换类型提供了便利,但其缺少了对 .env 文件解析的能力,在实际场景中,大多数环境变量都是通过 .env 文件配置的,所以这也是一个遗憾吧。
如果你喜欢我的文章,欢迎点赞和关注。
