go代码示例:reflect反射

介绍

这篇文章使用一些列子来介绍 reflect 包的用法. reflect包主要做一些decoding/encoding的事情.动态的调用函数.大部分的例子都是我之前项目中的代码. 如果有什么疑问或者有建议请在文章下面留言. 这里介绍一个go语言卡通图标的项目@egonelbre/gophers.

Reflect Usage

读取 struct 的 tag

这种用法在 gorm模型中,gin参数验证,beego动态路由,和json解析的时候用到过. 如果你写一些开源工具的时候也许会用到.

package main

import (
	"fmt"
	"reflect"
)

type User struct {
	Email  string `mcl:"email"`
	Name   string `mcl:"name"`
	Age    int    `mcl:"age"`
	Github string `mcl:"github" default:"a8m"`
}

func main() {
	var u interface{} = User{}
	//TypeOf 返回 reflect.Type 表示 u的动态type
	t := reflect.TypeOf(u)
	//kind 返回类型
	if t.Kind() != reflect.Struct {
		return
	}
	//变量field
	for i := 0; i < t.NumField(); i++ {
		f := t.Field(i)
		//获取tag的值
		fmt.Println(f.Tag.Get("mcl"), f.Tag.Get("default"))
	}
}

调用 Kind() 中的一个 类型常量.

获取和设置结构体的属性值

package main

import (
	"fmt"
	"reflect"
)

type User struct {
	Email  string `mcl:"email"`
	Name   string `mcl:"name"`
	Age    int    `mcl:"age"`
	Github string `mcl:"github" default:"a8m"`
}

func main() {
	u := &User{Name: "Ariel Mashraki"}
	// Elem returns the value that the pointer u points to.
	//得到u的指针
	v := reflect.ValueOf(u).Elem()
	f := v.FieldByName("Github")
	// make sure that this field is defined, and can be changed.
	//确保field 是有效的 也是可以写的
	if !f.IsValid() || !f.CanSet() {
		return
	}
	if f.Kind() != reflect.String || f.String() != "" {
		return
	}
	f.SetString("a8m")
	fmt.Printf("Github username was changed to: %q\n", u.Github)
}

向不知都类型的slice中添加字符 使用场景decoder

package main

import (
	"fmt"
	"io"
	"reflect"
)

func main() {
	var (
		a []string
		b []interface{}
		c []io.Writer
	)
	fmt.Println(fill(&a), a) // pass
	fmt.Println(fill(&b), b) // pass
	fmt.Println(fill(&c), c) // fail
}

func fill(i interface{}) error {
	v := reflect.ValueOf(i)
	if v.Kind() != reflect.Ptr {
		return fmt.Errorf("non-pointer %v", v.Type())
	}
	// get the value that the pointer v points to.
	v = v.Elem()
	if v.Kind() != reflect.Slice {
		return fmt.Errorf("can't fill non-slice value")
	}
	v.Set(reflect.MakeSlice(v.Type(), 3, 3))
	// validate the type of the slice. see below.
	if !canAssign(v.Index(0)) {
		return fmt.Errorf("can't assign string to slice elements")
	}
	for i, w := range []string{"foo", "bar", "baz"} {
		v.Index(i).Set(reflect.ValueOf(w))
	}
	return nil
}

// we accept strings, or empty interfaces.
func canAssign(v reflect.Value) bool {
	return v.Kind() == reflect.String || (v.Kind() == reflect.Interface && v.NumMethod() == 0)
}

给数字类型变量设置值,场景decoder

package main

import (
	"fmt"
	"reflect"
)

const n = 255

func main() {
	var (
		a int8
		b int16
		c uint
		d float32
		e string
	)
	fmt.Println(fill(&a), a)
	fmt.Println(fill(&b), b)
	fmt.Println(fill(&c), c)
	fmt.Println(fill(&d), c)
	fmt.Println(fill(&e), e)
}

func fill(i interface{}) error {
	v := reflect.ValueOf(i)
	if v.Kind() != reflect.Ptr {
		return fmt.Errorf("non-pointer %v", v.Type())
	}
	// get the value that the pointer v points to.
	v = v.Elem()
	switch v.Kind() {
	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
		if v.OverflowInt(n) {
			return fmt.Errorf("can't assign value due to %s-overflow", v.Kind())
		}
		v.SetInt(n)
	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
		if v.OverflowUint(n) {
			return fmt.Errorf("can't assign value due to %s-overflow", v.Kind())
		}
		v.SetUint(n)
	case reflect.Float32, reflect.Float64:
		if v.OverflowFloat(n) {
			return fmt.Errorf("can't assign value due to %s-overflow", v.Kind())
		}
		v.SetFloat(n)
	default:
		return fmt.Errorf("can't assign value to a non-number type")
	}
	return nil
}

Decode key-value 到 map

package main

import (
	"fmt"
	"reflect"
	"strconv"
	"strings"
)

func main() {
	var (
		m0 = make(map[int]string)
		m1 = make(map[int64]string)
		m2 map[interface{}]string
		m3 map[interface{}]interface{}
		m4 map[bool]string
	)
	s := "1=foo,2=bar,3=baz"
	fmt.Println(decodeMap(s, &m0), m0) // pass
	fmt.Println(decodeMap(s, &m1), m1) // pass
	fmt.Println(decodeMap(s, &m2), m2) // pass
	fmt.Println(decodeMap(s, &m3), m3) // pass
	fmt.Println(decodeMap(s, &m4), m4) // fail
}

func decodeMap(s string, i interface{}) error {
	v := reflect.ValueOf(i)
	if v.Kind() != reflect.Ptr {
		return fmt.Errorf("non-pointer %v", v.Type())
	}
	// get the value that the pointer v points to.
	v = v.Elem()
	t := v.Type()
	// allocate a new map, if v is nil. see: m2, m3, m4.
	if v.IsNil() {
		v.Set(reflect.MakeMap(t))
	}
	// assume that the input is valid.
	for _, kv := range strings.Split(s, ",") {
		s := strings.Split(kv, "=")
		n, err := strconv.Atoi(s[0])
		if err != nil {
			return fmt.Errorf("failed to parse number: %v", err)
		}
		k, e := reflect.ValueOf(n), reflect.ValueOf(s[1])
		// get the type of the key.
		kt := t.Key()
		if !k.Type().ConvertibleTo(kt) {
			return fmt.Errorf("can't convert key to type %v", kt.Kind())
		}
		k = k.Convert(kt)
		// get the element type.
		et := t.Elem()
		if et.Kind() != v.Kind() && !e.Type().ConvertibleTo(et) {
			return fmt.Errorf("can't assign value to type %v", kt.Kind())
		}
		v.SetMapIndex(k, e.Convert(et))
	}
	return nil
}

Decode key-value 到结构体

package main

import (
	"fmt"
	"reflect"
	"strings"
)

type User struct {
	Name    string
	Github  string
	private string
}

func main() {
	var (
		v0 User
		v1 *User
		v2 = new(User)
		v3 struct{ Name string }
		s  = "Name=Ariel,Github=a8m"
	)
	fmt.Println(decode(s, &v0), v0) // pass
	fmt.Println(decode(s, v1), v1)  // fail
	fmt.Println(decode(s, v2), v2)  // pass
	fmt.Println(decode(s, v3), v3)  // fail
	fmt.Println(decode(s, &v3), v3) // pass
}

func decode(s string, i interface{}) error {
	v := reflect.ValueOf(i)
	if v.Kind() != reflect.Ptr || v.IsNil() {
		return fmt.Errorf("decode requires non-nil pointer")
	}
	// get the value that the pointer v points to.
	v = v.Elem()
	// assume that the input is valid.
	for _, kv := range strings.Split(s, ",") {
		s := strings.Split(kv, "=")
		f := v.FieldByName(s[0])
		// make sure that this field is defined, and can be changed.
		if !f.IsValid() || !f.CanSet() {
			continue
		}
		// assume all the fields are type string.
		f.SetString(s[1])
	}
	return nil
}

Encode 结构体到 key-value 对

package main

import (
	"fmt"
	"reflect"
	"strings"
)

type User struct {
	Email   string `kv:"email,omitempty"`
	Name    string `kv:"name,omitempty"`
	Github  string `kv:"github,omitempty"`
	private string
}

func main() {
	var (
		u = User{Name: "Ariel", Github: "a8m"}
		v = struct {
			A, B, C string
		}{
			"foo",
			"bar",
			"baz",
		}
		w = &User{}
	)
	fmt.Println(encode(u))
	fmt.Println(encode(v))
	fmt.Println(encode(w))
}

// this example supports only structs, and assume their
// fields are type string.
func encode(i interface{}) (string, error) {
	v := reflect.ValueOf(i)
	t := v.Type()
	if t.Kind() != reflect.Struct {
		return "", fmt.Errorf("type %s is not supported", t.Kind())
	}
	var s []string
	for i := 0; i < t.NumField(); i++ {
		f := t.Field(i)
		// skip unexported fields. from godoc:
		// PkgPath is the package path that qualifies a lower case (unexported)
		// field name. It is empty for upper case (exported) field names.
		if f.PkgPath != "" {
			continue
		}
		fv := v.Field(i)
		key, omit := readTag(f)
		// skip empty values when "omitempty" set.
		if omit && fv.String() == "" {
			continue
		}
		s = append(s, fmt.Sprintf("%s=%s", key, fv.String()))
	}
	return strings.Join(s, ","), nil
}

func readTag(f reflect.StructField) (string, bool) {
	val, ok := f.Tag.Lookup("kv")
	if !ok {
		return f.Name, false
	}
	opts := strings.Split(val, ",")
	omit := false
	if len(opts) == 1 {
		omit = opts[1] == "omitempty"
	}
	return opts[0], omit
}

检查某个对象是否实现 interface 方法

package main

import (
	"fmt"
	"reflect"
)

type Marshaler interface {
	MarshalKV() (string, error)
}

type User struct {
	Email   string `kv:"email,omitempty"`
	Name    string `kv:"name,omitempty"`
	Github  string `kv:"github,omitempty"`
	private string
}

func (u User) MarshalKV() (string, error) {
	return fmt.Sprintf("name=%s,email=%s,github=%s", u.Name, u.Email, u.Github), nil
}

func main() {
	fmt.Println(encode(User{"boring", "Ariel", "a8m", ""}))
	fmt.Println(encode(&User{Github: "posener", Name: "Eyal", Email: "boring"}))
}

var marshalerType = reflect.TypeOf(new(Marshaler)).Elem()

func encode(i interface{}) (string, error) {
	t := reflect.TypeOf(i)
	if !t.Implements(marshalerType) {
		return "", fmt.Errorf("encode only supports structs that implement the Marshaler interface")
	}
	m, _ := reflect.ValueOf(i).Interface().(Marshaler)
	return m.MarshalKV()
}

调用函数

不带参数没有返回值调用函数

package main

import (
	"fmt"
	"reflect"
)

type A struct{}

func (A) Hello() { fmt.Println("World") }

func main() {
	// ValueOf returns a new Value, which is the reflection interface to a Go value.
	v := reflect.ValueOf(A{})
	m := v.MethodByName("Hello")
	if m.Kind() != reflect.Func {
		return
	}
	m.Call(nil)
}

带一系列参数调用函数验证并返回值

package main

import (
	"fmt"
	"reflect"
)

func Add(a, b int) int { return a + b }

func main() {
	v := reflect.ValueOf(Add)
	if v.Kind() != reflect.Func {
		return
	}
	t := v.Type()
	argv := make([]reflect.Value, t.NumIn())
	for i := range argv {
		// validate the type of parameter "i".
		if t.In(i).Kind() != reflect.Int {
			return
		}
		argv[i] = reflect.ValueOf(i)
	}
	// note that, len(result) == t.NumOut()
	result := v.Call(argv)
	if len(result) != 1 || result[0].Kind() != reflect.Int {
		return
	}
	fmt.Println(result[0].Int())
}

动态调用函数,类似于 template/text

可以用在写自己的动态路由

package main

import (
	"fmt"
	"html/template"
	"reflect"
	"strconv"
	"strings"
)
func main() {
	funcs := template.FuncMap{
		"trim":    strings.Trim,
		"lower":   strings.ToLower,
		"repeat":  strings.Repeat,
		"replace": strings.Replace,
	}
	fmt.Println(eval(`{{ "hello" 4 | repeat }}`, funcs))
	fmt.Println(eval(`{{ "Hello-World" | lower }}`, funcs))
	fmt.Println(eval(`{{ "foobarfoo" "foo" "bar" -1 | replace }}`, funcs))
	fmt.Println(eval(`{{ "-^-Hello-^-" "-^" | trim }}`, funcs))
}


// evaluate an expression. note that this implemetation is assuming that the
// input is valid, and also very limited. for example, whitespaces are not allowed
// inside a quoted string.
func eval(s string, funcs template.FuncMap) (string, error) {
	args, name := parseArgs(s)
	fn, ok := funcs[name]
	if !ok {
		return "", fmt.Errorf("function %s is not defined", name)
	}
	v := reflect.ValueOf(fn)
	t := v.Type()
	if len(args) != t.NumIn() {
		return "", fmt.Errorf("invalid number of arguments. got: %v, want: %v", len(args), t.NumIn())
	}
	argv := make([]reflect.Value, len(args))
	// go over the arguments, validate and build them.
	// note that we support only int, and string in this simple example.
	for i := range argv {
		var argType reflect.Kind
		// if the argument "i" is string.
		if strings.HasPrefix(args[i], "\"") {
			argType = reflect.String
			argv[i] = reflect.ValueOf(strings.Trim(args[i], "\""))
		} else {
			argType = reflect.Int
			// assume that the input is valid.
			num, _ := strconv.Atoi(args[i])
			argv[i] = reflect.ValueOf(num)
		}
		if t.In(i).Kind() != argType {
			return "", fmt.Errorf("Invalid argument. got: %v, want: %v", argType, t.In(i).Kind())
		}
	}
	result := v.Call(argv)
	// in real-world code, we validate it before executing the function,
	// using the v.NumOut() method. similiar to the text/template package.
	if len(result) != 1 || result[0].Kind() != reflect.String {
		return "", fmt.Errorf("function %s must return a one string value", name)
	}
	return result[0].String(), nil
}

// parseArgs is an auxiliary function, that extract the function and its
// parameter from the given expression.
func parseArgs(s string) ([]string, string) {
	args := strings.Split(strings.Trim(s, "{ }"), "|")
	return strings.Split(strings.Trim(args[0], " "), " "), strings.Trim(args[len(args)-1], " ")
}

调用可变参数函数

package main

import (
	"fmt"
	"math/rand"
	"reflect"
)

func Sum(x1, x2 int, xs ...int) int {
	sum := x1 + x2
	for _, xi := range xs {
		sum += xi
	}
	return sum
}

func main() {
	v := reflect.ValueOf(Sum)
	if v.Kind() != reflect.Func {
		return
	}
	t := v.Type()
	argc := t.NumIn()
	if t.IsVariadic() {
		argc += rand.Intn(10)
	}
	argv := make([]reflect.Value, argc)
	for i := range argv {
		argv[i] = reflect.ValueOf(i)
	}
	result := v.Call(argv)
	fmt.Println(result[0].Int()) // assume that t.NumOut() > 0 tested above.
}

在runtime中创建函数

package main

import (
	"fmt"
	"reflect"
)

type Add func(int64, int64) int64

func main() {
	t := reflect.TypeOf(Add(nil))
	mul := reflect.MakeFunc(t, func(args []reflect.Value) []reflect.Value {
		a := args[0].Int()
		b := args[1].Int()
		return []reflect.Value{reflect.ValueOf(a+b)}
	})
	fn, ok := mul.Interface().(Add)
	if !ok {
		return
	}
	fmt.Println(fn(2,3))
}

修改权限

//相当于cd 改变当前的工作目录到 dir 工作目录
func Chdir(dir string) error
//chmod
func Chmod(name string, mode FileMode) error
//chown
func Chown(name string, uid, gid int) error
//修改文件的时间戳
func Chtimes(name string, atime time.Time, mtime time.Time) error
目录