在上一篇文章中,我們介紹了 fx
框架的基本用法,並展示瞭如何使用 fx
構建一個簡單的服務。相信大家現在已經掌握了使用 fx 建立和管理依賴注入的基本方法以及啟動應用程式的方法。為了讓你的專案更加專業和高效,我們接下來將深入探討 fx
框架的高階功能和使用技巧,如如何利用 fx.Lifecycle
管理服務生命週期,在應用啟動和停止時執行特定邏輯,以及如何使用 fx.Invoke
註冊啟動時需要呼叫的函式。透過了解這些高階功能,你將能夠充分發揮 fx
的潛力,構建出更加複雜和健壯的 Go
應用程式。
讓我們一起探索 fx
的高階用法,提升你的程式設計技巧和專案質量。
fx.Invoke
上一篇文章,我們講到 fx.Invoke
方法可以註冊 fx.Lifecycle
中的 hook
,用來進行生命週期的管理。接下來我們介紹另外一個用法:用於註冊需要依賴注入的函式,這些函式會在應用程式啟動時呼叫,並且會自動接收所需的依賴項。
也就是說透過 fx.Invoke
呼叫一些函式在程式啟動時例項化某些依賴物件。具體來說,fx.Invoke
註冊的函式會在應用程式啟動時被呼叫,這些函式的引數會自動由 fx
提供的依賴注入機制解析並注入。初始化呼叫有點類似 Springboot
中的 org.springframework.boot.CommandLineRunner#run
,不同的是 Springboot
會在這個方法之前例項化各個 Component
物件,而 fx
預設的是呼叫時候才會初始化。所以如果想在程式啟動的時候初始化一些資源或者物件,就可以透過呼叫 fx.Invoke
方法實現。
下面是一個簡單的例子:
package main
import (
"go.uber.org/fx"
"go.uber.org/zap")
func main() {
app := fx.New( //建立fx.App例項
fx.Provide(NewTester, func() *Age {
return &Age{Num: 18} //提供Age例項
}, func() *zap.Logger {
production, _ := zap.NewProduction() //提供zap.Logger例項
return production
}), //提供NewTester函式
fx.Invoke(func(*Tester) {
//呼叫Tester函式,預設會呼叫對應的 provide 方法中提供的函式,如果不需要實際呼叫物件,可以不寫形參的名稱
}),
)
app.Run() //執行fx.App例項
}
type Age struct {
Num int //年齡,整型
}
type Tester struct {
Log *zap.Logger //日誌
Age *Age //年齡
}
func NewTester(age *Age, log *zap.Logger) *Tester {
return &Tester{
Age: age,
Log: log,
}
}
fx.Invoke
註冊了一個匿名函式,該函式接收一個 Tester 型別的引數。fx 容器會確保在應用啟動時,Tester 及其所有依賴(Age 和 zap.Logger)都被例項化。即使匿名函式中不使用 Tester 物件,fx 仍會呼叫 NewTester 以確保 Tester 被正確建立和初始化。
fx.Supply
fx.Supply
方法用於直接向 fx
框架 provide
一個物件,不用透過方法注入。主要的使用場景如下:
使用場景
- 靜態配置:你已經有了一個配置物件,可以直接將其提供給 Fx。
- 現有例項:你有一些已經建立好的例項,可以直接注入,而不需要透過 fx.Provide。
- 測試物件:在單元測試中,你可以使用 fx.Supply 提供一些測試物件。
下面是個簡單的例子:
package main
import (
"go.uber.org/fx"
"go.uber.org/zap")
func main() {
app := fx.New( //建立fx.App例項
fx.Provide(func() *zap.Logger {
production, _ := zap.NewProduction() //提供zap.Logger例項
return production
}), //提供NewTester函式
fx.Supply(&Age{Num: 18}), //提供Age例項
)
app.Run() //執行fx.App例項
}
type Age struct {
Num int //年齡,整型
}
type Tester struct {
Log *zap.Logger //日誌
Age *Age //年齡
}
func NewTester(age *Age, log *zap.Logger) *Tester {
return &Tester{
Age: age,
Log: log,
}
}
fx.popular
fx.Populate
是 Fx
框架中的一個功能,用於將依賴注入到外部的變數中。它可以讓你在應用啟動時,將 fx
容器中的依賴直接注入到你指定的變數中,而不需要在建構函式或初始化邏輯中顯式地傳遞這些依賴。
意思就是使用這個方法,傳入一些物件的指標,然後就可以在程式啟動的時候初始化建立例項了。
需要注意的是 Populate(targets …interface{}) 中傳入的 targets 必須得是目標型別 TypeX 的指標型別 *TypeX,哪怕 TypeX 本身就是指標型別
下面是 fx.popular
兩種使用場景:
- 外部變數注入:需要將 fx 容器中的依賴注入到外部的全域性變數或其他作用域中。
- 測試:在單元測試中,可以方便地將依賴注入到測試用例中,便於進行依賴的替換和注入。
下面來展示一下程式碼:
package main
import (
"go.uber.org/fx"
"go.uber.org/zap")
var (
logger *zap.Logger
age *Age
)
func main() {
app := fx.New(
fx.Provide(
NewLogger,
NewAge,
),
fx.Populate(&logger, &age),
fx.Invoke(func() {
logger.Info("Application started", zap.Int("age", age.Num))
}),
)
app.Run()
}
func NewLogger() (*zap.Logger, error) {
return zap.NewProduction()
}
func NewAge() *Age {
return &Age{Num: 30}
}
type Age struct {
Num int
}
fx.popular
方法的優勢體現在下面三個方面:
- 簡化依賴注入:fx.Populate 提供了一種簡潔的方式,將依賴注入到外部變數中,避免了在建構函式或初始化邏輯中顯式地傳遞這些依賴。
- 提高程式碼可讀性:透過使用全域性變數或特定作用域的變數,可以使程式碼更加直觀和易讀。
- 測試友好:在測試環境中,可以方便地替換和注入依賴,便於進行單元測試和整合測試。
fx.Annotated
fx.Annotated
是 Fx
框架中的一個功能,用於向依賴注入容器提供帶有特定標籤的建構函式。這在處理依賴注入時非常有用,特別是當你有多個相同型別的例項,但需要將它們區分開來時。
當我們依賴的物件型別相同時候,可以用 fx.Annotated
方法進行物件的區分,比如我們同時要記錄多種日誌、連線多個資料庫等等。不僅僅要在注入依賴物件的時候進行區分,也需要在使用的時候進行區分。
下面是個例子:
package main
import (
"go.uber.org/fx"
"go.uber.org/zap")
type Age struct {
Num int
}
type Tester struct {
Log *zap.Logger
Age *Age
}
var Ages = fx.Provide(
fx.Annotated{
Name: "old",
Target: NewAgeOld,
},
fx.Annotated{
Name: "young",
Target: NewAgeYoung,
})
func main() {
app := fx.New(
fx.Provide(
NewLogger,
fx.Annotate(
NewTester,
fx.ParamTags(`name:"old"`),
),
),
Ages,
fx.Invoke(
func(t *Tester) {
t.Log.Info("sussess", zap.Int("age", t.Age.Num))
},
),
)
app.Run()
}
func NewLogger() (*zap.Logger, error) {
return zap.NewProduction()
}
func NewTester(age *Age, log *zap.Logger) *Tester {
return &Tester{Log: log, Age: age}
}
func NewAgeYoung() *Age {
return &Age{Num: 18}
}
func NewAgeOld() *Age {
return &Age{Num: 60}
}
fx.Annotated
還需要搭配 fx.Annotate
才能將引數傳給建立的方法,制定同一型別物件的具體某個例項。fx.Annotated
的構造方法中還有一個引數 Group
和 Name
不能被同時使用。下面是 Group
的例子。
package main
import (
"fmt"
"go.uber.org/fx")
type Age interface {
Print() //無返回值
}
type AgeOld struct {
}
type AgeYoung struct {
}
func (age *AgeOld) Print() {
fmt.Println("old")
}
func (age *AgeYoung) Print() {
fmt.Println("young")
}
type Man struct {
Ages []Age `group:"men"`
}
func NewApp(ages []Age) *Man {
return &Man{Ages: ages}
}
var Ages = fx.Provide(
fx.Annotated{
Group: "men",
Target: NewAgeOld,
},
fx.Annotated{
Group: "men",
Target: NewAgeYoung,
})
func main() {
app := fx.New(
Ages,
fx.Provide(
fx.Annotate(
NewApp,
fx.ParamTags(`group:"men"`),
),
),
fx.Invoke(func(man *Man) {
ages := man.Ages
for _, age := range ages {
age.Print()
}
}),
)
app.Run()
}
func NewAgeYoung() Age {
return &AgeYoung{}
}
func NewAgeOld() Age {
return &AgeOld{}
}
fx.In 和 fx.Out
fx.In
和 fx.Out
是 Uber 的 fx 依賴注入框架中的兩個重要結構,用於管理複雜的依賴注入場景。一般在構建大型專案的時候,會經常用到 fx.In
和 fx.Out
,對於提升程式碼整潔度和可維護性有很重要的作用,同時也能夠避免依賴注入異常的發生。
fx.In
fx.In
用於聚合多個輸入引數到一個結構體中。當我們使用 fx.In
結構體時,就無需在 fx.New()
方法中顯示定義構造方法了。此時,只要當前結構體的依賴物件均在 fx
框架中定義,就可以直接建立當前的結構體物件。其主要特點是:
- 允許將多個依賴組合成一個單一的引數
- 支援可選依賴
- 可以使用標籤來指定特定的依賴
演示的程式碼如下:
package main
import (
"fmt"
"go.uber.org/fx")
type Name struct {
Str string
}
type Age struct {
Num int
}
type Tester struct {
fx.In
Age *Age
Name *Name
}
func main() {
NewAge := func() *Age {
return &Age{Num: 30}
}
NewName := func() *Name {
return &Name{Str: "FunTester"}
}
app := fx.New(
fx.Provide(
NewAge,
NewName,
),
fx.Invoke(func(t Tester) {
fmt.Println(t.Name.Str)
}),
)
app.Run()
}
fx.Out
fx.Out
用於從一個函式返回多個值,這些值可以被注入到其他地方。當我們使用 fx.Out
定義一個結構體,那麼當我們初始化這個結構體物件的時候,它所依賴的屬性物件也會自動 provide
到 fx
框架當中。其主要特點是:
- 允許一個函式提供多個依賴
- 支援使用標籤來命名或分組輸出
下面是演示程式碼:
package main
import (
"fmt"
"go.uber.org/fx")
type Name struct {
Str string
}
type Age struct {
Num int
}
// Values 表示兩個返回值:年齡和姓名
type Values struct {
fx.Out
Age *Age
Name *Name
}
func NewValues() Values {
return Values{
Age: &Age{Num: 30},
Name: &Name{Str: "FunTester"},
}
}
func main() {
app := fx.New(
// 提供建構函式
fx.Provide(
NewValues, // 使用 NewValues 而不是單獨的 NewAge 和 NewName ),
fx.Invoke(func(age *Age) {
fmt.Println(age.Num)
}),
)
app.Run()
}
結語
到這裡,常用的功能都覆蓋了,當前 fx
的內容不僅僅是這些,但對於學習、開發一個 Go
專案已經足夠了。相信只要不斷前進,早晚會用到更高階的語法。下面我列一下我學習過程中未在文章中列舉的 API
:
fx.module
fx.Module
是 fx
框架中的一個功能,用於組織和封裝相關的依賴和功能。它允許開發者將一組相關的提供者(providers
)和呼叫者(invokers
)打包成一個獨立的單元。這些模組可以被命名、重用和組合,從而簡化大型應用的結構。fx.Module
支援巢狀和條件載入,提高了程式碼的模組化程度、可維護性和可測試性。它特別適用於構建複雜、可擴充套件的Go
應用程式,使得依賴管理和功能組織變得更加清晰和高效。
fx.withlogger
fx.WithLogger
允許自定義 fx
框架的日誌記錄器。透過提供一個函式,該函式接收標準日誌記錄器並返回 fxevent.Logger
,你可以替換預設的日誌記錄器,實現特定需求的日誌記錄。例如,可以整合 zap.Logger
,使 fx
使用 zap
進行一致的日誌記錄,從而提高除錯和監控的效果。
fx.As
fx.As
是 fx
包中的一個選項,用於將具體型別轉換為其介面型別進行依賴注入。透過 fx.As
,你可以在 fx.Provide
中指定將某個建構函式的返回值作為介面型別提供,使得依賴注入更加靈活和可擴充套件。這有助於實現松耦合和增強程式碼的可測試性。
- 服務端功能測試
- 效能測試專題
- Java、Groovy、Go、Python
- 單元&白盒&工具合集
- 測試方案&BUG&爬蟲&UI
- 測試理論雞湯
- 社群風采&影片合集