goFrame
框架是一款國產的 golang web 框架,它事無鉅細的把我們可能用到的元件都進行了封裝,目前 Github star 已高達 5.7K ,人送“封裝狂魔”的稱號。既然如此,那我們就來學習一下別人是怎麼來封裝的。
我們知道 goFrame
框架的 main 檔案非常簡單,以下是官方模板的 main 檔案示例:
package main
import (
_ "github.com/gogf/gf-demos/router"
"github.com/gogf/gf/frame/g"
)
// @title `gf-demo`示例服務API
// @version 1.0
// @description `GoFrame`基礎開發框架示例服務API介面文件。
// @schemes http
func main() {
g.Server().Run()
}
正常我們拉起一個 Server ,拿到例項之後再去註冊路由,比如 Gin 拉起服務是這樣的:
package main
import "github.com/gin-gonic/gin"
func main() {
r := gin.Default()
r.GET("/ping", func(c *gin.Context) {
c.JSON(200, gin.H{
"message": "pong",
})
})
r.Run() // listen and serve on 0.0.0.0:8080
}
為什麼 GoFrame 註冊路由不需要 main 中 初始化的例項就可以直接註冊路由呢?
package router
import (
"github.com/gogf/gf-demos/app/api"
"github.com/gogf/gf/frame/g"
"github.com/gogf/gf/net/ghttp"
)
func init() {
s := g.Server()
s.Group("/", func(group *ghttp.RouterGroup) {
group.ALL("/chat", api.Chat)
})
}
為什麼這麼簡短的程式碼就能拉起一個 Server 服務呢?我們追蹤程式碼來看下 Server()
方法做了些什麼事情!
// 位於 github.com/gogf/gf/frame/g/g_object.go 檔案 30 行
func Server(name ...interface{}) *ghttp.Server {
return gins.Server(name...)
}
Server()
方法很簡單,我們繼續往下追蹤:
// 位於 github.com/gogf/gf/frame/gins/gins_server.go 檔案
package gins
import (
"fmt"
"github.com/gogf/gf/net/ghttp"
"github.com/gogf/gf/util/gutil"
)
const (
frameCoreComponentNameServer = "gf.core.component.server"
configNodeNameServer = "server"
)
// Server returns an instance of http server with specified name.
func Server(name ...interface{}) *ghttp.Server {
instanceKey := fmt.Sprintf("%s.%v", frameCoreComponentNameServer, name)
return instances.GetOrSetFuncLock(instanceKey, func() interface{} {
s := ghttp.GetServer(name...)
// To avoid file no found error while it's not necessary.
if Config().Available() {
var m map[string]interface{}
nodeKey, _ := gutil.MapPossibleItemByKey(Config().GetMap("."), configNodeNameServer)
if nodeKey == "" {
nodeKey = configNodeNameServer
}
m = Config().GetMap(fmt.Sprintf(`%s.%s`, nodeKey, s.GetName()))
if len(m) == 0 {
m = Config().GetMap(nodeKey)
}
if len(m) > 0 {
if err := s.SetConfigWithMap(m); err != nil {
panic(err)
}
}
// As it might use template feature,
// it initialize the view instance as well.
_ = getViewInstance()
}
return s
}).(*ghttp.Server)
}
到這裡也就初見端倪了,instances
是一個全域性的 Map:
// 位於 github.com/gogf/gf/frame/gins/gins.go 檔案
package gins
import (
"github.com/gogf/gf/container/gmap"
)
var (
// instances is the instance map for common used components.
instances = gmap.NewStrAnyMap(true)
)
GetOrSetFuncLock
則是一個加鎖的操作,先檢索 Map 中是否存在指定 key 的值,沒有則執行傳入的 func
並將返回值新增到 Map 中:
func (m *StrAnyMap) GetOrSetFuncLock(key string, f func() interface{}) interface{} {
if v, ok := m.Search(key); !ok {
return m.doSetWithLockCheck(key, f)
} else {
return v
}
}
我們知道匯入包的 init
方法會先於 main
方法執行,所以當 main 檔案引入的 router.go
中執行 g.Server()
時,全域性 Map instances 中並沒有對應的 Server,Server()
方法則會初始化一個服務並放入全域性 Map 中,然後 main 方法再執行 g.Server()
獲取到的則是我們在 router
檔案中初始化並註冊好路由的 Server。
這就是經典的設計模式 單例 的實現了,那有沒有其他的方式實現單例呢?
sync.Once
是 golang 基礎包中自帶的一個方法,跟他的名字一樣,程式碼僅執行一次。
package main
import (
"sync"
"fmt"
)
var once sync.Once
func main() {
for i := 0; i < 3; i++ {
Server()
}
}
func Server() {
once.Do(func() {
fmt.Println("new server")
})
fmt.Println("get server")
}
// new server
// get server
// get server
// get server
這也就達到了我們的要求,初始化程式碼只執行一次。那他是怎麼實現的呢?我們來看看 sync.Once
的原始碼。
package sync
import (
"sync/atomic"
)
type Once struct {
done uint32
m Mutex
}
func (o *Once) Do(f func()) {
if atomic.LoadUint32(&o.done) == 0 {
o.doSlow(f)
}
}
func (o *Once) doSlow(f func()) {
o.m.Lock()
defer o.m.Unlock()
if o.done == 0 {
defer atomic.StoreUint32(&o.done, 1)
f()
}
}
程式碼其實很簡單,底層其實也是加鎖實現。用 done
標誌位來標識是否已經執行過了,已執行就直接跳過。
這裡的加鎖是非常有必要的,如果不加鎖,當 f 還沒執行完,done 標誌位還未置為 1 時,這時併發執行就會導致再次執行 f 。
本作品採用《CC 協議》,轉載必須註明作者和本文連結