Appearance
The Go Programming Language - 反射
1. reflect.Type 和 reflect.Value
反射是由 reflect 包提供的。它定义了两个重要的类型,Type 和 Value。一个 Type 表示一个 Go 类型。它是一个接口,有许多方法来区分类型以及检查它们的组成部分,例如一个结构体的成员或一个函数的参数等。唯一能反映 reflect.Type 实现的是接口的类型描述信息,也正是这个实体标识了接口值的动态类型。
1.1. reflect.Type
函数 reflect.TypeOf 接受任意的 interface{} 类型,并以 reflect.Type 形式返回其动态类型:
Go
t := reflect.TypeOf(3) // a reflect.Type
fmt.Println(t.String()) // "int"
fmt.Println(t) // "int"1
2
3
2
3
其中 TypeOf(3) 调用将值 3 传给 interface{} 参数。将一个具体的值转为接口类型会有一个隐式的接口转换操作,它会创建一个包含两个信息的接口值: 操作数的动态类型(这里是 int)和它的动态的值(这里是 3)。
因为 reflect.TypeOf 返回的是一个接口值的动态类型,它总是返回具体的类型。因此,下面的代码将打印 *os.File 而不是 io.Writer。稍后,我们将看到能够表达接口类型的 reflect.Type。
Go
var w io.Writer = os.Stdout
fmt.Println(reflect.TypeOf(w)) // "*os.File"1
2
2
要注意的是 reflect.Type 接口是满足 fmt.Stringer 接口的。因为打印一个接口的动态类型对于调试和日志是有帮助的,fmt.Printf 提供了一个缩写 %T 参数,内部使用 reflect.TypeOf 来输出:
Go
fmt.Printf("%T\n", 3) // "int"1.2. reflect.Value
reflect 包中另一个重要的类型是 Value。一个 reflect.Value 可以装载任意类型的值。函数 reflect.ValueOf 接受任意的 interface{} 类型,并返回一个装载着其动态值的 reflect.Value。和 reflect.TypeOf 类似,reflect.ValueOf 返回的结果也是具体的类型,但是 reflect.Value 也可以持有一个接口值。
Go
v := reflect.ValueOf(3) // a reflect.Value
fmt.Println(v) // "3"
fmt.Printf("%v\n", v) // "3"
fmt.Println(v.String()) // NOTE: "<int Value>"1
2
3
4
2
3
4
和 reflect.Type 类似,reflect.Value 也满足 fmt.Stringer 接口,但是除非 Value 持有的是字符串,否则 String 方法只返回其类型。而使用 fmt 包的 %v 标志参数会对 reflect.Value 特殊处理。
对 Value 调用 Type 方法将返回具体类型所对应的 reflect.Type:
Go
t := v.Type() // a reflect.Type
fmt.Println(t.String()) // "int"1
2
2
reflect.ValueOf 的逆操作是 reflect.Value.Interface 方法。它返回一个 interface{} 类型,装载着与 reflect.Value 相同的具体值:
Go
v := reflect.ValueOf(3) // a reflect.Value
x := v.Interface() // an interface{}
i := x.(int) // an int
fmt.Printf("%d\n", i) // "3"1
2
3
4
2
3
4
reflect.Value 和 interface{} 都能装载任意的值。所不同的是,一个空的接口隐藏了值内部的表示方式和所有方法,因此只有我们知道具体的动态类型才能使用类型断言来访问内部的值(就像上面那样),内部值我们没法访问。相比之下,一个 Value 则有很多方法来检查其内容,无论它的具体类型是什么。让我们再次尝试实现我们的格式化函数 format.Any。
1.3. 示例:formatAtom
我们使用 reflect.Value 的 Kind 方法来替代之前的类型 switch。虽然还是有无穷多的类型,但是它们的 kinds 类型却是有限的: Bool、String 和 所有数字类型的基础类型;Array 和 Struct 对应的聚合类型;Chan、Func、Ptr、Slice 和 Map 对应的引用类型;interface 类型;还有表示空值的 Invalid 类型。(空的 reflect.Value 的 kind 即为 Invalid。)
Go
package format
import (
"reflect"
"strconv"
)
// Any formats any value as a string.
func Any(value interface{}) string {
return formatAtom(reflect.ValueOf(value))
}
// formatAtom formats a value without inspecting its internal structure.
func formatAtom(v reflect.Value) string {
switch v.Kind() {
case reflect.Invalid:
return "invalid"
case reflect.Int, reflect.Int8, reflect.Int16,
reflect.Int32, reflect.Int64:
return strconv.FormatInt(v.Int(), 10)
case reflect.Uint, reflect.Uint8, reflect.Uint16,
reflect.Uint32, reflect.Uint64, reflect.Uintptr:
return strconv.FormatUint(v.Uint(), 10)
// ...floating-point and complex cases omitted for brevity...
case reflect.Bool:
return strconv.FormatBool(v.Bool())
case reflect.String:
return strconv.Quote(v.String())
case reflect.Chan, reflect.Func, reflect.Ptr, reflect.Slice, reflect.Map:
return v.Type().String() + " 0x" +
strconv.FormatUint(uint64(v.Pointer()), 16)
default: // reflect.Array, reflect.Struct, reflect.Interface
return v.Type().String() + " value"
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
到目前为止,我们的函数将每个值视作一个不可分割没有内部结构的物品,因此它叫 formatAtom。对于聚合类型(结构体和数组)和接口,只是打印值的类型,对于引用类型(channels、functions、pointers、slices、和 maps),打印类型和十六进制的引用地址。虽然还不够理想,但是依然是一个重大的进步,并且 Kind 只关心底层表示,format.Any 也支持具名类型。例如:
Go
var x int64 = 1
var d time.Duration = 1 * time.Nanosecond
fmt.Println(format.Any(x)) // "1"
fmt.Println(format.Any(d)) // "1"
fmt.Println(format.Any([]int64{x})) // "[]int64 0x8202b87b0"
fmt.Println(format.Any([]time.Duration{d})) // "[]time.Duration 0x8202b87e0"1
2
3
4
5
6
2
3
4
5
6
1.4. 示例:递归打印
让我们来继续完善 formatAtom 函数对聚合数据类型的显示,首先我们需要一个递归函数:
Go
func display(path string, v reflect.Value) {
switch v.Kind() {
case reflect.Invalid:
fmt.Printf("%s = invalid\n", path)
case reflect.Slice, reflect.Array:
for i := 0; i < v.Len(); i++ {
display(fmt.Sprintf("%s[%d]", path, i), v.Index(i))
}
case reflect.Struct:
for i := 0; i < v.NumField(); i++ {
fieldPath := fmt.Sprintf("%s.%s", path, v.Type().Field(i).Name)
display(fieldPath, v.Field(i))
}
case reflect.Map:
for _, key := range v.MapKeys() {
display(fmt.Sprintf("%s[%s]", path,
formatAtom(key)), v.MapIndex(key))
}
case reflect.Ptr:
if v.IsNil() {
fmt.Printf("%s = nil\n", path)
} else {
display(fmt.Sprintf("(*%s)", path), v.Elem())
}
case reflect.Interface:
if v.IsNil() {
fmt.Printf("%s = nil\n", path)
} else {
fmt.Printf("%s.type = %s\n", path, v.Elem().Type())
display(path+".value", v.Elem())
}
default: // basic types, channels, funcs
fmt.Printf("%s = %s\n", path, formatAtom(v))
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
让我们针对不同类型分别讨论。
Slice 和数组:两种的处理逻辑是一样的。
Len方法返回 slice 或数组值中的元素个数,Index(i)活动索引i对应的元素,返回的也是一个reflect.Value;如果索引i超出范围的话将导致 panic 异常,这与数组或 slice 类型内建的len(a)和a[i]操作类似。display针对序列中的每个元素递归调用自身处理,我们通过在递归处理时向path附加[i]来表示访问路径;虽然
reflect.Value类型带有很多方法,但是只有少数的方法能对任意值都安全调用。例如,Index方法只能对 Slice、数组或字符串类型的值调用,如果对其它类型调用则会导致 panic 异常。结构体:
NumField方法报告结构体中成员的数量,Field(i)以reflect.Value类型返回第i个成员的值。成员列表也包括通过匿名字段提升上来的成员。为了在path添加.f来表示成员路径,我们必须获得结构体对应的reflect.Type类型信息,然后访问结构体第i个成员的名字;Maps:
MapKeys方法返回一个reflect.Value类型的 slice,每一个元素对应map的一个 key。和往常一样,遍历map时顺序是随机的。MapIndex(key)返回map中 key 对应的 value。我们向path添加[key]来表示访问路径。(我们这里有一个未完成的工作。其实map的 key 的类型并不局限于formatAtom能完美处理的类型;数组、结构体和接口都可以作为map的 key);指针:
Elem方法返回指针指向的变量,依然是reflect.Value类型。即使指针是nil,这个操作也是安全的,在这种情况下指针是Invalid类型,但是我们可以用IsNil方法来显式地测试一个空指针,这样我们可以打印更合适的信息。我们在path前面添加*,并用括弧包含以避免歧义;接口:再一次,我们使用
IsNil方法来测试接口是否是nil,如果不是,我们可以调用v.Elem()来获取接口对应的动态值,并且打印对应的类型和值;
现在我们的 Display 函数总算完工了,让我们看看它的表现吧:
Go
type Movie struct {
Title, Subtitle string
Year int
Color bool
Actor map[string]string
Oscars []string
Sequel *string
}
// ...
strangelove := Movie{
Title: "Dr. Strangelove",
Subtitle: "How I Learned to Stop Worrying and Love the Bomb",
Year: 1964,
Color: false,
Actor: map[string]string{
"Dr. Strangelove": "Peter Sellers",
"Grp. Capt. Lionel Mandrake": "Peter Sellers",
"Pres. Merkin Muffley": "Peter Sellers",
"Gen. Buck Turgidson": "George C. Scott",
"Brig. Gen. Jack D. Ripper": "Sterling Hayden",
`Maj. T.J. "King" Kong`: "Slim Pickens",
},
Oscars: []string{
"Best Actor (Nomin.)",
"Best Adapted Screenplay (Nomin.)",
"Best Director (Nomin.)",
"Best Picture (Nomin.)",
},
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
Display("strangelove", strangelove) 调用将显示:
Text
Display strangelove (display.Movie):
strangelove.Title = "Dr. Strangelove"
strangelove.Subtitle = "How I Learned to Stop Worrying and Love the Bomb"
strangelove.Year = 1964
strangelove.Color = false
strangelove.Actor["Gen. Buck Turgidson"] = "George C. Scott"
strangelove.Actor["Brig. Gen. Jack D. Ripper"] = "Sterling Hayden"
strangelove.Actor["Maj. T.J. \"King\" Kong"] = "Slim Pickens"
strangelove.Actor["Dr. Strangelove"] = "Peter Sellers"
strangelove.Actor["Grp. Capt. Lionel Mandrake"] = "Peter Sellers"
strangelove.Actor["Pres. Merkin Muffley"] = "Peter Sellers"
strangelove.Oscars[0] = "Best Actor (Nomin.)"
strangelove.Oscars[1] = "Best Adapted Screenplay (Nomin.)"
strangelove.Oscars[2] = "Best Director (Nomin.)"
strangelove.Oscars[3] = "Best Picture (Nomin.)"
strangelove.Sequel = nil1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
我们也可以使用 Display 函数来显示标准库中类型的内部结构,例如 *os.File 类型:
Go
Display("os.Stderr", os.Stderr)
// Output:
// Display os.Stderr (*os.File):
// (*(*os.Stderr).file).fd = 2
// (*(*os.Stderr).file).name = "/dev/stderr"
// (*(*os.Stderr).file).nepipe = 01
2
3
4
5
6
2
3
4
5
6
可以看出,反射能够访问到结构体中未导出的成员。需要当心的是这个例子的输出在不同操作系统上可能是不同的,并且随着标准库的发展也可能导致结果不同。(这也是将这些成员定义为私有成员的原因之一)我们甚至可以用 Display 函数来显示 reflect.Value 的内部构造(在这里设置为 *os.File 的类型描述体)。Display("rV", reflect.ValueOf(os.Stderr)) 调用的输出如下,当然不同环境得到的结果可能有差异:
Text
Display rV (reflect.Value):
(*rV.typ).size = 8
(*rV.typ).hash = 871609668
(*rV.typ).align = 8
(*rV.typ).fieldAlign = 8
(*rV.typ).kind = 22
(*(*rV.typ).string) = "*os.File"
(*(*(*rV.typ).uncommonType).methods[0].name) = "Chdir"
(*(*(*(*rV.typ).uncommonType).methods[0].mtyp).string) = "func() error"
(*(*(*(*rV.typ).uncommonType).methods[0].typ).string) = "func(*os.File) error"
...1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
观察下面两个例子的区别:
Go
var i interface{} = 3
Display("i", i)
// Output:
// Display i (int):
// i = 3
Display("&i", &i)
// Output:
// Display &i (*interface {}):
// (*&i).type = int
// (*&i).value = 31
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
在第一个例子中,Display 函数调用 reflect.ValueOf(i),它返回一个 Int 类型的值。正如我们之前提到的,reflect.ValueOf 总是返回一个具体类型的 Value,因为它是从一个接口值提取的内容。
在第二个例子中,Display 函数调用的是 reflect.ValueOf(&i),它返回一个指向 i 的指针,对应 Ptr 类型。在 switch 的 Ptr 分支中,对这个值调用 Elem 方法,返回一个 Value 来表示变量 i 本身,对应 Interface 类型。像这样一个间接获得的 Value,可能代表任意类型的值,包括接口类型。display 函数递归调用自身,这次它分别打印了这个接口的动态类型和值。
对于目前的实现,如果遇到对象图中含有回环,Display 将会陷入死循环,例如下面这个首尾相连的链表:
Go
// a struct that points to itself
type Cycle struct{ Value int; Tail *Cycle }
var c Cycle
c = Cycle{42, &c}
Display("c", c)1
2
3
4
5
2
3
4
5
Display 会永远不停地进行深度递归打印:
Text
Display c (display.Cycle):
c.Value = 42
(*c.Tail).Value = 42
(*(*c.Tail).Tail).Value = 42
(*(*(*c.Tail).Tail).Tail).Value = 42
...ad infinitum...1
2
3
4
5
6
2
3
4
5
6
许多 Go 语言程序都包含了一些循环的数据。让 Display 支持这类带环的数据结构需要些技巧,需要额外记录迄今访问的路径;相应会带来成本。通用的解决方案是采用 unsafe 的语言特性,我们将在后续看到具体的解决方案。
带环的数据结构很少会对 fmt.Sprint 函数造成问题,因为它很少尝试打印完整的数据结构。例如,当它遇到一个指针的时候,它只是简单第打印指针的数字值。在打印包含自身的 slice 或 map 时可能卡住,但是这种情况很罕见,不值得付出为了处理回环所需的开销。
2. 通过 reflect.Value 修改值
在本节中我们将重点讨论如何通过反射机制来修改变量。
回想一下,Go 语言中类似 x、x.f[1] 和 *p 形式的表达式都可以表示变量,但是其它如 x + 1 和 f(2) 则不是变量。一个变量就是一个可寻址的内存空间,里面存储了一个值,并且存储的值可以通过内存地址来更新。
对于 reflect.Value 也有类似的区别。有一些 reflect.Value 是可取地址的;其它一些则不可以。考虑以下的声明语句:
Go
x := 2 // value type variable?
a := reflect.ValueOf(2) // 2 int no
b := reflect.ValueOf(x) // 2 int no
c := reflect.ValueOf(&x) // &x *int no
d := c.Elem() // 2 int yes (x)1
2
3
4
5
2
3
4
5
其中 a 对应的变量不可取地址。因为 a 中的值仅仅是整数 2 的拷贝副本。b 中的值也同样不可取地址。c 中的值还是不可取地址,它只是一个指针 &x 的拷贝。实际上,所有通过 reflect.ValueOf(x) 返回的 reflect.Value 都是不可取地址的。但是对于 d,它是 c 的解引用方式生成的,指向另一个变量,因此是可取地址的。我们可以通过调用 reflect.ValueOf(&x).Elem(),来获取任意变量 x 对应的可取地址的 Value。
我们可以通过调用 reflect.Value 的 CanAddr 方法来判断其是否可以被取地址:
Go
fmt.Println(a.CanAddr()) // "false"
fmt.Println(b.CanAddr()) // "false"
fmt.Println(c.CanAddr()) // "false"
fmt.Println(d.CanAddr()) // "true"1
2
3
4
2
3
4
每当我们通过指针间接地获取的 reflect.Value 都是可取地址的,即使开始的是一个不可取地址的 Value。在反射机制中,所有关于是否支持取地址的规则都是类似的。例如,slice 的索引表达式 e[i] 将隐式地包含一个指针,它就是可取地址的,即使开始的 e 表达式不支持也没有关系。以此类推,reflect.ValueOf(e).Index(i) 对于的值也是可取地址的,即使原始的 reflect.ValueOf(e) 不支持也没有关系。
要从变量对应的可取地址的 reflect.Value 来访问变量需要三个步骤。第一步是调用 Addr() 方法,它返回一个 Value,里面保存了指向变量的指针。然后是在 Value 上调用 Interface() 方法,也就是返回一个 interface{},里面包含指向变量的指针。最后,如果我们知道变量的类型,我们可以使用类型的断言机制将得到的 interface{} 类型的接口强制转为普通的类型指针。这样我们就可以通过这个普通指针来更新变量了:
Go
x := 2
d := reflect.ValueOf(&x).Elem() // d refers to the variable x
px := d.Addr().Interface().(*int) // px := &x
*px = 3 // x = 3
fmt.Println(x) // "3"1
2
3
4
5
2
3
4
5
或者,不使用指针,而是通过调用可取地址的 reflect.Value 的 reflect.Value.Set 方法来更新对于的值:
Go
d.Set(reflect.ValueOf(4))
fmt.Println(x) // "4"1
2
2
Set 方法将在运行时执行和编译时进行类似的可赋值性约束的检查。以上代码,变量和值都是 int 类型,但是如果变量是 int64 类型,那么程序将抛出一个 panic 异常,所以关键问题是要确保改类型的变量可以接受对应的值:
Go
d.Set(reflect.ValueOf(int64(5))) // panic: int64 is not assignable to int同样,对一个不可取地址的 reflect.Value 调用 Set 方法也会导致 panic 异常:
Go
x := 2
b := reflect.ValueOf(x)
b.Set(reflect.ValueOf(3)) // panic: Set using unaddressable value1
2
3
2
3
这里有很多用于基本数据类型的 Set 方法:SetInt、SetUint、SetString 和 SetFloat 等。
Go
d := reflect.ValueOf(&x).Elem()
d.SetInt(3)
fmt.Println(x) // "3"1
2
3
2
3
从某种程度上说,这些 Set 方法总是尽可能地完成任务。以 SetInt 为例,只要变量是某种类型的有符号整数就可以工作,即使是一些命名的类型、甚至只要底层数据类型是有符号整数就可以,而且如果对于变量类型值太大的话会被自动截断。但需要谨慎的是:对于一个引用 interface{} 类型的 reflect.Value 调用 SetInt 会导致 panic 异常,即使那个 interface{} 变量对于整数类型也不行。
Go
x := 1
rx := reflect.ValueOf(&x).Elem()
rx.SetInt(2) // OK, x = 2
rx.Set(reflect.ValueOf(3)) // OK, x = 3
rx.SetString("hello") // panic: string is not assignable to int
rx.Set(reflect.ValueOf("hello")) // panic: string is not assignable to int
var y interface{}
ry := reflect.ValueOf(&y).Elem()
ry.SetInt(2) // panic: SetInt called on interface Value
ry.Set(reflect.ValueOf(3)) // OK, y = int(3)
ry.SetString("hello") // panic: SetString called on interface Value
ry.Set(reflect.ValueOf("hello")) // OK, y = "hello"1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
当我们用 Display 显示 os.Stdout 结构时,我们发现反射可以越过 Go 语言的导出规则的限制读取结构体中未导出的成员,比如在类 Unix 系统上 os.File 结构体中的 fd int 成员。然而,利用反射机制并不能修改这些未导出的成员:
Go
stdout := reflect.ValueOf(os.Stdout).Elem() // *os.Stdout, an os.File var
fmt.Println(stdout.Type()) // "os.File"
fd := stdout.FieldByName("fd")
fmt.Println(fd.Int()) // "1"
fd.SetInt(2) // panic: unexported field1
2
3
4
5
2
3
4
5
一个可取地址的 reflect.Value 会记录一个结构体成员是否是未导出成员,如果是的话则拒绝修改操作。因此,CanAddr 方法并不能正确反映一个变量是否是可以被修改的。另一个相关的方法 CanSet 是用于检查对应的 reflect.Value 是否是可取地址并可被修改的:
Go
fmt.Println(fd.CanAddr(), fd.CanSet()) // "true false"3. 获取结构体字段标识
在本节,我们将看到如果通过反射机制获取成员标签。
对于一个 web 服务,大部分 HTTP 处理函数要做的第一件事情就是展开请求中的参数到本地变量中。我们定义了一个工具函数,叫 params.Unpack,通过使用结构体成员标签机制来让 HTTP 处理函数解析请求参数更方便。
首先,我们看看如何使用它。下面的 search 函数是一个 HTTP 请求处理函数。它定义了一个匿名结构体类型的变量,用结构体的每个成员表示 HTTP 请求的参数。其中结构体成员标签指明了对于请求参数的名字,为了减少 URL 的长度这些参数名通常都是神秘的缩略词。Unpack 将请求参数填充到合适的结构体成员中,这样我们可以方便地通过合适的类型类来访问这些参数。
Go
import "gopl.io/ch12/params"
// search implements the /search URL endpoint.
func search(resp http.ResponseWriter, req *http.Request) {
var data struct {
Labels []string `http:"l"`
MaxResults int `http:"max"`
Exact bool `http:"x"`
}
data.MaxResults = 10 // set default
if err := params.Unpack(req, &data); err != nil {
http.Error(resp, err.Error(), http.StatusBadRequest) // 400
return
}
// ...rest of handler...
fmt.Fprintf(resp, "Search: %+v\n", data)
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
下面的 Unpack 函数主要完成三件事情。第一,它调用 req.ParseForm() 来解析 HTTP 请求。然后,req.Form 将包含所有的请求参数,不管 HTTP 客户端使用的是 GET 还是 POST 请求方法。
下一步,Unpack 函数将构建每个结构体成员有效参数名字到成员变量的映射。如果结构体成员有成员标签的话,有效参数名字可能和实际的成员名字不相同。reflect.Type 的 Field 方法将返回一个 reflect.StructField,里面含有每个成员的名字、类型和可选的成员标签等信息。其中成员标签信息对应 reflect.StructTag 类型的字符串,并且提供了 Get 方法用于解析和根据特定 key 提取的子串,例如这里的 http:"..." 形式的子串。
Go
// Unpack populates the fields of the struct pointed to by ptr
// from the HTTP request parameters in req.
func Unpack(req *http.Request, ptr interface{}) error {
if err := req.ParseForm(); err != nil {
return err
}
// Build map of fields keyed by effective name.
fields := make(map[string]reflect.Value)
v := reflect.ValueOf(ptr).Elem() // the struct variable
for i := 0; i < v.NumField(); i++ {
fieldInfo := v.Type().Field(i) // a reflect.StructField
tag := fieldInfo.Tag // a reflect.StructTag
name := tag.Get("http")
if name == "" {
name = strings.ToLower(fieldInfo.Name)
}
fields[name] = v.Field(i)
}
// Update struct field for each parameter in the request.
for name, values := range req.Form {
f := fields[name]
if !f.IsValid() {
continue // ignore unrecognized HTTP parameters
}
for _, value := range values {
if f.Kind() == reflect.Slice {
elem := reflect.New(f.Type().Elem()).Elem()
if err := populate(elem, value); err != nil {
return fmt.Errorf("%s: %v", name, err)
}
f.Set(reflect.Append(f, elem))
} else {
if err := populate(f, value); err != nil {
return fmt.Errorf("%s: %v", name, err)
}
}
}
}
return nil
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
最后,Unpack 遍历 HTTP 请求的 name/value 参数键值对,并且根据更新相应的结构体成员。回想一下,同一个名字的参数可能出现多次。如果发生这种情况,并且对应的结构体成员是一个 slice,那么就将所有的参数添加到 slice 中。其它情况,对应的成员值将被覆盖,只有最后一次出现的参数值才是起作用的。
populate 函数小心用请求的字符串类型参数值来填充单一的成员 v(或者是 slice 类型成员中的单一的元素)。目前,它仅支持字符串、有符号整数和布尔型。其中其它的类型可以当作练习任务。
Go
func populate(v reflect.Value, value string) error {
switch v.Kind() {
case reflect.String:
v.SetString(value)
case reflect.Int:
i, err := strconv.ParseInt(value, 10, 64)
if err != nil {
return err
}
v.SetInt(i)
case reflect.Bool:
b, err := strconv.ParseBool(value)
if err != nil {
return err
}
v.SetBool(b)
default:
return fmt.Errorf("unsupported kind %s", v.Type())
}
return nil
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
如果我们上上面的处理程序添加到一个 web 服务器,则可以产生以下的会话:
Bash
$ go build gopl.io/ch12/search
$ ./search &
$ ./fetch 'http://localhost:12345/search'
Search: {Labels:[] MaxResults:10 Exact:false}
$ ./fetch 'http://localhost:12345/search?l=golang&l=programming'
Search: {Labels:[golang programming] MaxResults:10 Exact:false}
$ ./fetch 'http://localhost:12345/search?l=golang&l=programming&max=100'
Search: {Labels:[golang programming] MaxResults:100 Exact:false}
$ ./fetch 'http://localhost:12345/search?x=true&l=golang&l=programming'
Search: {Labels:[golang programming] MaxResults:10 Exact:true}
$ ./fetch 'http://localhost:12345/search?q=hello&x=123'
x: strconv.ParseBool: parsing "123": invalid syntax
$ ./fetch 'http://localhost:12345/search?q=hello&max=lots'
max: strconv.ParseInt: parsing "lots": invalid syntax1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
4. 显示一个类型的方法集
我们的最后一个例子是使用 reflect.Type 来打印任意值的类型和枚举它的方法:
Go
// Print prints the method set of the value x.
func Print(x interface{}) {
v := reflect.ValueOf(x)
t := v.Type()
fmt.Printf("type %s\n", t)
for i := 0; i < v.NumMethod(); i++ {
methType := v.Method(i).Type()
fmt.Printf("func (%s) %s%s\n", t, t.Method(i).Name,
strings.TrimPrefix(methType.String(), "func"))
}
}1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
reflect.Type 和 reflect.Value 都提供了一个 Method 方法:
- 调用
t.Method(i)将返回一个reflect.Method的实例,对应一个用于描述一个方法的名称和类型的结构体; - 调用
v.Method(i)将返回一个reflect.Value表示一个方法值,也就是一个绑定到该接收者的方法。使用reflect.Value.Call可以调用该方法(我们这里没有演示);
这是属于 time.Duration 和 *strings.Replacer 两个类型的方法:
Go
methods.Print(time.Hour)
// Output:
// type time.Duration
// func (time.Duration) Hours() float64
// func (time.Duration) Minutes() float64
// func (time.Duration) Nanoseconds() int64
// func (time.Duration) Seconds() float64
// func (time.Duration) String() string
methods.Print(new(strings.Replacer))
// Output:
// type *strings.Replacer
// func (*strings.Replacer) Replace(string) string
// func (*strings.Replacer) WriteString(io.Writer, string) (int, error)1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14