фактическиTypeа такжеValueСуть в том, что это всего лишь инкапсуляция базовых данных Golang, фактически основанная на iface и eface для разработки на уровне программирования, потому что эти два объекта являются черными ящиками для разработчиков.
Почему я беру большеFieldа такжеMethodПо сути, чтобы отразить важность этих двух факторов, он также сосредоточится на
Несколько моментов отражения: 1. Безопасное использование отражения 2. полагаться на
TypeМожет генерировать данные типа, ядро лежит вNewа такжеSetСпособ 3. ПониманиеTypeа такжеValueПрименение 4. МастерствоCallфункция для безопасной работы 5. Поймите использование API и освойте его 6. Научитесь использовать отражениеIOCдействовать
Type
Тип также является метаинформацией, аналогично объекту Class в Java, вы можете делать практически все с Type, включая информацию о структуре, информацию о полях, информацию о методах и т. д., так что это ключевой момент. Java имеет некоторые функции в Go. Отличие заключается в динамической модификации и внедрении байт-кода, части компилируемого языка, которая должна отсутствовать, потому что так называемые типы определяются до запуска программы и не могут быть изменены.Эта часть является наиболее важной.
Что нужно добавить, так это то, что при использовании пакета отражения вам нужно обратить внимание на точку границы, на которую необходимо обратить внимание, и это также самая основная проблема, потому что некоторые вызовы методов являются условными.
Reflect.Typeof() может передать нулевой указатель при его получении, если нулевой указатель имеет тип!
структура
Основные проблемы и границы, официальные комментарии очень подробные, если вы хотите использовать, какой метод вам нужно, чтобы увидеть границы использования
type Type interface {
// Methods applicable to all types.,对应着unsafe的align,是一个计算大小的方法
Align() int
// 字段大小,必须是type.kind=结构体类型
FieldAlign() int
// It panics if i is not in the range [0, NumMethod()).
// 很重要,区别于Value.Method()方法,后面会专门讲到Method结构,要求方法长度是[0,numM),也就是不限制类型
Method(int) Method
MethodByName(string) (Method, bool)
NumMethod() int
// Name returns the type's name within its package for a defined type.
// For other (non-defined) types it returns the empty string.
Name() string
PkgPath() string
// 大小,不需要care
Size() uintptr
// 类似于Java的ToString
String() string
// Kind returns the specific kind of this type.
// 很重要,边界多依靠kind进行区分,返回该对象类型,比如指针,切片,结构体。。。。
Kind() Kind
// 是否实现了某个接口
// Implements reports whether the type implements the interface type u.
Implements(u Type) bool
// AssignableTo reports whether a value of the type is assignable to type u.
AssignableTo(u Type) bool
// 是否能转换
// ConvertibleTo reports whether a value of the type is convertible to type u.
ConvertibleTo(u Type) bool
// Comparable reports whether values of this type are comparable.
Comparable() bool
// It panics if the type's Kind is not one of the
// sized or unsized Int, Uint, Float, or Complex kinds.
Bits() int
// ChanDir returns a channel type's direction.
// It panics if the type's Kind is not Chan.
ChanDir() ChanDir
// 首先得是一个fun,判断是不是可变参数
// IsVariadic panics if the type's Kind is not Func.
IsVariadic() bool
// Elem returns a type's element type.(记得interface讲过,有些时候会有包装类型)
// It panics if the type's Kind is not Array, Chan, Map, Ptr, or Slice.
Elem() Type
// 字段信息
// It panics if the type's Kind is not Struct.
// It panics if i is not in the range [0, NumField()).
Field(i int) StructField
// 嵌套,比如field(1)为结构体,进入这个结构体,就需要这个[1,1],就是这个结构体的字段一
FieldByIndex(index []int) StructField
FieldByName(name string) (StructField, bool)
// 回掉,filter
FieldByNameFunc(match func(string) bool) (StructField, bool)
// 函数的参数类型
// It panics if the type's Kind is not Func.
// It panics if i is not in the range [0, NumIn()).
In(i int) Type
// 返回map对象的key类型
// It panics if the type's Kind is not Map.
Key() Type
// 返回数组长度
// It panics if the type's Kind is not Array.
Len() int
// 返回结构体的字段数
// It panics if the type's Kind is not Struct.
NumField() int
// 函数的参数个数
// It panics if the type's Kind is not Func.
NumIn() int
// 函数的返回值个数
// It panics if the type's Kind is not Func.
NumOut() int
// 函数的输出类型
// It panics if the type's Kind is not Func.
// It panics if i is not in the range [0, NumOut()).
Out(i int) Type
common() *rtype
uncommon() *uncommonType
}
как пользоваться
Кратко представить:
Три тестовых объекта, которые будут использованы позже
type UserService interface {
Service(str string) string
}
type userService struct {
ServerName string
Info map[string]interface{}
List [10]int
}
func (*userService) Service(str string) string {
return "name"
}
Как использовать его безопасно
func TestUserServer(t *testing.T) {
var in = (*UserService)(nil) //1、接口
inter := reflect.TypeOf(in)
if inter.Kind() == reflect.Ptr { // 2、判断是不是指针,拿到内部的元素
inter = inter.Elem()
}
if inter.Kind() != reflect.Interface {
panic("this service not interface")
}
service := new(userService)
tp := reflect.TypeOf(service)
if tp.Kind() == reflect.Ptr {
method := tp.NumMethod() // 获取方法
for x := 0; x < method; x++ {
fmt.Printf("%+v\n", tp.Method(x))
}
if tp.Implements(inter) { // 判断是否实现了某个接口
fmt.Printf("%s implatement %s\n", tp, inter)
}
tp = tp.Elem()
}
if tp.Kind() == reflect.Struct { //
fieldN := tp.NumField()
for x := 0; x < fieldN; x++ { // 获取字段信息
fmt.Printf("%+v\n", tp.Field(x))
if tp.Field(x).Type.Kind() == reflect.Map { // 如果是map,可以获取key元素
fmt.Printf("FieldName: %s, key: %s.\n", tp.Field(x).Name, tp.Field(x).Type.Key().Kind())
}
if tp.Field(x).Type.Kind() == reflect.Array { // 如果是数组,可以获取长度信息
fmt.Printf("FieldName: %s, len: %d.\n", tp.Field(x).Name, tp.Field(x).Type.Len())
}
}
}
}
Value
структура
Можно сказать, что ценность является мостом между нашими данными и метаинформацией, но из-за моста ценность в основном заключается в понимании использования методов.
type Value struct {
// typ holds the type of the value represented by a Value.
typ *rtype // 可以理解为iface的 type
// Pointer-valued data or, if flagIndir is set, pointer to data.
// Valid when either flagIndir is set or typ.pointers() is true.
ptr unsafe.Pointer // 可以理解为iface 的 data
// flag holds metadata about the value.
flag
}
основная операция
Метод адреса
Можно понять, например, что наш объект не является указателем, теперь, если мы хотим указатель, нам нужно использовать
Addr(), собственно, это и означает, но следует отметить, что это должно бытьCanAddr()Его можно только конвертировать.Чтобы добавить,на самом деле надо играть по-моему. Честно говоря, это кажется бесполезным.
fmt.Println(reflect.ValueOf(&userService{}).CanAddr()) // 所有的 reflect.ValueOf()都不可以直接拿到addr()
fmt.Println(reflect.ValueOf(userService{}).CanAddr())
// addr 的作用
func TestAddr(t *testing.T) {
x := 2
d := reflect.ValueOf(&x)
value := d.Elem().Addr().Interface().(*int) // 可以直接转换为指针
*value = 1000
fmt.Println(x) // "3"
}
Установить метод
Этот метод более полезен, обратите внимание, что вам нужно использовать его при вызове
reflect.CanSet()Судя по тому, что я написал ниже, на самом деле неправильно.
func TestDemos(t *testing.T) {
x := 2
d := reflect.ValueOf(&x)
d.Elem().SetInt(1000)
fmt.Println(x) // 1000
}
Elem
Elem возвращает значение, которое содержит интерфейс v или на которое указывает указатель v.
В основном, чтобы вернуть то, что действительно содержит интерфейс, или место, на которое указывает указатель. Поэтому, когда вы используете его, лучше всего сделать суждение о типе.
func TestElem(t *testing.T) {
x := 2
d := reflect.ValueOf(&x)
if d.Kind() == reflect.Ptr {
d.Elem() // 调用elem 获取指针真正指向的对象
}
// 或者,可以调用这个方法安全的调用
d=reflect.Indirect(d)
}
Новое и установленное (очень важно)
Иногда мы получаем тип и хотим создать экземпляр объекта, как насчет того, чтобы использовать это, таких методов много, newslice, newarr и т. д., обратите внимание
reflect.New()Возвращаемый тип — это указатель на тип, например type=string, а сгенерированный объект — type=*string.При вызове Set он должен быть вызван первым
CanSet(), чтобы определить, может ли он быть установлен, в основном каждый объект Value не может быть CanSet при его инициализации.
func TestElem(t *testing.T) {
value := reflect.New(reflect.TypeOf("111")) // 初始化一个 string类型的value,但是需要注意的是初始化完成后是 *string,任何类型都是,New()方法调用完成后都会是一个指针指向原来的类型数据,也就是多了个*
fmt.Println(value.Kind()) // 因此这里输出的是 *string ,ptr
value = reflect.Indirect(value) // 获取真正的类型,string,
fmt.Println(value.Kind()) //
if value.CanSet() {
value.SetString("hello world") // set string,必须要求类型是string的,而且can set,
}
fmt.Println(value.Interface().(string)) // "hello world"
}
Обратите внимание один
reflect.New()Методы никогда не должны создавать новый тип указателя
В качестве примера возьмем инициализацию структуры:
// 错误写法
func main() {
// reflect.TypeOf(new(api.User)) 类型为 *api.User
// reflect.New(reflect.TypeOf(new(api.User))) 语意是:初始化一个x=(*api.User)(nil)数据,返回值为&x,所以最终的返回类型是**api.User,值为nil的数据
value := reflect.New(reflect.TypeOf(new(api.User)))
fmt.Println(value.String()) //<**api.User Value>
// value.Elem() 类型为*api.User
fmt.Printf("%+v", value.Elem().Interface().(*api.User)) // nil
}
// 正确做法
func main() {
// reflect.TypeOf(new(api.User)) 类型是 *api.User
// reflect.TypeOf(new(api.User)).Elem() 类型是 api.User
// reflect.New(reflect.TypeOf(new(api.User)).Elem()) 的含义是初始化一个api.User类型的数据,返回&api.User,所以最终类型是 *api.User
value := reflect.New(reflect.TypeOf(new(api.User)).Elem())
fmt.Println(value.String()) // <*api.User Value>
user := value.Interface().(*api.User) // 所以类型是 *api.User, 没毛病
user.Name = "tom" //
nuser := value.Elem().Interface().(api.User) // 拿到 api.User类型,设置一下试试
nuser.Age = 10
fmt.Printf("%+v", value.Interface().(*api.User)) //&{Name:tom Age:0} ,所以拿到指针数据就可以进行赋值修改了,但是切记不能拿struct类型进行修改,不然修改无效
}
В соответствии с приведенным выше примером, я надеюсь, что все могут понять опасность нового указателя, поэтому помните, что во время разработки не следует создавать новые данные типа = ptr.
Примечание два
value.Set()Когда метод устанавливает данные, тип значения не может быть типом указателя, хотя его можно использовать.value.CanSet()Об этом можно судить (на самом деле, в основном судят, когда поле можно установить), но ведь нам нужно получить значение для установки данных, а не обязательно поля.
// 错误写法
func main() {
value := reflect.New(reflect.TypeOf(new(api.User)).Elem()) // value 类型为 *api.User
if value.Kind()==reflect.Ptr { // 指针类型 没问题,我们就去设置一个指针类型的数据吧
value.Set(reflect.ValueOf(&api.User{})) // 设置一个 *api.User的数据,发现panic了
}
}
// panic: reflect: reflect.flag.mustBeAssignable using unaddressable value
// 正确写法
func main() {
value := reflect.New(reflect.TypeOf(new(api.User)).Elem())
if value.Kind() == reflect.Ptr {
value = value.Elem()
}
if value.CanSet() {
value.Set(reflect.ValueOf(api.User{}))
}
fmt.Printf("%+v",value.Interface().(api.User))
}
// 输出: {Name: Age:0}
Поэтому его часто используют для
type User struct {
Name string
Age int
birthday time.Time //这个不可见
}
func test() {
user := api.User{}
value := reflect.ValueOf(&user)
fmt.Println(value.String())
for value.Kind() == reflect.Ptr { // 是指针类型,就去拿到真正的类型
value = value.Elem()
}
if value.Kind() == reflect.Struct {// 如果是struct类型,就可以去拿字段
birthDay := value.FieldByName("birthday") // 这个显然不能set
if birthDay.CanSet() {
birthDay.Set(reflect.ValueOf(time.Now()))
}
name := value.FieldByName("Name") // 这个可以
if name.CanSet() {
name.Set(reflect.ValueOf("tom"))
}
}
fmt.Printf("%+v", user) // {Name:tom Age:0 birthday:{wall:0 ext:0 loc:<nil>}}
}
Другой момент: (как присвоить значение интерфейсу)
Как безопасно присвоить значение
// inter must a ptr
func SetInterface(dest interface{}, src interface{}) error {
if dest == nil || src == nil {
return nil
}
value := reflect.ValueOf(dest)
kind := value.Kind()
_, isExist := isNilKind[kind]
if isExist && value.IsNil() {
return errors.New("the dest is nil")
}
if kind != reflect.Ptr {
return errors.New("the dest must a ptr")
}
srcValue := reflect.ValueOf(src)
// 如果 src也是指针,那就取elem
if value.Type() == srcValue.Type() {
srcValue = srcValue.Elem()
}
elem := value.Elem()
if elem.CanSet() {
if elem.Type() == srcValue.Type() {
elem.Set(srcValue)
}
}
return nil
}
Звоните (очень важно)
Это вызов метода, похожего на Java
Method.Invoke(), На самом деле такой геймплей не рекомендуется. Мы знаем, что golang очень произволен для методов. Различные типы могут определять методы, поэтому основные языки RPC используют ограничения интерфейса.Methodинформация для получения типа. Позже я буду интерпретировать go-rpc, внутреннюю реализацию собственной структуры rpc.Разница в том,
reflect.ValueOf().Call()а такжеreflect.TypeOf().Method().Func.Call()Разница между двумя методами вызова заключается в том, что первому не нужно передавать получателя, а у второго первым параметром должен быть получатель.
func TestCall(t *testing.T) {
value := reflect.ValueOf(new(userService))
if value.NumMethod() > 0 {
fmt.Println(value.NumMethod()) // 1
method := value.MethodByName("Service")
fmt.Println(method.Kind()) // "func"
method.Call([]reflect.Value{reflect.ValueOf("hello world")}) // hello world
}
}
IsValid
// isValid 是判断一个value 是不是有值
func TestIsValid(t *testing.T) {
of := reflect.ValueOf(nil)
fmt.Println(of.IsValid()) // false
}
// IsValid reports whether v represents a value. 代表v表示一个值(nil显示不是)
// It returns false if v is the zero Value.(false 是 v是一个空)
IsNil
В основном для определения типа упаковки, является ли она объектом, инкапсулированным указателем, и является ли она пустой. Но легко из-за типа паники
// ok 没问题
func TestIsNil(t *testing.T) {
i := (*error)(nil)
value := reflect.ValueOf(i)
fmt.Println(value.IsValid()) // true ,显示不是空
fmt.Println(value.IsNil()) // true , 因为它确实是nil
}
// IsNil reports whether its argument v is nil. The argument must be
// a chan, func, interface, map, pointer, or slice value; if it is
// not, IsNil panics.
// 正确用法
func TestIsNil2(t *testing.T) {
var (
isNilKind = map[reflect.Kind]interface{}{
reflect.Chan: nil,
reflect.Func: nil,
reflect.Map: nil,
reflect.Ptr: nil,
reflect.UnsafePointer: nil,
reflect.Interface: nil,
reflect.Slice: nil,
}
)
value := reflect.ValueOf(1234)
_, isExist := isNilKind[value.Kind()]
if isExist {
fmt.Println(value.IsNil()) // 调用这个需要前置的判断条件,就是kind
}
}
Field
Метаинформация некоторых полей, таких как
tagинформация, имя поля, тип поля и т. д.
func TestDemos(t *testing.T) {
tp := reflect.TypeOf(new(userService))
if tp.Kind() == reflect.Ptr {
tp = tp.Elem()
}
if tp.Kind() != reflect.Struct {
t.Fatal("not support")
}
field, _ := tp.FieldByName("ServerName") // 不许是struct 类型
fmt.Printf("FieldTag json=%s\n",field.Tag.Get("json"))
fmt.Printf("FieldName=%s\n",field.Name)
}
Method
В первую очередь необходимо знать, какую информацию содержит Метод, особое внимание здесь следует обратить на Функцию.
reflect.MethodByName()Метод, получаемый данным типом метода, отличается.Первым параметром первого является вызов (то есть это), первым параметром второго является непосредственно первый параметр, а тип последнего -reflect.Value.
type Method struct {
Name string // 方法名
PkgPath string
Type Type // method type 方法类型
Func Value // func with receiver as first argument,以接收者为第一个参数,也就是调用者
Index int // index for Type.Method,第几个方法
}
Интерфейсно-ориентированная разработка
type MethodMeta struct {
obj reflect.Value // 调用者
Method reflect.Method// method信息
InType []reflect.Type // 输入类型
OutType [] reflect.Type// 接受类型
}
Получив его, что, если мы захотим вызвать этот метод
Объясните, почему передача параметров является интерфейсом. Преимущество интерфейса заключается в соглашении о типах. Любой тип в go может реализовывать методы, так что это головная боль для такого рода проблем. Реализация этой части основного фреймворка rpc в зависимости от уровня интерфейса.
func Proxy(service UserService) *MethodMeta {
val := reflect.TypeOf(service)
method, _ := val.MethodByName("Service") // 获取方法
meta := MethodMeta{}
meta.Method = method // 方法的原信息
tt := method.Type // 方法类型
{
in := tt.NumIn()
meta.InType = make([]reflect.Type, in)
for x := 1; x < in; x++ { // 0号元素是调用方,所以只需要记录参数,所以需要变动下
meta.InType[x-1] = tt.In(x)
}
}
{
in := tt.NumOut()
meta.OutType = make([]reflect.Type, in)
for x := 0; x < in; x++ {
meta.OutType[x] = tt.Out(x)
}
}
meta.obj = reflect.ValueOf(service)
return &meta
}
Demo
func BenchmarkCall(b *testing.B) {
b.SetParallelism(1)
proxy := Proxy(new(userService))
for i := 0; i < b.N; i++ {
value := reflect.New(proxy.InType[0]).Elem() // new 传入类型
if value.CanSet() {
value.SetString("11111")
}
call := proxy.Method.Func.Call([]reflect.Value{proxy.obj, value}) // 调用函数,回去返回类型
_ = call[0].Interface() // 获取真正的类型
}
}
// goos: darwin
// goarch: amd64
// pkg: go-src-demo/insafe/test
//BenchmarkCall-8 2672127 442 ns/op
//PASS
func BenchmarkInvoke(b *testing.B) {
b.SetParallelism(1)
proxy :=new(userService)
for i := 0; i < b.N; i++ {
proxy.Service("111")
}
}
// BenchmarkInvoke-8 1000000000 0.338 ns/op
Вы видите, сколько времени занимает вызов отражения? Это примерно в 10 000 раз выше эффективности зазора, а может быть и выше. Честно говоря, уровень ns приемлем.
Gob && Json сериализация
В настоящее время все основные платформы RPC используют настраиваемый механизм кодирования и декодирования, который реализуется за счет реализации интерфейса, такой как унифицированная реализация.Requestа такжеResponseИнтерфейс, обеспечивающий ввод кодека.
Кроме того, Go предоставляет множество механизмов кодирования и декодирования. Базовый пакет golang поддерживает кодирование и декодирование в форме Value, аналогично Java.SerializableВстроенная сериализация интерфейсов, не подходит для кросс-языка.
Но Json, xml и т. д. можно использовать на разных языках.
гоб-кодек
func TestGob(t *testing.T) {
user := User{
Name: "tom",
Age: 1,
}
// 编码,这个编码不是普通的json编码,而是具有特殊含义的
buffer := bytes.Buffer{}
err := gob.NewEncoder(&buffer).Encode(user) // 编码
if err != nil {
t.Fatal(err)
}
// 解码,也是,支持使用Value的方式解码
of := reflect.TypeOf(new(User))
var value reflect.Value
if of.Kind() == reflect.Ptr {
value = reflect.New(of.Elem()) // 反射实例化一个对象
}
err = gob.NewDecoder(&buffer).DecodeValue(value)
if err != nil {
t.Fatal(err)
}
fmt.Println(value.Interface().(*User)) // 成功解码
}
json-кодек
func TestJson(t *testing.T) {
user := User{
Name: "tom",
Age: 1,
}
buffer := bytes.Buffer{}
err := json.NewEncoder(&buffer).Encode(user) //json编码
if err != nil {
t.Fatal(err)
}
of := reflect.TypeOf(new(User))
var value reflect.Value
if of.Kind() == reflect.Ptr {
value = reflect.New(of.Elem())
}
err = json.NewDecoder(&buffer).Decode(value.Interface()) //json解码
if err != nil {
t.Fatal(err)
}
fmt.Println(value.Interface().(*User)) //打印数据
}
Эффективность, json намного быстрее, чем gob
BenchmarkName-8 530700 1984 ns/op //json
BenchmarkName-8 44244 26257 ns/op //gob
MakeFunc
reflect.MakeFuncЭта функция запоминает, что первый тип Type должен быть Func, а во-вторых, этой Func нужно только имя метода, а также входящий и исходящий типы метода.
type Model interface {
TableName() string
}
func TestEcho(t *testing.T) {
fun := (*Model)(nil)
tp := reflect.TypeOf(fun).Elem()
if tp.NumMethod() > 0 {
method, _ := tp.MethodByName("TableName")
if method.Type.Kind() == reflect.Func {
makeFunc := reflect.MakeFunc(method.Type, func(args []reflect.Value) (results []reflect.Value) {
return []reflect.Value{reflect.ValueOf("student")}
})
fmt.Println(makeFunc.Call([]reflect.Value{}))
}
}
}
Честно говоря, эта штука бесполезна, ей никто не пользуется, во-первых, нет эффективного вызова метода, а во-вторых, она толком ничего не умеет.