beego框架程式碼分析

TIGERB發表於2018-12-17

前言

也許beego框架在國內應該是眾多PHPer轉go的首選,因為beego的MVC、ORM、完善的中文文件讓PHPer們得心應手,毫無疑問我也是。這種感覺就像當年入門PHP時使用ThinkPHP一樣。

也許隨著你的認知的提升,你會討厭現在東西,比如某一天你可能慢慢的開始討厭beego,你會發現go語言裡的真正意義,你開始反思MVC真的適合go嗎,或者你開始覺著ORM在靜態語言裡的雞肋,等等。我只想說:“也許你成長了~”。但是這些都不重要,每一個受歡迎的事物自然有我們值的學習的地方。今天這篇文章很簡單,像一篇筆記,記錄了我這幾天抽空讀beego原始碼的記錄。

如何讀一個框架?

毫無疑問讀go的框架和PHP框架也是一樣的:

  1. 配置載入:如何載入配置檔案的。
  2. 路由:分析框架如何通過URI執行對應業務的。
  3. ORM:ORM如何實現的。

這裡(1.)和(3.)無非就是載入個檔案和sql解析器的實現,我就忽略了,重點就看看路由的實現。

安裝

簡單帶過:

// Step1: 安裝beego
go get github.com/astaxie/beego

// Step2: 安裝bee
go get github.com/beego/bee

// Step3: 用bee工具建立一個新的專案
bee new beego-code-read
複製程式碼

程式碼分析

go有自己實現的http包,大多go框架也是基於這個http包,所以看beego之前我們先補充或者複習下這個知識點。如下:

go如何啟動一個http server

package main

import (
	// 匯入net/http包
	"net/http"
)

func main() {
	// ------------------ 使用http包啟動一個http服務 方式一 ------------------
	// *http.Request http請求內容例項的指標
	// http.ResponseWriter 寫http響應內容的例項
	http.HandleFunc("/v1/demo", func(w http.ResponseWriter, r *http.Request) {
		// 寫入響應內容
		w.Write([]byte("Hello TIGERB !\n"))
	})
	// 啟動一個http服務並監聽8888埠 這裡第二個引數可以指定handler
	http.ListenAndServe(":8888", nil)
}

// 測試我們的服務
// --------------------
// 啟動:bee run
// 訪問: curl "http://127.0.0.1:8888/v1/demo"
// 響應結果:Hello TIGERB !
複製程式碼

ListenAndServe是對http.Server的進一步封裝,除了上面的方式,還可以使用http.Server直接啟服務,這個需要設定Handler,這個Handler要實現Server.Handler這個介面。當請求來了會執行這個Handler的ServeHTTP方法,如下:

package main

// 匯入net/http包
import (
	"net/http"
)

// DemoHandle server handle示例
type DemoHandle struct {
}

// ServeHTTP 匹配到路由後執行的方法
func (DemoHandle) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	w.Write([]byte("Hello TIGERB !\n"))
}

func main() {
	// ------------------ 使用http包的Server啟動一個http服務 方式二 ------------------
	// 初始化一個http.Server
	server := &http.Server{}
	// 初始化handler並賦值給server.Handler
	server.Handler = DemoHandle{}
	// 繫結地址
	server.Addr = ":8888"

	// 啟動一個http服務
	server.ListenAndServe()

}

// 測試我們的服務
// --------------------
// 啟動:bee run
// 訪問: curl "http://127.0.0.1:8888/v1/demo"
// 響應結果:Hello TIGERB !
複製程式碼

beego路由分析

接下里我們開始看beego的程式碼。拿訪問"http://127.0.0.1:8080/"來說,對於beego程式碼來說有三個關鍵點,分別如下:

  1. 啟動:main.go -> beego.Run()

  2. 註冊路由:routers\router.go -> beego.Router("/", &controllers.MainController{})

  3. 控制器:controllers\default.go -> Get()

下面來看3個關鍵點的詳細分析:

beego.Run()主要的工作

// github.com/astaxie/beego/beego.go
func Run(params ...string) {
	// 啟動http服務之前的一些初始化 忽略 往下看
	initBeforeHTTPRun()

	// http服務的ip&port設定
	if len(params) > 0 && params[0] != "" {
		strs := strings.Split(params[0], ":")
		if len(strs) > 0 && strs[0] != "" {
			BConfig.Listen.HTTPAddr = strs[0]
		}
		if len(strs) > 1 && strs[1] != "" {
			BConfig.Listen.HTTPPort, _ = strconv.Atoi(strs[1])
		}
	}

	// 又一個run 往下看
	BeeApp.Run()
}
複製程式碼
// github.com/astaxie/beego/app.go
func (app *App) Run(mws ...MiddleWare) {
	// ... 省略 

	// 看了下這裡app.Server的型別就是*http.Server 也就是說用的原生http包 且是上面“go如何啟動一個http server”中的第二種方式
	app.Server.Handler = app.Handlers

	// ... 省略

	if BConfig.Listen.EnableHTTP {
		go func() {
			app.Server.Addr = addr
			logs.Info("http server Running on http://%s", app.Server.Addr)

			// 預設配置false不強制tcp4
			if BConfig.Listen.ListenTCP4 {
				//...
				// 忽略 預設false
			} else {
				// 關鍵點 ListenAndServe: app.Server的型別就是*http.Server 所以這裡就啟動了http服務 
				if err := app.Server.ListenAndServe(); err != nil {
					logs.Critical("ListenAndServe: ", err)
					time.Sleep(100 * time.Microsecond)
					endRunning <- true
				}
			}
		}()
	}
	// 阻塞到服務啟動
	<-endRunning
}

// 看到這裡http已經啟動了 而且是註冊Handler的方式
複製程式碼

接著去找這個Handler的ServeHTTP方法,通過上面的這段程式碼app.Server.Handler = app.Handlers,我們找到了下面的定義,Handler即是ControllerRegister的值,所以每次親求來了就會去執行ControllerRegister.ServeHTTP(rw http.ResponseWriter, r *http.Request)

// src/github.com/astaxie/beego/app.go
func init() {
	// 呼叫 建立beego框架例項的方法
	BeeApp = NewApp()
}

// App結構體
type App struct {
	// 關鍵的請求回撥Handler
	Handlers *ControllerRegister
	// http包的服務
	Server   *http.Server
}

func NewApp() *App {
	// 初始化http handler
	cr := NewControllerRegister()
	// 建立beego 例項
	app := &App{Handlers: cr, Server: &http.Server{}}
	return app
}

複製程式碼

通過我們追beego.Run()的程式碼,目前我們得到的結論就是:

  1. 使用的http包啟動的服務
  2. 沒有使用http.HandleFun()的定義路由策略,而是註冊Handler的方式

所以beego就是通過beego.Router()自己管理路由,如果http請求來了,回撥ControllerRegister.ServeHTTP(rw http.ResponseWriter, r *http.Request)方法,在ControllerRegister.ServeHTTP(rw http.ResponseWriter, r *http.Request)方法中去匹配路由並執行對應的controller 也就是beegoControllerInterface型別的控制器的方法,比如RESTFUL或者自定義啊等。

beego.Router() 如何註冊路由

首先路由檔案是如何載入的,我們發現在main.go檔案裡匯入了路由包:

package main

import (
	// 匯入routers包 只執行init方法
	_ "beego-code-read/routers"

	"github.com/astaxie/beego"
)

func main() {
	beego.Run()
}
複製程式碼

上面我們啟動了http服務,接著關鍵就是beego.Router()如何註冊路由了。追了下程式碼如下:

beego.Router() 
-> BeeApp.Handlers.Add(rootpath, c, mappingMethods...) 
-> ControllerRegister.addWithMethodParams(pattern, c, nil, mappingMethods...) 
-> ControllerRegister.addToRouter(method, pattern string, r *ControllerInfo) 
-> *Tree.AddRouter(pattern string, runObject interface{})
複製程式碼

最後就是在*Tree.AddRouter()完成了路由註冊,這裡的程式碼邏輯暫時就先不看了,至此這個beego框架的流程就其本理順了,最後我們在回頭總結下整個流程如下圖:

備註:go匯入包相當於入棧過程,先import後執行init

https://user-gold-cdn.xitu.io/2018/12/17/167b9b2614263118?w=490&h=2416&f=png&s=99804

beego框架程式碼分析

相關文章