golang 反射(reflect)

反射是现代程序必备的元素,用于在 运行时 获取程序元素,如对象等的 元数据,实现动态识别类型及其结构,以及相关的语义信息。

反射在程序中应用非常多,例如:

  • 动态生成数据:json 序列化/反序列化; orm 映射, proxy 透明代理对象
  • 动态调用方法:plugin 实现
  • 框架自动处理程序:annotation tag 注解标签
  • 其他需要数据元数据的应用

在必要的场合,灵活应用反射,是中高级程序员能力的评价标准之一。灵活应用的根本是加深对 go 语言编译与实现的理解,并阅读典型应用案例。

滥用反射,也是低中级程序员最常见的问题,造成程序效率底下、不确定性错误增多。

一、Go 中的反射

go 是静态语言,表示内存中任何一个数据对象(data object)的值及其类型必须是编译期可确定的。因此,go 应用运行时不会像 java 等动态语言一样,在运行期维护所有对象的元数据,以支持多态等需要。也不像 c 语言,不提供任何元数据支持。 但注定 go 语言的反射是简单和有限的。

大神文章,必读!必读!必读!在短短的文章中,说明了 go 语言反射的要点!

$go tool tour

这里,仅提示其中要点:

interface{}接口对象.(断言类型)TypeOf(i interface{})ValueOf(i interface{})Zero,NewAt,MakeSlice...vv.Interface()v.Interface()vv.Elem()v.NumField()v.Field(i int)v.FieldByXXX(...)
package main

import "fmt"
import "reflect"

type T struct {
    A int
    B string
}

func (t *T) SetA(i int) {
    t.A = i
}

func main() {

    t := T{23, "skidoo"}
    s := reflect.ValueOf(&t).Elem()
    typeOfT := s.Type()
    for i := 0; i < s.NumField(); i++ {
        f := s.Field(i)
        fmt.Printf("%d: %s %s = %v\n", i,
        typeOfT.Field(i).Name, f.Type(), f.Interface())
    }
    typePT := reflect.TypeOf(&t)
    fmt.Printf("%d\n",typePT.NumMethod())
    for i := 0; i < typePT.NumMethod(); i++ {
        m := typePT.Method(i)
        fmt.Printf("%d: %s %v\n", m.Index,m.Name,m.Type)
    }
    s.Field(0).SetInt(77)
    s.Field(1).SetString("Sunset Strip")
    fmt.Println("t is now", t)

    //调用方法/函数
    m := typePT.Method(0)
    params := make([]reflect.Value,2) 
    params[0] = reflect.ValueOf(&t) 
    params[1] = reflect.ValueOf(5)
    m.Func.Call(params)
    fmt.Println("t is now", t)
}
   
   
  • 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

二、golang 获取包资源

程序中有许多资源,如配置文件、图片、网页等都是随着包提供。对于 windows 程序或 java 程序都有 ResourceLoad 函数读取运行程序(exe,dll,jar)中的资源。go 语言一般都源代码提供,因此资源都是直接放置在包目录下,而不打包。

go 包 为你提供了按需管理程序资源的能力。其中,go/build 子包 是管理包以及应用环境最重要的包。

var Default Context = defaultContext()
   
   
  • 1

Context 包含了程序构建工作区、版本等重要信息。

go tour 的源代码,local.go 的 findRoot 函数提供查询教学资源目录的案例!

三、反射练习

设计一个简单 ORMEngin 对象,使它完成以下任务:

数据库表

CREATE TABLE `userinfo` (
    `uid` INT(10) NOT NULL AUTO_INCREMENT,
    `username` VARCHAR(64) NULL DEFAULT NULL,
    `departname` VARCHAR(64) NULL DEFAULT NULL,
    `created` DATE NULL DEFAULT NULL,
    PRIMARY KEY (`uid`)
);
   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

1、orm 规则

我们在field对应的Tag中对Column的一些属性进行定义,例如:

// UserInfo .
type UserInfo struct {
    UID        int   `orm:"id,auto-inc,type=INT(10)"` //语义标签
    UserName   string
    DepartName string
    CreateAt   *time.Time `orm:"name=created" json:",omitempty"`
} 
   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

在 orm 标签中,用“,”号作为属性的分割,每个属性为“key=value”。如果只有key,表示它是 Bool 属性,默认是 true。例如:id 表示这个字段是关键字。 更多字段属性可参考 Column属性定义 ,也可以用自己定义的规则和 key。

2、实现自动插入数据

用户的样例代码:

user := UserInfo{...}
affected, err := engine.Insert(user)
// INSERT INTO user (name) values (?)
   
   
  • 1
  • 2
  • 3
Insert(o interface{})

3、实现查询结果自动映射

用户的样例代码:

pEveryOne := make([]*Userinfo, 0)
err := engine.Find(&pEveryOne)
// SELECT `col-name`,`col-name` ... FROM UserInfo
   
   
  • 1
  • 2
  • 3

要求利用反射技术,根据输入数据的类型自动生成查询 sql 语句,并将结果集合根据数据类型自动映射到对象,并加入结果表。

提示

reflect.New(t)
package main

import (
    "fmt"
    _ "github.com/lib/pq"
    "database/sql"
)

func main() {

    db, _ := sql.Open(
        "postgres",
        "user=postgres dbname=go_testing password=pass sslmode=disable")

    rows, _ := db.Query("SELECT * FROM _user;")

    columns, _ := rows.Columns()
    count := len(columns)
    values := make([]interface{}, count)
    valuePtrs := make([]interface{}, count)

    for rows.Next() {

        for i, _ := range columns {
            valuePtrs[i] = &values[i]
        }

        rows.Scan(valuePtrs...)

        for i, col := range columns {
            var v interface{}
            val := values[i]
            b, ok := val.([]byte)

            if (ok) {
                v = string(b)
            } else {
                v = val
            }

            fmt.Println(col, 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
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44