
golang-google-wire
熱門在 Golang 中使用 google/wire 進行編譯時期依賴注入 — wire.NewSet、wire.Build、wire.Bind(介面→具體實作)、wire.Struct、wire.Value、wire.InterfaceValue、wire.FieldsOf、清理函式、//go:build wireinject 注入器檔案,以及產生的 wire_gen.go。適用於使用或採用 google/wire 時、程式碼庫匯入 `github.com/google/wire` 時,或透過 `wire.Build` 在編譯時期建構應用程式圖時。若需執行時期反射式 DI,請參閱 `samber/cc-skills-golang@golang-uber-dig` 技能。
在 Golang 中使用 google/wire 進行編譯時期依賴注入 — wire.NewSet、wire.Build、wire.Bind(介面→具體實作)、wire.Struct、wire.Value、wire.InterfaceValue、wire.FieldsOf、清理函式、//go:build wireinject 注入器檔案,以及產生的 wire_gen.go。適用於使用或採用 google/wire 時、程式碼庫匯入 `github.com/google/wire` 時,或透過 `wire.Build` 在編譯時期建構應用程式圖時。若需執行時期反射式 DI,請參閱 `samber/cc-skills-golang@golang-uber-dig` 技能。
角色: 你是一位使用 wire 進行編譯時期 DI 的 Go 架構師。你讓編譯器捕捉遺漏的依賴,將 wire_gen.go 視為已提交的原始碼,並在每次圖形變更後重新執行 wire ./...。
依賴:
- wire:
go install github.com/google/wire/cmd/wire@latest
在 Go 中使用 google/wire 進行編譯時期依賴注入
程式碼生成 DI 工具包。Wire 在編譯時期解析依賴圖,並產生純 Go 建構子呼叫 — 沒有執行時期容器,沒有反射。錯誤在執行 wire ./... 時出現,而不是在第一次請求時。
注意:google/wire 已於 2025 年 8 月封存(功能完整;仍接受錯誤修正)。
官方資源: pkg.go.dev · github.com/google/wire · 使用者指南 · 最佳實踐
此技能並非詳盡無遺。請參閱函式庫文件和程式碼範例以獲取更多資訊。Context7 可作為探索平台提供協助。如需 Go 套件文件、版本、符號和已知漏洞,→ 請參閱 samber/cc-skills-golang@golang-pkg-go-dev 技能。
go get -tool github.com/google/wire/cmd/wire@latest
go get github.com/google/wire
wire 與執行時期 DI 的比較
| 關注點 | wire | dig / fx / samber/do |
|---|---|---|
| 解析方式 | 編譯時期(程式碼生成) | 執行時期(反射) |
| 錯誤偵測 | wire ./... 失敗 |
首次 Invoke/啟動時 |
| 執行時期容器 | 無 — 純 Go 呼叫 | 存在 |
| 生命週期鉤子 | 無內建 | fx: OnStart/OnStop |
| 產生的檔案 | wire_gen.go(已提交) |
無 |
如需生命週期、延遲載入和完整矩陣,請參閱 samber/cc-skills-golang@golang-dependency-injection。
提供者
提供者是任何 Go 函式 — 輸入是依賴,輸出是提供的型別。三種回傳形式:
func NewConfig() *Config { return &Config{Addr: ":8080"} }
func NewDB(cfg *Config) (*sql.DB, error) { return sql.Open("postgres", cfg.DSN) }
func NewRedis(cfg *Config) (*redis.Client, func(), error) { // 清理函式以反向順序鏈結
c := redis.NewClient(&redis.Options{Addr: cfg.RedisAddr})
return c, func() { c.Close() }, nil
}
提供者集合
wire.NewSet 將提供者分組以便重用。集合可以引用其他集合。
// infra/wire.go
var InfraSet = wire.NewSet(
NewConfig,
NewDB,
NewRedis,
)
// service/wire.go
var ServiceSet = wire.NewSet(
NewUserRepo,
NewUserService,
wire.Bind(new(UserStore), new(*UserRepo)), // 介面綁定
)
保持集合小巧:函式庫集合暴露穩定的表面(新增輸入或移除輸出會破壞下游注入器)。每個套件一個集合是實用的預設值。
注入器與 //go:build wireinject
注入器檔案宣告初始化函式。Wire 將其主體產生到 wire_gen.go 中,並取代樁程式。
//go:build wireinject
package main
import "github.com/google/wire"
// Wire 產生此函式的主體。
func InitApp() (*App, func(), error) {
wire.Build(InfraSet, ServiceSet, NewApp)
return nil, nil, nil // 由程式碼生成取代
}
//go:build wireinject 標籤防止樁程式被編譯進二進位檔 — 只有 wire_gen.go(沒有此標籤)能通過 go build。沒有此標籤時,兩個檔案會定義相同的函式,導致編譯錯誤。
當虛擬回傳不方便時的替代語法:
func InitApp() (*App, func(), error) {
panic(wire.Build(InfraSet, ServiceSet, NewApp))
}
介面綁定
Wire 禁止隱含的介面滿足 — 你必須明確宣告綁定,以便在有多個型別實作相同介面時,圖形不會產生歧義。
var Set = wire.NewSet(
NewPostgresUserRepo,
wire.Bind(new(UserStore), new(*PostgresUserRepo)), // 告訴 wire:*PostgresUserRepo 滿足 UserStore
)
明確綁定可防止在別處新增實作相同介面的新型別時,圖形被破壞。
結構體提供者與值
wire.Struct 從圖形中填入結構體欄位,無需手動建構子。使用 wire:"-" 標記欄位以排除。
wire.Struct(new(Server), "Logger", "DB") // 注入指定欄位
wire.Struct(new(Server), "*") // 注入所有未排除的欄位
wire.Value(Foo{X: 42}) // 常數表達式(無函式呼叫/通道)
wire.InterfaceValue(new(io.Reader), os.Stdin) // 介面型別的字面值
wire.FieldsOf(new(Config), "DSN", "Addr") // 將結構體欄位提升為圖形節點
請參閱 advanced.md 了解 wire:"-" 排除標籤和 wire.FieldsOf 的詳細資訊。
消除重複型別的歧義
Wire 禁止為相同型別提供兩個提供者。將底層型別包裝成不同的命名型別,使每個型別只有一個提供者:
type PrimaryDSN string
type ReplicaDSN string
完整應用程式範例
// wire.go — 注入器,透過建置標籤從二進位檔中排除
//go:build wireinject
package main
func InitApp() (*App, func(), error) {
wire.Build(config.ConfigSet, infra.InfraSet, service.ServiceSet, NewApp)
return nil, nil, nil
}
// main.go
func main() {
app, cleanup, err := InitApp()
if err != nil { log.Fatal(err) }
defer cleanup()
app.Run()
}
Wire 產生 wire_gen.go(純 Go,已提交,請勿編輯)。如需包含每個套件集合、大量清理圖形和產生輸出的完整範例,請參閱 recipes.md。
程式碼生成工作流程
wire ./... # 重新產生模組中的所有注入器
wire check ./... # 驗證圖形但不重新產生(快速 CI 檢查)
每次建構子簽章變更後,執行 wire ./...。在注入器檔案中加入 //go:generate go run github.com/google/wire/cmd/wire,以便 go generate ./... 也能運作。提交 wire_gen.go — 它必須保持同步以進行 CI 建置。
最佳實踐
- 絕不手動編輯
wire_gen.go— 每次執行wire ./...時都會覆寫它。將其視為已提交的建置產物;真相來源是提供者和注入器檔案。 - 務必在注入器檔案中加入
//go:build wireinject— 省略它會導致重複符號的編譯錯誤,因為樁程式和產生的檔案都定義了相同的函式。 - 使用命名型別來區分相同底層型別的值 — wire 強制每個型別只有一個提供者;命名型別如
type DSN string可讓PrimaryDSN和ReplicaDSN共存。 - 保持函式庫提供者集合最小化且向後相容 — 新增必要的輸入會破壞下游注入器;移除輸出也會。僅在同一個版本中引入新建立的型別。
- 從清理提供者回傳
(T, func(), error),讓 wire 鏈結它們 — wire 產生正確的反向順序清理,並處理部分失敗(如果建構中途失敗,只會執行已建構項目的清理)。 - 保持注入器檔案專注 — 每個檔案一個函式,一次一個套件匯入。包含數十個
wire.Build參數的龐大注入器難以理解;委派給每個套件的集合。
常見錯誤
| 錯誤 | 修正 |
|---|---|
手動編輯 wire_gen.go |
絕不編輯。修改提供者或注入器,然後重新執行 wire ./...。 |
缺少 //go:build wireinject |
將標籤作為每個注入器檔案的第一行加入。 |
兩個提供者回傳 *sql.DB |
用命名結構體型別包裝:type PrimaryDB struct { *sql.DB } — Wire 不區分指標型別別名。 |
注入介面時未使用 wire.Bind |
在提供者集合中加入 wire.Bind(new(MyInterface), new(*MyImpl))。 |
變更後忘記重新執行 wire ./... |
在 go build 前執行 wire;將其加入 go generate 或 Makefile 目標。 |
呼叫 cleanup() 時未檢查 nil |
Wire 在建構錯誤時回傳 nil cleanup;使用 if cleanup != nil { defer cleanup() } 保護。 |
測試
Wire 產生純 Go 建構子,因此單元測試使用手動注入 — 無需複製或重設容器。如需測試模式(測試注入器將真實提供者替換為假物件、CI 檢查 wire_gen.go 是否過時),請參閱 testing.md。
延伸閱讀
- advanced.md — 清理鏈結、多個注入器、集合巢狀、錯誤目錄、程式碼生成旗標、快速參考
- recipes.md — HTTP 伺服器、多注入器建置、大量清理圖形、CLI 嵌入
- testing.md — 測試注入器、假綁定、CI 過時檢查
交叉參考
- → 參閱
samber/cc-skills-golang@golang-dependency-injection技能了解 DI 概念和函式庫比較 - → 參閱
samber/cc-skills-golang@golang-uber-dig技能了解無生命週期的執行時期反射式 DI - → 參閱
samber/cc-skills-golang@golang-uber-fx技能了解具有生命週期鉤子、模組和訊號感知 Run() 的執行時期 DI - → 參閱
samber/cc-skills-golang@golang-samber-do技能了解基於泛型且無反射的 DI - → 參閱
samber/cc-skills-golang@golang-structs-interfaces技能了解介面設計模式 - → 參閱
samber/cc-skills-golang@golang-testing技能了解一般測試模式
如果你遇到 google/wire 的錯誤或非預期行為,請在 https://github.com/google/wire/issues 開啟問題。





