依赖注入
fun 自带一个按类型装配的轻量 DI 容器(box.go):单例创建、递归装配、New() 生命周期钩子。不引入任何第三方依赖。
核心 API:Wired
go
// 按类型创建/获取单例;先注入其 fun:"auto" 字段,再调 New()(若有)
func Wired[T any]() (*T, error)go
cfg, err := fun.Wired[config.Config]()
if err != nil {
log.Fatal(err) // 装配失败:缺少可注入依赖等
}Wired[T] 的执行顺序:
- 容器里已有
*T→ 直接返回; - 否则创建
&T{},扫描其字段,带fun:"auto"标签的指针结构体字段递归装配(容器没有就先创建依赖); - 若
T实现了New()(必须无参无返回)则调用——连接类资源在这里初始化,失败可以log.Fatalf。
go
type Postgres struct {
Config *Config `fun:"auto"` // 自动注入已注册的 *Config
Pool *sql.DB
}
func (p *Postgres) New() {
pool, err := sql.Open("postgres", p.Config.DSN)
if err != nil {
log.Fatalf("postgres: %v", err) // 连接类资源失败即退出
}
p.Pool = pool
}服务字段的自动装配
服务结构体上的指针结构体字段不需要标签,BindService 注册时自动从容器解析或补建:
go
type UserSvc struct {
fun.Ctx
Pg *platform.Postgres // 容器有则复用,无则递归创建(触发 New())
Redis *platform.Redis
}BindService 会拉起基础设施
BindService 触发真实装配——Postgres / Redis 的 New() 会真正建连接。因此代码生成不要用 BindService,用 BindServiceForGen(只登记元信息,不装配)。
启动顺序
go
func main() {
// ① 先 Wired 基础配置与平台单例(触发 New() 建连接)
cfg, err := fun.Wired[config.Config]()
if err != nil {
log.Fatal(err)
}
_, _ = fun.Wired[platform.Postgres]()
_, _ = fun.Wired[platform.Redis]()
// ② 再注册服务(服务字段从容器取已建好的单例)
f := fun.GetFun()
if err := f.BindService(&UserSvc{}); err != nil {
log.Fatal(err)
}
// ③ 最后启动
go f.Start(cfg.ListenPort())
}顺序不强制(BindService 会自动补建缺失依赖),但显式先 Wired 基础设施可以让连接问题在启动最早、堆栈最清晰的地方暴露。
Guard 也是 Box
Guard 结构体同样参与装配,字段自动注入:
go
type AuthGuard struct {
Redis *platform.Redis // 会话存储自动注入
}
func (g *AuthGuard) Guard(ctx fun.Ctx) error {
token := ctx.State["token"]
// ... 校验
return nil
}路由级 Guard(BindRoute 变参)亦然;Guard 依赖装配失败以 error 返回(不 panic)。
设计要点
| 问题 | 答案 |
|---|---|
| 单例粒度 | 按 *T 类型,一个类型一个实例 |
| 依赖声明 | Box 内 fun:"auto" 标签;服务/Guard 字段免标签 |
| 生命周期 | New() 无参无返回,装配完成即调用 |
| 失败策略 | Wired / BindService / BindRoute 返回 error;New() 里 log.Fatalf |
| 每请求 | 服务实例每请求新建,依赖(Box)全局共享 |