
本文介绍在 go 中利用反射机制,从包含嵌入指针(如 *a)的结构体(如 b)出发,安全获取并操作其底层结构体 a 的字段,重点解决“如何穿透指针嵌入层访问被嵌入类型的公开字段”这一典型反射需求。
在 Go 反射中,当结构体嵌入的是指针类型(例如 *A),直接调用 reflect.Value.FieldByName() 并不能自动解引用——它仅按字段名查找当前层级的导出字段,而 *A 本身是一个指针字段,其值是 reflect.Value 类型的指针对象。要访问 A 的字段(如 Field_1),必须先解引用该嵌入指针,再进入其指向的结构体。
核心步骤如下:
- 获取目标值的 reflect.Value:对结构体实例(如 b)取地址后传入 reflect.ValueOf(),再调用 .Elem() 获取其值(避免操作指针本身);
- 定位嵌入指针字段:使用 .FieldByName("A")(注意:嵌入指针字段名默认为类型名 A,除非显式重命名);
- 解引用指针:调用 .Elem() 将 *A 的 Value 转为 A 的 Value;
- 访问目标字段:在解引用后的 Value 上调用 .FieldByName("Field_1") 即可读写。
以下为完整可运行示例:
package main
import (
"fmt"
"reflect"
)
type A struct {
Field_1 string
}
type B struct {
*A // 嵌入 *A,字段名隐式为 "A"
}
func getEmbeddedStructFields(v reflect.Value, embeddedTypeName string) (reflect.Value, bool) {
field := v.FieldByName(embeddedTypeName)
if !field.IsValid() || field.Kind() != reflect.Ptr {
return reflect.Value{}, false
}
if field.IsNil() {
return reflect.Value{}, false
}
elem := field.Elem()
if !elem.IsValid() || elem.Kind() != reflect.Struct {
return reflect.Value{}, false
}
return elem, true
}
func main() {
b := B{A: &A{"initial"}}
fmt.Println("Initial value:", *b.A) // {initial}
v := reflect.ValueOf(&b).Elem()
// 安全获取嵌入的 *A 并解引用
aVal, ok := getEmbeddedStructFields(v, "A")
if !ok {
panic("failed to get embedded *A")
}
// 访问 A 的字段
f1 := aVal.FieldByName("Field_1")
if !f1.IsValid() || !f1.CanInterface() {
panic("Field_1 is not accessible")
}
fmt.Println("Field_1 through reflection:", f1.String()) // "initial"
// 修改字段值(需可寻址且可设置)
if f1.CanSet() {
f1.SetString("works")
}
fmt.Println("After modified through reflection:", *b.A) // {works}
}⚠️ 注意事项:
- 嵌入指针字段名默认为类型名(如 *A → 字段名 "A"),若显式命名(如 MyA *A),则需用 "MyA" 查找;
- field.Elem() 前必须确保 field 非空(!field.IsNil()),否则 panic;
- 只有导出字段(首字母大写)才能被反射访问和修改;
- 若要修改字段,原始变量必须是可寻址的(即传入 &b 而非 b),且目标字段需满足 CanSet() 条件。
掌握这一模式,即可灵活处理多层嵌入、指针嵌入等复杂结构体反射场景,是构建通用序列化器、校验器或 ORM 映射工具的关键基础。推荐阅读官方经典教程:The Laws of Reflection 深入理解反射本质。










