Go-14-反射、unsafe 与元数据编程
本节目标:理解 Go 的反射机制,能写通用序列化、ORM 风格的代码,知道什么时候该用、什么时候不该用。
1. 反射的三大定律
Rob Pike 总结的反射三定律:
- 反射从接口值到反射对象(reflection object)
- 反射从反射对象到接口值
- 要修改反射对象,其值必须是可设置的(addressable)
2. reflect.Type 和 reflect.Value
1 | import "reflect" |
Type 和 Value 的方法:
1 | t.Name() // 类型名("float64") |
3. 反射的”可设置”限制
反射对象必须是可寻址的(addressable),才能被修改:
1 | x := 3.14 |
这正是为什么 ORM 框架需要传指针:
1 | func save(v interface{}) error { |
4. 反射遍历结构体
1 | type User struct { |
5. 反射调用方法
1 | type Calculator struct{ Base int } |
限制:Call 接收 []reflect.Value,每个参数都要用 reflect.ValueOf 包装;返回值也是 []reflect.Value。
6. 反射实现通用设置
1 | func setField(obj interface{}, name string, value interface{}) error { |
7. 实战:简易 ORM
1 | type Model struct { |
生产用 ORM:GORM、sqlx、ent。自己用反射写的简单版够学,但生产项目用成熟库。
8. unsafe 包
unsafe 绕过 Go 的类型安全,慎用:
1 | import "unsafe" |
常见用途:
- 高性能序列化/反序列化(如
gogo protobuf) - 绕过拷贝(
bytes.Buffer优化) - CGO 数据传递
不要用 unsafe 的场景:
- 业务逻辑
- 性能瓶颈在算法时(先优化算法)
- 任何你能用普通 Go 写出来的场景
9. runtime 包
1 | import "runtime" |
10. 反射的性能代价
反射比直接调用慢 10-100 倍:
1 | // 直接调用:~5ns |
优化策略:
- 缓存反射结果:第一次反射,缓存 Type/Value 到 map
- 代码生成代替反射:
easyjson、protoc-gen-go、mockgen - 避免热路径用反射
11. 反射的替代方案
| 场景 | 反射 | 替代 |
|---|---|---|
| JSON 序列化 | 标准库用反射 | jsoniter(JIT 优化)/easyjson(代码生成) |
| ORM | 反射查询字段 | 代码生成(gorm/gen、ent) |
| Mock | 反射 | gomock、mockery(代码生成) |
| 依赖注入 | 反射 | wire(代码生成)/ fx(运行时 + 反射) |
12. 何时用反射
✅ 适合:
- 框架代码(你不知道用户会传什么类型)
- 通用序列化/反序列化
- 配置文件解析(map[string]interface{})
- 单元测试的 deep equal
❌ 不适合:
- 业务代码
- 性能敏感的路径
- 能用接口解决的地方
小结
| 工具 | 用途 |
|---|---|
reflect.TypeOf |
获取类型元信息 |
reflect.ValueOf |
获取值信息 |
reflect.New(t) |
动态创建新值 |
unsafe.Pointer |
绕过类型系统 |
runtime |
运行时信息 |
黄金法则:
- 不要为了”酷”用反射
- 先尝试用接口解决问题
- 热路径用代码生成而不是反射
- 必须用反射时,缓存结果
下一节(最后一节)讲 Go 惯用法、模式和工具链。
- 本文作者: CoderSong
- 本文链接: https://jack-song-gif.github.io/2026/08/19/Go-14-反射与元数据编程/
- 版权声明: 本博客所有文章除特别声明外,均采用 MIT 许可协议。转载请注明出处!