成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

IOC-Golang 的 AOP 原理與應用

原創 精選
開發 前端
Spring 具備強大的依賴注入能力,在此基礎之上,提供了適配與業務對象方法的 AOP 能力,可以通過定義切點,將攔截器封裝在業務函數外部。這些 “切面”、“切點” 的概念,都是限定于 Spring 框架內,由其依賴注入(也就是 IOC)能力所管理。

作者 |   李志信(冀鋒)

?AOP 與 IOC 的關系

AOP (面向切面編程)是一種編程設計思想,旨在通過攔截業務過程的切面,實現特定模塊化的能力,降低業務邏輯之間的耦合度。這一思路在眾多知名項目中都有實踐。例如 Spring 的切點 PointCut 、 gRPC的攔截器 Interceptor 、Dubbo 的過濾器 Filter。AOP 只是一種概念,這種概念被應用在不同的場景下,產生了不同的實現。

我們首先討論比較具體的 RPC 場景,以 gRPC 為例。

圖片

圖片摘自 grpc.io

針對一次 RPC 過程,gRPC 提供了可供用戶擴展的 Interceptor 接口,方便開發者寫入與業務相關的攔截邏輯。例如引入鑒權、服務發現、可觀測等能力,在 gRPC 生態中存在很多基于 Interceptor 的擴展實現,可參考 go-grpc-middleware[1]。這些擴展實現歸屬于 gRPC 生態,限定于 Client 和 Server 兩側的概念,限定于 RPC 場景。

我們將具象的場景抽象化,參考 Spring 的做法。

Spring 具備強大的依賴注入能力,在此基礎之上,提供了適配與業務對象方法的 AOP 能力,可以通過定義切點,將攔截器封裝在業務函數外部。這些 “切面”、“切點” 的概念,都是限定于 Spring 框架內,由其依賴注入(也就是 IOC)能力所管理。

我想表達的觀點是,AOP 的概念需要結合具體場景落地,必須受到來自所集成生態的約束。我認為單獨提 AOP 的概念,是不具備開發友好性和生產意義的,例如我可以按照面向過程編程的思路,寫一連串的函數調用,也可以說這是實現了 AOP,但其不具備可擴展性、可遷移性、更不具備通用性。這份約束是必要的,可強可弱,例如 Spring 生態的 AOP,較弱的約束具備較大的可擴展性,但實現起來相對復雜,發者需要學習其生態的眾多概念與 API,再若 Dubbo 、gRPC 生態的適配于 RPC 場景的 AOP,開發者只需要實現接口并以單一的 API 注入即可,其能力相對局限。

上述 “約束” 在實際開發場景可以具象為依賴注入,也就是 IOC。開發者需要使用的對象由生態所納管、封裝,無論是 Dubbo 的 Invoker、還是 Spring 的 Bean,IOC 過程為 AOP 的實踐提供了約束借口,提供了模型,提供了落地價值。

圖片

Go 生態與 AOP

AOP 概念與語言無關,雖然我贊成使用 AOP 的最佳實踐方案需要 Java 語言,但我不認為 AOP 是 Java 語言的專屬。在我所熟悉的 Go 生態中,依然有較多基于 AOP 思路的優秀項目,這些項目的共性,也如我上一節所闡述的,都是結合特定生態,解決特定業務場景問題,其中解決問題的廣度,取決于其 IOC 生態的約束力。IOC 是基石,AOP 是 IOC 生態的衍生物,一個不提供 AOP 的 IOC 生態可以做的很干凈很清爽,而一個提供 AOP 能力的 IOC 生態,可以做的很包容很強大。

上個月我開源了 IOC-golang [2]服務框架,專注于解決 Go 應用開發過程中的依賴注入問題。很多開發者把這個框架和 Google 開源的 wire [3]框架做比較,認為沒有 wire 清爽好用,這個問題的本質是兩個生態的設計初衷不同。wire 注重 IOC 而非 AOP,因此開發者可以通過學習一些簡單的概念和 API,使用腳手架和代碼生成能力,快速實現依賴注入,開發體驗很好。IOC-golang 注重基于 IOC 的 AOP 能力,并擁抱這一層的可擴展性,把 AOP 能力看作這一框架和其他 IOC 框架的差異點和價值點。

相比于解決具體問題的 SDK,我們可以把依賴注入框架的 IOC 能力看作“弱約束的IOC場景”,通過兩個框架差異點比較,拋出兩個核心的問題:

Go 生態在 “弱約束 IOC 的場景” 需不需要 AOP?

GO 生態在 “弱約束 IOC 的場景” 的 AOP 可以用來做什么?

我的觀點是:Go 生態一定是需要 AOP 的,即使在“弱約束 IOC 場景”,依然可以使用 AOP 來做一些業務無關的事情,比如增強應用的運維可觀測能力。由于語言特性,Go 生態的 AOP 不能和 Java 劃等號,Go 不支持注解,限制了開發者使用編寫業務語義 AOP 層的便利性,所以我認為 Go 的 AOP 并不適合處理業務邏輯,即使強行實現出來,也是反直覺的。我更接受把運維可觀測能力賦予 Go 生態的 AOP 層,而開發者對于 AOP 是無感知的。

例如,對于任何接口的實現結構,都可以使用 IOC-golang 框架封裝運維 AOP 層,從而讓一個應用程序的所有對象都具備可觀測能力。除此之外,我們也可以結合 RPC 場景、服務治理場景、故障注入場景,產生出更多 “運維” 領域的擴展思路。

IOC-golang 的 AOP 原理

使用 Go 語言實現方法代理的思路有二,分別為通過反射實現接口代理,和基于 Monkey 補丁的函數指針交換。后者不依賴接口,可以針對任何結構的方法封裝函數代理,需要侵入底層匯編代碼,關閉編譯優化,對于 CPU 架構有要求,并且在處理并發請求時會顯著削弱性能。

前者的生產意義較大,依賴接口,也是本節所討論的重點。

3.1 IOC-golang 的接口注入

在本框架開源的第一篇文章中有提到,IOC-golang 在依賴注入的過程具備兩個視角,結構提供者和結構使用者??蚣芙邮軄碜越Y構提供者定義的結構,并按照結構使用者的要求把結構提供出來。結構提供者只需關注結構本體,無需關注結構實現了哪些接口。而結構使用者需要關心結構的注入和使用方式:是注入至接口?注入至指針?是通過 API 獲???還是通過標簽注入獲???

  • 通過標簽注入依賴對象
// +ioc:autowire=true
// +ioc:autowire:type=singleton

type App struct {
// 將實現注入至結構體指針
ServiceStruct *ServiceStruct `singleton:""`

// 將實現注入至接口
ServiceImpl Service `singleton:"main.ServiceImpl1"`
}

App 的 ServiceStruct 字段是具體結構的指針,字段本身已經可以定位期望被注入的結構,因此不需要在標簽中給定期望被注入的結構名。對于這種注入到結構體指針的字段,無法通過注入接口代理的方式提供 AOP 能力,只能通過上文提到的 monkey 補丁方案,這種方式不被推薦。

App 的 ServiceImpl 字段是一個名為 Service 的接口,期望注入的結構指針是 main.ServiceImpl。本質上是一個從結構到接口的斷言邏輯,雖然框架可以進行接口實現的校驗,但仍需要結構使用者保證注入的接口實現了該方法。對于這種注入到接口的方式,IOC-golang 框架自動為 main.ServiceImpl 結構創建代理,并將代理結構注入在 ServiceImpl 字段,因此這一接口字段具備了 AOP 能力。

因此,ioc 更建議開發者面向接口編程,而不是直接依賴具體結構,除了 AOP 能力之外,面向接口編程也會提高 go 代碼的可讀性、單元測試能力、模塊解耦合程度等。

  • 通過 API 的方式獲取對象

IOC-golang 框架的開發者可以通過 API 的方式獲取結構指針,通過調用自動裝載模型(例如singleton)的 GetImpl 方法,可以獲取結構指針。


func GetServiceStructSingleton() (*ServiceStruct, error) {
i, err := singleton.GetImpl("main.ServiceStruct", nil)
if err != nil {
return nil, err
}
impl := i.(*ServiceStruct)
return impl, nil
}

使用 IOC-golang 框架的開發者更推薦通過API 的方式獲取接口對象,通過調用自動裝載模型(例如singleton)的 GetImplWithProxy 方法,可以獲取代理結構,該結構可被斷言為一個接口供使用。這個接口并非結構提供者手動創建,而是由 iocli 自動生成的“結構專屬接口”,在下文中將予以解釋。

func GetServiceStructIOCInterfaceSingleton() (ServiceStructIOCInterface, error) {
i, err := singleton.GetImplWithProxy("main.ServiceStruct", nil)
if err != nil {
return nil, err
}
impl := i.(ServiceStructIOCInterface)
return impl, nil
}

這兩種通過 API 獲取對象的方式可以由 iocli 工具自動生成,注意,這些代碼的作用都是方便開發者調用 API ,減少代碼量,而 ioc 自動裝載的邏輯內核并不是由工具生成的,這是與 wire 提供的依賴注入實現思路的不同點之一,也是很多開發者誤解的一點。

  • IOC-golang 的結構專屬接口。

通過上面的介紹,我們知道 IOC-golang 框架推薦的 AOP 注入方式是強依賴接口的。但要求開發者為自己的全部結構,都手寫一個與之匹配的接口出來,這會耗費大量的時間。因此 iocli 工具可以自動生成結構專屬接口,減輕開發人員的代碼編寫量。

例如一個名為 ServiceImpl 的結構,其包含 GetHelloString 方法。


// +ioc:autowire=true
// +ioc:autowire:type=singleton

type ServiceImpl struct {
}

func (s *ServiceImpl) GetHelloString(name string) string {
return fmt.Sprintf("This is ServiceImpl1, hello %s", name)
}

當執行 iocli gen 命令后, 會在當前目錄生成一份代碼zz_generated.ioc.go 其中包含該結構的“專屬接口”:

type ServiceImplIOCInterface interface {
GetHelloString(name string) string
}

專屬接口的命名為 $(結構名)IOCInterface,專屬接口包含了結構的全部方法。專屬接口的作用有二:

  • 減輕開發者工作量,方便直接通過 API 的方式 Get 到代理結構,方便直接作為字段注入。
  • 結構專屬接口可以直接定位結構 ID,因此在注入專屬接口的時候,標簽無需顯式指定結構類型:
// +ioc:autowire=true
// +ioc:autowire:type=singleton

type App struct {
// 注入 ServiceImpl 結構專屬接口,無需在標簽中指定結構ID
ServiceOwnInterface ServiceImplIOCInterface `singleton:""`
}

因此,隨便找一個現有的 go 工程,其中使用結構指針的位置,我們推薦替換成結構專屬接口,框架默認注入代理;對于其中已經使用了接口的字段,我們推薦直接通過標簽注入結構,也是由框架默認注入代理。按照這種模式開發的工程,其全部對象都將具備運維能力。

3.2 代理的生成與注入

上一小節所提到的“注入至接口”的對象,都被被框架默認封裝了代理,具備運維能力,并提到了 iocli 會為所有結構產生“專屬接口”。在本節中,將解釋框架如何封裝代理層,如何注入至接口的。

  • 代理結構的代碼生成與注冊

在前文提到生成的 zz.generated.ioc.go 代碼中包含結構專屬接口,同樣,其中也包含結構代理的定義。還是以上文中提到的 ServiceImpl 結構為例,它生成的代理結構如下:

type serviceImpl1_ struct {
GetHelloString_ func(name string) string
}

func (s *serviceImpl1_) GetHelloString(name string) string {
return s.GetHelloString_(name)
}

代理結構命名為小寫字母開頭的 $(結構名)_,其實現了“結構專屬接口” 的全部方法,并將所有方法調用代理至 $(方法名)_ 的方法字段,該方法字段會被框架以反射的方式實現。

與結構代碼一樣,代理結構也會在這個生成的文件中注冊到框架:

func init(){
normal.RegisterStructDescriptor(&autowire.StructDescriptor{
Factory: func() interface{} {
return &serviceImpl1_{} // 注冊代理結構
},
})
}
  • 代理對象的注入

上述內容描述了代理結構的定義和注冊過程。當用戶期望獲取封裝了AOP層的代理對象,將首先加載真實對象,然后嘗試加載代理對象,最終通過反射實例化代理對象,注入接口,從而賦予接口運維能力。該過程可由下圖展示:

圖片

IOC-golang 基于 AOP 的應用

理解了上文中提到的實現思路,我們可以認為,使用 IOC-golang 框架開發的應用程序中,從框架注入、獲取的所有接口對象都是具備運維能力的。我們可以基于 AOP 的思路,擴展出我們期望的能力。我們提供了一個簡易的電商系統 demo shopping-system[4],展示了在分布式場景下 IOC-golang 基于 AOP 的可視化能力。感興趣的開發者可以參考 README,在自己的集群里運行這個系統,感受其運維能力底座。

4.1 方法、參數可觀測

  • 查看應用接口和方法
% iocli list
github.com/alibaba/ioc-golang/extension/autowire/rpc/protocol/protocol_impl.IOCProtocol
[Invoke Export]

github.com/ioc-golang/shopping-system/internal/auth.Authenticator
[Check]

github.com/ioc-golang/shopping-system/pkg/service/festival/api.serviceIOCRPCClient
[ListCards ListCachedCards]
  • 監聽調用參數

通過 iocli watch命令, 我們可以監聽鑒權接口的 Check 方法的調用:

iocli watch github.com/ioc-golang/shopping-system/internal/auth.Authenticator Check
  • 發起針對入口的調用

curl -i -X GET 'localhost:8080/festival/listCards?user_id=1&num=10'

可查看到被監聽方法的調用參數和返回值,user id 為1。

 % iocli watch github.com/ioc-golang/shopping-system/internal/auth.Authenticator Check
========== On Call ==========
github.com/ioc-golang/shopping-system/internal/auth.Authenticator.Check()
Param 1: (int64) 1

========== On Response ==========
github.com/ioc-golang/shopping-system/internal/auth.Authenticator.Check()
Response 1: (bool) true

4.2 全鏈路追蹤

基于 IOC-golang 的 AOP 層,可以提供用戶無感知、業務無侵入的分布式場景下全鏈路追蹤能力。即一個由本框架開發的系統,可以以任何一個接口方法為入口,采集到方法粒度的跨進程調用全鏈路。

圖片

基于 shopping-system 的全鏈路耗時信息,可以排查到名為 festival 進程的 gorm.First() 方法是系統的瓶頸。

這個能力的實現包括兩部分,分別是進程內的方法粒度鏈路追蹤,和進程之間的 RPC 調用鏈路追蹤。IOC 旨在打造開發者開箱即用的應用開發生態組件,這些內置的組件與框架提供的 RPC 能力都具備了運維能力。

  • 基于 AOP 的進程內鏈路追蹤

IOC-golang 提供的鏈路追蹤能力的進程內實現,是基于 AOP 層做的,為了做到業務無感知,我們并沒有通過 context 上下文的方式去標識調用鏈路,而是通過 go routine id 進行標識。通過 go runtime 調用棧,來記錄當前調用相對入口函數的深度。

  • 基于 IOC 原生 RPC 的進程間鏈路追蹤

IOC-golang 提供的原生 RPC 能力,無需定義 IDL文件,只需要為服務提供者標注 // +ioc:autowire:type=rpc ,即可生成相關注冊代碼和客戶端調用存根,啟動時暴露接口??蛻舳酥恍枰脒@一接口的客戶端存根,即可發起調用。這一原生 RPC 能力基于 json 序列化和 http 傳輸協議,方便承載鏈路追蹤 id。

展望

IOC-golang 開源至今已經突破 700 star,其熱度的增長超出了我的想象,也希望這個項目能帶來更大的開源價值與生產價值,歡迎越來越多的開發者參與到這個項目的討論和建設中。

參考鏈接:

[1]https://github.com/grpc-ecosystem/go-grpc-middleware

[2]https://github.com/alibaba/ioc-golang

[3]https://github.com/google/wire

[4]https://github.com/ioc-golang/shopping-system

責任編輯:武曉燕 來源: 阿里開發者
相關推薦

2012-09-28 10:20:14

IBMdw

2012-09-27 09:47:43

SpringJava面向對象

2021-05-06 07:58:57

Spring BeanIOCAOP

2020-08-17 07:59:47

IoC DINestJS

2024-11-04 16:29:19

2009-06-11 16:45:47

Java事物

2022-04-13 08:23:31

Golang并發

2009-07-10 12:00:27

2013-07-05 14:47:51

IoC需求

2025-03-27 02:50:00

2020-07-07 14:09:16

云原生JavaGolang

2009-06-18 13:31:03

Spring工作原理

2018-10-25 16:20:23

JavaSpring AOPSpringMVC

2020-11-30 16:01:03

Semaphore

2023-08-04 17:46:22

Flutter代碼檢查原理

2020-12-04 19:28:53

CountDownLaPhaserCyclicBarri

2021-01-14 18:17:33

SpringFrameIOCJava

2023-11-26 18:02:00

ReactDOM

2024-03-28 10:37:44

IoC依賴注入依賴查找

2022-08-30 22:21:41

gotop監視器圖形
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 日韩久久久久久 | 天天插天天操 | 欧美久久一区二区三区 | 黄色在线免费网站 | 欧美国产日韩成人 | www.99热.com| 一级看片免费视频囗交动图 | 天堂中文字幕av | 亚洲看片网站 | 国产一区999| 亚洲欧美日韩电影 | 日韩午夜电影在线观看 | 成人精品视频99在线观看免费 | 国产精品a免费一区久久电影 | 亚洲国产成人一区二区 | 91porn在线 | 国产精品亚洲片在线播放 | 日本欧美黄色片 | 久久精品网 | 久久香蕉精品视频 | av网址在线 | 欧美三级在线 | 亚洲午夜视频 | 欧美视频成人 | 欧美色综合一区二区三区 | 国产99免费 | 成人在线免费观看视频 | 99热精品6| 亚洲成人综合社区 | 久久人人网 | 国产乱码精品一区二区三区五月婷 | 久久国产精品免费视频 | 国家一级黄色片 | 99亚洲| 国产乱码精品一区二三赶尸艳谈 | 97人人超碰| 亚洲精品乱码久久久久久久久 | 超碰综合 | 成人中文字幕在线观看 | 欧美大片在线观看 | av中文在线 |