<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
最近看到一個內部專案的外掛載入機制,非常贊。當然這裡說的外掛並不是指的golang原生的可以在buildmode中載入指定so檔案的那種載入機制。而是軟體設計上的「外掛」。如果你的軟體是一個框架,或者一個平臺性產品,想要提升擴充套件性,即可以讓第三方進行第三方庫開發,最終能像搭積木一樣將這些庫組裝起來。那麼就可能需要這種庫載入機制。
我們的目標是什麼?對第三方庫進行某種庫規範,只要按照這種庫規範進行開發,這個庫就可以被載入到框架中。
我們先定義一個外掛的資料結構,這裡肯定是需要使用介面來規範,這個可以根據你的專案自由發揮,比如我希望外掛有一個Setup方法來在啟動的時候載入即可。然後我就定義如下的Plugin結構。
type Plugin interface{ Name() string Setup(config map[string]string) error }
而在框架啟動的時候,我啟動了一個如下的全域性變數:
var plugins map[string]Plugin
有人可能會問,這裡有了載入函數setup,但是為什麼沒有註冊邏輯呢?
答案是註冊的邏輯放在庫的init函數中。
即框架還提供了一個註冊函數。
// package plugin Register(plugin Plugin)
這個register就是實現了將第三方plugin放到plugins全域性變數中。
所以第三方的plugin庫大致實現如下:
package MyPlugin type MyPlugin struct{ } func (m *MyPlugin) Setup(config map[string]string) error { // TODO func (m *MyPlugin) Name() string { return "myPlugin" func init() { plugin.Register(&MyPlugin)
這樣註冊的邏輯就變成了,如果你要載入一個外掛,那麼你在main.go中直接以 _ import的形式引入即可。
package main _ import "github.com/foo/myplugin" func main() { }
整體的感覺,這樣子外掛的註冊就被“隱藏”到import中了。
註冊的邏輯其實看起來也平平無奇,但是載入的邏輯就考驗細節了。
首先外掛的載入其實有兩點需要考慮:
設定指的是外掛一定是有某種設定的,這些設定以組態檔yaml中plugins.myplugin的路徑存在。
plugins: myplugin: foo: bar
其實我對這種實現持保留意見。組態檔以一個檔案中設定項的形式存在,好像不如以組態檔的形式存在,即以config/plugins/myplugin.yaml 的檔案。
這樣不會出現一個大組態檔的問題。畢竟每個組態檔本身就是一門DSL語言。如果你將組態檔的邏輯變複雜,一定會有很多附帶的bug是由於組態檔錯誤導致的。
第二個說的是依賴。外掛A依賴與外掛B,那麼這裡就有載入函數Setup的先後順序了。這種先後順序如果純依賴使用者的“經驗”,將某個外掛的Setup呼叫放在某個外掛的Setup呼叫之前,是非常痛苦的。(雖然一定是有辦法可以做到)。更好的辦法是依賴於框架自身的載入機制來進行載入。
首先我們在plugin包中定義一個介面:
type Depend interface{ DependOn() []string }
如果我的外掛依賴一個名字為 “fooPlugin” 的外掛,那麼我的外掛 MyPlugin就會實現這個介面。
package MyPlugin type MyPlugin struct{ } func (m *MyPlugin) Setup(config map[string]string) error { // TODO func (m *MyPlugin) Name() string { return "myPlugin" func init() { plugin.Register(&MyPlugin) func (m *MyPlugin) DependOn() []string { return []string{"fooPlugin"}
在最終載入所有外掛的時候,我們並不是簡單地將所有外掛呼叫Setup,而是使用一個channel,將所有外掛放在channel中,然後一個個呼叫Setup,遇到有Depend其他外掛的,且依賴外掛還未被載入,則將當前外掛放在佇列最後(重新塞入channel)。
var setupStatus map[string]bool // 獲取所有註冊外掛 func loadPlugins() (plugin chan Plugin, setupStatus map[string]bool) { // 這裡定義一個長度為10的佇列 var sortPlugin = make(chan Plugin, 10) var setupStatus = make[string]bool // 所有的外掛 for name, plugin := range plugins { sortPlugin <- plugin setupStatus[name] = false } return sortPlugin, setupStatus } // 載入所有外掛 func SetupPlugins(pluginChan chan Plugin, setupStatus map[string]bool) error { num := len(pluginChan) for num > 0 { plugin <- pluginChan canSetup := true if deps, ok := p.(Depend); ok { depends := deps.DependOn() for _, dependName := range depends{ if _, setuped := setupStatus[dependName]; !setup { // 有未載入的外掛 canSetup = false break } } } // 如果這個外掛能被setup if canSetup { plugin.Setup(xxx) setupStatus[p.Name()] = true } else { // 如果外掛不能被setup, 這個plugin就塞入到最後一個佇列 pluginChan <- plugin return nil }
上面這段程式碼最精妙的就是使用了一個有buffer的channel作為一個佇列,消費佇列一方SetupPlugins,除了消費佇列,也有可能生產資料到佇列,這樣就保證了佇列中所有plugin都是被按照標記的依賴被順序載入的。
這種外掛的註冊和載入機制是非常優雅的。註冊方面,巧妙使用隱式import來做外掛的註冊。而載入方面,巧妙使用有buffer的channel作為載入佇列。
到此這篇關於Golang庫外掛註冊載入機制的文章就介紹到這了,更多相關Golang外掛機制內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45