首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >【初识Go】| Day11 反射机制

【初识Go】| Day11 反射机制

作者头像
yussuy
修改于 2020-12-25 02:05:15
修改于 2020-12-25 02:05:15
4880
举报

反射是什么

反射的概念是由Smith在1982年首次提出的,主要是指程序可以访问、检测和修改它本身状态或行为的一种能力。

有时候我们需要编写一个函数能够处理一类并不满足普通公共接口的类型的值,也可能是因为它们并没有确定的表示方式,或者是在我们设计该函数的时候这些类型可能还不存在。

Go 语言提供了一种机制在运行时更新变量和检查它们的值、调用它们的方法,但是在编译时并不知道这些变量的具体类型,这称为反射机制。

反射的作用

1.在编写不定传参类型函数的时候,或传入类型过多时

典型应用是对象关系映射

代码语言:txt
AI代码解释
复制
type User struct {
  gorm.Model
  Name         string
  Age          sql.NullInt64
  Birthday     *time.Time
  Email        string  `gorm:"type:varchar(100);unique_index"`
  Role         string  `gorm:"size:255"` // set field size to 255
  MemberNumber *string `gorm:"unique;not null"` // set member number to unique and not null
  Num          int     `gorm:"AUTO_INCREMENT"` // set num to auto incrementable
  Address      string  `gorm:"index:addr"` // create index with name `addr` for address
  IgnoreMe     int     `gorm:"-"` // ignore this field
}

var users []User
db.Find(&users)

2.不确定调用哪个函数,需要根据某些条件来动态执行

代码语言:txt
AI代码解释
复制
func bridge(funcPtr interface{}, args ...interface{})

第一个参数funcPtr以接口的形式传入函数指针,函数参数args以可变参数的形式传入,bridge函数中可以用反射来动态执行funcPtr函数。

反射的实现

Go的反射基础是接口和类型系统,Go的反射机制是通过接口来进行的。

反射是由 reflect 包提供的。 它定义了两个重要的类型, Type 和 Value. 一个 Type 表示一个Go类型. 它是一个接口, 有许多方法来区分类型以及检查它们的组成部分, 例如一个结构体的成员或一个函数的参数等. 唯一能反映 reflect.Type 实现的是接口的类型描述信息, 也正是这个实体标识了接口值的动态类型.

Go 语言在 reflect 包里定义了各种类型,实现了反射的各种函数,通过它们可以在运行时检测类型的信息、改变类型的值。

反射三定律

1.反射可以将“接口类型变量”转换为“反射类型对象”。

反射提供一种机制,允许程序在运行时访问接口内的数据。首先介绍一下reflect包里的两个方法reflect.Valuereflect.Type

代码语言:txt
AI代码解释
复制
package main

import (
	"fmt"
	"reflect"
)

func main() {
	var Num float64 = 3.14

	v := reflect.ValueOf(Num)
	t := reflect.TypeOf(Num)

	fmt.Println("Reflect : Num.Value = ", v)
	fmt.Println("Reflect : Num.Type  = ", t)
}

返回

代码语言:txt
AI代码解释
复制
Reflect : Num.Value =  3.14
Reflect : Num.Type  =  float64

上面的例子通过reflect.ValueOf和reflect.TypeOf将接口类型变量分别转换为反射类型对象v和t,v是Num的值,t也是Num的类型。

先来看一下reflect.ValueOf和reflect.TypeOf的函数签名

代码语言:txt
AI代码解释
复制
func TypeOf(i interface{}) Type
func (v Value) Interface() (i interface{})

两个方法的参数类型都是空接口

在整个过程中,当我们调用reflect.TypeOf(x)的时候,

当我们调用reflect.TypeOf(x)的时候,Num会被存储在这个空接口中,然后reflect.TypeOf再对空接口进行拆解,将接口类型变量转换为反射类型变量

2.反射可以将“反射类型对象”转换为“接口类型变量”。

定律2是定律1的反过程。

根据一个 reflect.Value 类型的变量,我们可以使用 Interface 方法恢复其接口类型的值。

代码语言:txt
AI代码解释
复制
package main
import (
    "fmt"
    "reflect"
)
func main() {
    var Num = 3.14
    v := reflect.ValueOf(Num)
    t := reflect.TypeOf(Num)
    fmt.Println(v)
    fmt.Println(t)

    origin := v.Interface().(float64)
    fmt.Println(origin)
}

返回

代码语言:txt
AI代码解释
复制
3.14
float64
3.14

3.如果要修改“反射类型对象”,其值必须是“可写的”。

运行一下程序:

代码语言:txt
AI代码解释
复制
package main
import (
    "reflect"
)
func main() {
        var Num float64 = 3.14
        v := reflect.ValueOf(Num)
        v.SetFloat(6.18)
}

出现panic了

代码语言:txt
AI代码解释
复制
panic: reflect: reflect.Value.SetFloat using unaddressable value

goroutine 1 [running]:
reflect.flag.mustBeAssignableSlow(0x8e)
	/usr/local/go/src/reflect/value.go:259 +0x138
reflect.flag.mustBeAssignable(...)
	/usr/local/go/src/reflect/value.go:246
reflect.Value.SetFloat(0x488ec0, 0xc00001a0b8, 0x8e, 0x4018b851eb851eb8)
	/usr/local/go/src/reflect/value.go:1609 +0x37
main.main()
	/home/ricardo/error.go:8 +0xb3
exit status 2

因为反射对象v包含的是副本值,所以无法修改。

我们可以通过CanSet函数来判断反射对象是否可以修改,如下:

代码语言:txt
AI代码解释
复制
package main
import (
    "fmt"
    "reflect"
)
func main() {
    var Num float64 = 3.14
    v := reflect.ValueOf(Num)
    fmt.Println("v的可写性:", v.CanSet())
}

小结

1.反射对象包含了接口变量中存储的值以及类型。

2.如果反射对象中包含的值是原始值,那么可以通过反射对象修改原始值;

3.如果反射对象中包含的值不是原始值(反射对象包含的是副本值或指向原始值的地址),则该反射对象不可以修改。

反射的实践

通过反射修改内容

代码语言:txt
AI代码解释
复制
var f float64 = 3.41
fmt.Println(f)
p := reflect.ValueOf(&f)
v := p.Elem()
v.SetFloat(6.18)
fmt.Println(f)

reflect.Elem() 方法获取这个指针指向的元素类型。这个获取过程被称为取元素,等效于对指针类型变量做了一个*操作

通过反射调用方法

代码语言:txt
AI代码解释
复制
package main

import (
        "fmt"
        "reflect"
)

func hello() {
  fmt.Println("Hello world!")
}

func main() {
  hl := hello
  fv := reflect.ValueOf(hl)
  fv.Call(nil)
}

反射会使得代码执行效率较慢,原因有

1.涉及到内存分配以及后续的垃圾回收

2.reflect实现里面有大量的枚举,也就是for循环,比如类型之类的

建议

虽然反射提供的API远多于我们讲到的,我们前面的例子主要是给出了一个方向,通过反射可以实现哪些功能。反射是一个强大并富有表达力的工具,但是它应该被小心地使用,原因有三。

第一个原因是,基于反射的代码是比较脆弱的。对于每一个会导致编译器报告类型错误的问题,在反射中都有与之相对应的误用问题,不同的是编译器会在构建时马上报告错误,而反射则是在真正运行到的时候才会抛出panic异常,可能是写完代码很久之后了,而且程序也可能运行了很长的时间。

以前面的readList函数(§12.6)为例,为了从输入读取字符串并填充int类型的变量而调用的reflect.Value.SetString方法可能导致panic异常。绝大多数使用反射的程序都有类似的风险,需要非常小心地检查每个reflect.Value的对于值的类型、是否可取地址,还有是否可以被修改等。

避免这种因反射而导致的脆弱性的问题的最好方法是将所有的反射相关的使用控制在包的内部,如果可能的话避免在包的API中直接暴露reflect.Value类型,这样可以限制一些非法输入。如果无法做到这一点,在每个有风险的操作前指向额外的类型检查。以标准库中的代码为例,当fmt.Printf收到一个非法的操作数是,它并不会抛出panic异常,而是打印相关的错误信息。程序虽然还有BUG,但是会更加容易诊断。

代码语言:txt
AI代码解释
复制
fmt.Printf("%d %s\n", "hello", 42) // "%!d(string=hello) %!s(int=42)"

反射同样降低了程序的安全性,还影响了自动化重构和分析工具的准确性,因为它们无法识别运行时才能确认的类型信息。

避免使用反射的第二个原因是,即使对应类型提供了相同文档,但是反射的操作不能做静态类型检查,而且大量反射的代码通常难以理解。总是需要小心翼翼地为每个导出的类型和其它接受interface{}或reflect.Value类型参数的函数维护说明文档。

第三个原因,基于反射的代码通常比正常的代码运行速度慢一到两个数量级。对于一个典型的项目,大部分函数的性能和程序的整体性能关系不大,所以使用反射可能会使程序更加清晰。测试是一个特别适合使用反射的场景,因为每个测试的数据集都很小。但是对于性能关键路径的函数,最好避免使用反射。

参考资料

https://github.com/datawhalechina/go-talent/blob/master/10.%E5%8F%8D%E5%B0%84%E6%9C%BA%E5%88%B6.md

http://shouce.jb51.net/gopl-zh/ch12/ch12-02.html

本文系转载,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文系转载,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
微信jssdk开发,PHP,必要步骤
一般说明步骤一:微信jssdk使用必须在微信公众平台进入其公众号设置,打开配置安全域名才可以。 安全域名则是请求调用微信接口的安全域名,非域名下则会出现权限错误,未授权域名等。
1_bit
2020/10/23
2.9K0
微信JS-SDK实现自定义分享功能,分享给朋友,分享到朋友圈「建议收藏」
设置js 安全域名在 设置–>公众号设置–>功能设置里边 appid appSercret 在开发–>基本配置里
全栈程序员站长
2022/08/27
2.9K0
vue + 微信二次分享/自定义分享
如上图,如果不做相关处理,页面进行二次分享,用户看到的就是链接+空图,上面显示的文案(考拉阅读)实际上是获取的title标签中的文案,我在网上查的相关例子有说明,图片如果不设置,将会自动获取浏览器渲染的第一张图片,经过个人测试,并没有实现(朋友圈同理,不做图片展示)。
super.x
2019/04/12
3.8K1
vue + 微信二次分享/自定义分享
微信公众平台开发[2] —— 微信端分享功能
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u011415782/article/details/51870790
泥豆芽儿 MT
2018/09/11
5.8K0
微信公众平台开发[2] —— 微信端分享功能
微信JSSDK分享到朋友圈和朋友自定义内容功能实现
https://mp.weixin.qq.com/wiki?action=doc&id=mp1421141115&t=0.6433997488875112#gaishu 本Demo是基于之前几个例子写
小帅丶
2018/02/08
10.2K0
微信JSSDK分享到朋友圈和朋友自定义内容功能实现
C# 实现微信自定义分享
在实际的应用中,我们可能不是简单的将该网页的链接直接分享出去,而是生成符合实际需要的URL,微信称其为自定义分享。意思即,在用户点击“转发给朋友”按钮之前,进行URL等内容的更新 ,经过调整后,再把链接发送给要分享的朋友。微信给出的关键方法是:updateAppMessageShareData。
初九之潜龙勿用
2024/06/20
2410
C# 实现微信自定义分享
ThinkPHP3.2.3集成微信分享JS-SDK实践
在没有集成微信分享js-sdk前是这样的:没有摘要,缩略图任意抓取正文图片
思梦php
2018/02/09
3.8K0
ThinkPHP3.2.3集成微信分享JS-SDK实践
微信分享H5自定义标题描述和图片
前言 哎呀,为啥人家分享的H5页面这么绚丽,有头有尾有妹子,唯独自己的又老有丑又难啃,自己都看不下去,千万不要给领导看见。然而,最终还是领导发话了这个必须得改。 永远不要指望微信给的案例能让你迅速解决
小柒2012
2018/06/19
2.4K0
记录一次开发微信网页分享
最近在做一个项目需求,分享领好书活动,获取用户的个人信息以及unionID,并诱导用户分享给好友或朋友圈,达到裂变拉新的目的。在做的过程中遇到了一些坑的地方,所以回过来总结一下
super.x
2019/07/02
1.6K0
.Net微信网页开发之使用微信JS-SDK自定义微信分享内容
  关于JS-SDK的使用步骤和timestamp(时间戳),nonceStr(随机串),signature(签名),access_token(接口调用凭据)生成获取的详细说明在这里:https://www.cnblogs.com/Can-daydayup/p/11124092.html
追逐时光者
2019/08/28
11.9K0
.Net微信网页开发之使用微信JS-SDK自定义微信分享内容
原 微信授权和朋友圈分享
作者:汪娇娇 日期:2016.9.25 现在想想,微信这东西真是让人又爱又恨,刚接触的时候,简直毫无头绪,不过在后台的配合下,现在终于能八九不离十的将微信获取用户信息和分享朋友圈这两块弄得比较透彻,得
jojo
2018/05/03
4K0
原                                                                                微信授权和朋友圈分享
微信中页面二次分享小图标丢失问题
在我们有房APP1.1的版本中增加了房产资讯的功能,昨天晚上有同事在群里反馈从APP中分享的资讯到微信中,然后再次分享出去的时候标题和小图标不见了,见下图:
猿天地
2018/07/25
3.3K0
微信中页面二次分享小图标丢失问题
微信jssdk分享接口
通过appId 和appSecret 获取access_token, 再通过access_token来获取jsapi_ticket. 由于jsapi_ticket具有7200s的的时效性,所以之前先判断redis里是否又jsapi_ticket.若有则直接使用官方的实例代码中的sign方法进行加密,若没有则重新请求后加密,之后将上面图中的数据发给前端.
治电小白菜
2020/08/25
7K0
微信jssdk分享接口
微信H5分享到朋友圈,转发朋友功能随记[通俗易懂]
最近刚做了一个微信公众号H5项目,里面包含一个分享到朋友圈和分享给好友的功能。配置白名单以及公众号js安全域名这些就不赘述了,接下来简单介绍下实现这个功能的几个前端步骤
全栈程序员站长
2022/09/05
2.1K0
基于koa实现的微信JS-SDK调用Demo
微信JS-SDK权限验证的签名必须在服务器端实现,签名用的url必须是调用JS接口页面的完整URL,所以这里决定用koa来同时完成页面渲染及生成签名所需验证配置。 项目依赖库如下:
薛定喵君
2021/07/23
5.2K0
微信开发之自定义分享
这篇文章所写的demo都是基于easyWeChat SDK的 源码地址:点我>>>> 一、获取分享所需的配置参数 Test.php 获取分享配置的公共方法 // 生成JSSDK配置文件 public function jssdkConfig(Array $apiList, $apiUrl = '') { $apiUrl && $this->app->jssdk->setUrl($apiUrl); return $this->app->jssdk->buildC
日常嗑瓜子
2021/04/11
1.1K0
微信开发之自定义分享
微信公众号开发之如何使用JSSDK
使用JSSDK主要包括 1、判断当前客户端版本是否支持指定JS接口、 2、分享接口(微信认证) 3、图像接口 4、音频接口 5、智能接口(识别语音并返回结果) 6、设备信息(获取网络状态) 7、地理位置 8、界面操作 9、微信扫一扫 10、微信小店(服务号必须通过微信认证) 11、微信卡券 (微信认证) 12、微信支付(服务号必须通过微信认证)
Javen
2018/08/21
4.8K0
微信公众号开发之如何使用JSSDK
微信分享
微信分享,咋一看好像很复杂,实则非常简单。只需要调用微信官方出的微信jssdk,加上些许配置,就可以实现h5页面在微信上的分享,官方文档戳这里
grain先森
2019/03/28
5.4K0
微信分享等设置 -- 缩略图等
debug:测试各绑定事件传入的测试(PC端打开,控制台console能看到各传入参数),上线改为false appId:公众号的唯一标识,为了安全考虑,后端传过来 timestamp:签名时间戳,例如:1414587457 nonceStr:签名随机字符串,例如:Wm3WZYTPz0wzccnW signature:签名 -- 通过appId请求到access_token,然后通过access_token请求到jsapi_ticket,通过jsapi_ticket、timestamp、nonceStr、url用sha1()加密生成signature; 为了安全考虑,这四个参数都由后台请求或者生成,然后前端请求使用。参考:微信公众平台JS-SDK说明文档附录1-JS-SDK使用权限签名算法
Rattenking
2021/01/29
1.3K0
微信分享功能_微信分享链接点开是图片
微信app右上角自带分享功能–不论是微信公众号还是微信小程序或者是用微信打开的别的链接,用户都可以进行微信分享出去,对于自定义微信分享功能会和默认分享存在一些样式区别。这就是为什么还要自定义微信分享功能。
全栈程序员站长
2022/09/20
4.5K0
微信分享功能_微信分享链接点开是图片
推荐阅读
相关推荐
微信jssdk开发,PHP,必要步骤
更多 >
交个朋友
加入HAI高性能应用服务器交流群
探索HAI应用新境界 共享实践心得
加入架构与运维学习入门群
系统架构设计入门 运维体系构建指南
加入架构与运维工作实战群
高并发系统设计 运维自动化实践
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档