Go 没有运行时注解,所谓“自定义注解”实为结构体标签(struct tag),仅能通过反射读取字段上反引号包裹的元数据(如db:"id,pk"),用于单表CRUD等有限场景。

Go 没有运行时注解,所谓“自定义注解”其实是结构体标签(struct tag)
很多人从 Java 转过来会下意识搜 “Go annotation” 或 “Go reflection parse annotation”,但 Go 语言本身不支持类似 @Override 那样的运行时注解机制。你真正能用反射读取的,只有结构体字段上的 struct tag —— 也就是写在 `` 里的字符串,比如:json:"user_name"`、db:"id,pk,autoinc"`。
所以“解析自定义注解生成 SQL”的本质是:定义一套 tag key(如 db),用 reflect 读取字段的 StructField.Tag.Get("db"),再按约定解析其值。
怎么定义和解析 db tag 来生成 INSERT 语句
假设你有结构体:
type User struct {
ID int64 `db:"id,pk,autoinc"`
Name string `db:"name,notnull"`
Email string `db:"email,unique"`
Age int `db:"age,default:0"`
}
目标是生成类似 INSERT INTO user (name, email, age) VALUES (?, ?, ?)(注意跳过 pk,autoinc 字段)。
立即学习“go语言免费学习笔记(深入)”;
关键步骤:
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
- 用
reflect.TypeOf(t).Elem()获取结构体类型(注意传指针) - 遍历每个
StructField,调用field.Tag.Get("db")获取原始 tag 值 - 跳过空 tag 或含
pk,autoinc的字段(业务逻辑判断) - 把剩余字段名拼成列名列表,同时收集占位符(如
?或$1) - 用
reflect.ValueOf(t).Elem().Field(i).Interface()提取对应值(注意可寻址性和导出性)
别直接 split db:"id,pk,autoinc" —— 更稳妥的是用逗号分隔后 trim 空格,再检查是否包含 "pk" 或 "autoinc";否则遇到 default:"foo,bar" 就会误切。
常见坑:tag 值解析错误、字段不可导出、嵌套结构体没处理
典型报错:panic: reflect: Field of non-struct type 或 reflect: call of reflect.Value.Interface on zero Value。
原因和对策:
- 传了非指针给反射函数 → 一定要传
&User{...},否则reflect.ValueOf(u).Elem()会 panic - 字段未导出(小写开头)→
reflect无法读取其值,即使 tag 存在也拿不到数据,生成 SQL 时参数数量对不上 - 把
db:"name"误写成db:"name,"→Tag.Get返回空字符串,后续切片越界或逻辑跳过 - 忽略零值字段(如
Age: 0)却没约定omitempty行为 → 生成的 SQL 插入 0 而不是用 default,需显式判断字段是否有default:且当前值为零值 - 没处理嵌套结构体或 slice/map 字段 → 反射走到非基本类型时应跳过,否则
Interface()可能 panic 或生成无效 SQL
生成 SELECT 时要注意字段别名和 JOIN 支持有限
如果 tag 写成 db:"user_name as name",解析逻辑就得支持 as 关键字提取别名;但更常见的做法是只存列名,别名由上层拼接。JOIN 场景下,单纯靠 struct tag 很难表达多表映射关系 —— 比如一个 User 结构体里嵌了个 Profile 字段,db:"profile_id" 并不能自动推导出 JOIN profile ON user.profile_id = profile.id。
这意味着:tag 只适合单表 CRUD 的元信息描述。复杂查询仍需手写 SQL 或引入 Query Builder(如 squirrel)或 ORM(如 gorm 的 gorm:"column:name")。
真正容易被忽略的一点:tag 解析逻辑一旦写死,就很难动态覆盖。比如测试时想临时禁用某个字段的插入,只能改结构体定义或加额外运行时开关 —— 这比 Java 注解 + AOP 灵活度低得多。

















