第一個Go語言類庫:啟用、創(chuàng)建并發(fā)布第一個模塊
這是《Go語言簡易入門》系列內(nèi)容第
6篇,所有內(nèi)容列表見:https://yishulun.com/books/go-easy/目錄.html。所有源碼及資料在“程序員LIYI”公號回復(fù)“Go語言簡易入門”獲取。
模塊化是編程界的潮流,無論是前端Vue、微信小程序開發(fā),還是后端Node.js、Golang開發(fā),都講究模塊化。模塊化的本質(zhì)是分工協(xié)作,將功能相對獨立完善的代碼以模塊方式發(fā)布,以便在其它程序中復(fù)用,這與汽車廠分別制造發(fā)動機、輪胎、車門等零件,然后再組裝是一個道理。
GO111MODULE的由來
那么在Go語言開發(fā)中,如何進行模塊化開發(fā)呢?
默認(rèn)在官方教程《如何使用Go編程》中是不講這一塊的,環(huán)境變量GO111MODULE默認(rèn)是關(guān)閉的,運行官方示例也不會受到影響。但模塊化確實是非常重要的概念,是任何想認(rèn)真使用這門語言的開發(fā)者都避不開的。
上面我們提到了GO111MODULE,什么是GO111MODULE?
這個名稱中有三個數(shù)字一,不是字母“l(fā)”,是數(shù)字“1”,它表示在Go語言1.11版本中加入的環(huán)境變量。單從這個名稱來看,它很有可能被干掉,但事實上一真沒有。
在以前最早2009年Go語言發(fā)布的時候,源碼都是通過GOPATH管理的。怎么理解呢?在代碼中我們通過import關(guān)鍵字引入一個第三方類庫,Go程序會依次向GOPATH、GOROOT這兩個總目錄下去查找,哪個先查到,就用哪個。
但是我們知道,位于github上的類庫,master分支是最新源碼,這個源碼經(jīng)常變動,有時候我們使用的僅是歷史上的某個版本。有的開發(fā)者注意到了這一點,所以當(dāng)類庫重構(gòu)的時候,會將舊代碼打一個Release版本,這樣即使源碼修改了,只要我們找到歷史版本,也不影響我們程序的正常運行。
但是問題起來,有的程序需要用某個類庫的新版本,有的需要用舊版本,GOPATH只有一個,怎么處理這個矛盾呢?
那個時候我用的是最笨的方法,起新項目的時候,我將GOPATH目錄復(fù)制一份,并修改GOPATH變量為復(fù)制后的新目錄。一個項目對應(yīng)一個GOPATH,這樣不同項目的類庫版本就不會相互掣肘了。
可能不止我一個人這么使用。Go語言在1.5版本的時候,推出了一個vendor特征,它充許我們將當(dāng)前項目所用的所有第三方類庫,全部自動拷貝到一個叫做vendor的子目錄下。Go程序在編譯的時候,會首先向vendor目錄查找,如果沒找到,再向GOPATH、GOROOT目錄查找。
但是這種方式并沒有從根本上在Go語言中解決模塊化編程的問題,項目在共享和分發(fā)時,隨身攜帶許多第三方類庫的源碼,既占空間,又不利于統(tǒng)一升級類庫。如果第三方類庫在新版本中修復(fù)了一個bug,而我們需要更新,在多個項目中更新將是一件麻煩事。
后來,在Go語言1.11版本中,Go語言推出了GO111MODULE環(huán)境變量,及mod子指令,基于這個變量和子指令,可以完美模塊化編程了。接下來我們看看,一般是怎么做的。
創(chuàng)建并發(fā)布自己的第一個模塊
首先我們在GOPATH路徑外面創(chuàng)建一個目錄:
rixingyike/
first
main.go
str
reserve.go
這是兩個示例。first目錄是測試代碼,用于測試我們發(fā)布的模塊。str是我們準(zhǔn)備創(chuàng)建和發(fā)布的模塊。模塊位于多級目錄下,這是我們故意為之的。go語言的類包都是單名一級引入,但在實際的項目開發(fā)中,我們的模塊往往處于多級目錄下,我們看看這種情況一般是怎么處理的。
先看一下模塊str/reserve.go的源碼:
// go-easy/rixingyike/str/reserve.go
package str
import(
"fmt"
"github.com/kataras/iris/v12"
)
// Reverse 將其實參字符串以符文為單位左右反轉(zhuǎn)
func Reverse(s string) string {
r := []rune(s)
for i, j := 0, len(r)-1; i < len(r)/2; i, j = i+1, j-1 {
r[i], r[j] = r[j], r[i]
}
fmt.Println(string(r))
return string(r)
}
// StartServer ...
func StartServer() {
app := iris.New()
app.Handle("GET", "/user/{id:uint64}", func(ctx iris.Context) {
id, _ := ctx.Params().GetUint64("id")
ctx.JSON(id)
})
app.Listen(":8080")
}
我們在這個文件中引入了iris框架。我們需要在這個模塊中啟用go mod,執(zhí)行如下指令:
cd go-easy/rixingyike/str/
go mod init gitee.com/rxyk/go-easy/rixingyike/str
go mod指令后面是我們模塊的名稱,注意這里分部分,前面gitee.com/rxyk/go-easy是我們的倉庫地址,后面/rixingyike/str是倉庫中模塊的相對路徑。
這里有一個問題值得注意下,就是我們的module name是gitee.com/rxyk/go-easy/rixingyike/str,但是reserve.go文件中的package名稱卻是str,后者是引入以后在源碼中使用的單名,這兩個名稱是不需要也不能一致的。
接下來是關(guān)鍵,接著執(zhí)行指令:
go env -w GOPRIVATE="gitee.com"
git tag rixingyike/str/v1.0.0
git push origin rixingyike/str/v1.0.0
第一行指令,是將gitee.com這個域名添加進GOPRIVATE變量中,GOPRIVATE這個變量的值可以用逗號分隔添加多個值,但這里我們不需要添加多個。這一步的環(huán)境變量設(shè)置,是為了跳過對gitee.com域名的網(wǎng)絡(luò)代理。這是國內(nèi)網(wǎng)站,是不需要代理的。
第二行和第三行指令是創(chuàng)建一個新的tag,并提交到遠(yuǎn)端倉庫里。這里的關(guān)鍵是tag名,前面rixingyike/str/是模塊在倉庫中的相對路徑,后面v1.0.0才是模塊的版本號。默認(rèn)情況下,如果類庫在根目錄下是不需要這樣處理的,直接寫一個像v1.0.0這樣的版本號就可以了。
使用自己的第一個模塊并在本地調(diào)試
現(xiàn)在模塊已經(jīng)在線上發(fā)布了,接下來我們看一下怎么使用。
現(xiàn)在我們切換到first例目錄,并進行module初始化,執(zhí)行如下指令:
cd go-easy/rixingyike/first/
go mod init gitee.com/rxyk/go-easy/rixingyike/first
vim main.go ...
go get gitee.com/rxyk/go-easy/rixingyike/[email protected]
第二行指令中這個module的名稱,因為不需要對外發(fā)布,其實是無所謂的。接下來編輯main.go的源碼:
// go-easy/rixingyike/first/main.go
package main
import (
"fmt"
"gitee.com/rxyk/go-easy/rixingyike/str"
"github.com/nleeper/goment"
)
func main() {
fmt.Printf("%s\n",str.Reverse("hi,ly"))
var g,_ = goment.New("2021-01-23 09:30:26")
println(g.ToString())
str.StartServer()
}
在這個測試示例中,我們引入了goment和str這兩個模塊,其中后者是我們自己定義的。
我們看一下自動生成的go mod文件:
// go-easy/rixingyike/first/go.mod
module gitee.com/rxyk/go-easy/rixingyike/first
go 1.15
// replace gitee.com/rxyk/go-easy/rixingyike/str v1.0.0 => ../str
require (
gitee.com/rxyk/go-easy/rixingyike/str v1.0.0
github.com/nleeper/goment v1.4.0
)
輸出是這樣的:
yl,ih
yl,ih
2021-01-23 09:30:26 +0000 UTC
Now listening on: http://localhost:8080
Application started. Press CMD+C to shut down.
在這個文件中,第三行代碼replace,是將依賴包替換。有兩個作用:
如果某個類庫因為網(wǎng)絡(luò)原因,不能下載,可以用這個功能
我們自己開發(fā)的模塊,需要在本地調(diào)試
我們將這行配置反注釋一下,而main.go中的import引入代碼不需要修改,再運行代碼,調(diào)用的就是本地的str下的代碼了。這個設(shè)置,方便我們在本地進行模塊代碼,調(diào)試完成后再統(tǒng)一上傳。
關(guān)于模塊化編程,以上就是全部內(nèi)部了。接下來我們補充了解一些相關(guān)的概念。
如何臨時修改GO111MODULE變量?
有時候我們需要臨時修改這個變量的值,但并不需要永久修改。有兩個方法:
go env -w GO111MODULE=on
export GO111MODULE=on
這是兩種方式,以第二種效果最佳。第一種方式go env -w *是一種Go語言提供的通用的編輯環(huán)境變量的方式。
開啟go mod后,還能再使用vendor統(tǒng)一打包源碼嗎?
可以的,在項目模塊目錄下,例如str,執(zhí)行:
go mod vendor
這樣就會在str目錄下生成一個vendor子目錄,它里面有所有的依賴包。
GO111MODULE有哪些有效值?
有三個值:
GO111MODULE=off,不支持module功能,此時查找依賴包的次序是:vendor、GOPATH、GOROOT。
GO111MODULE=on,支持使用modules,會從vendor目錄下查找,但不會去GOPATH、GOROOT目錄下查。
GO111MODULE=auto,是默認(rèn)值,自動性取決于上下文目錄。$GOPATH/src之中的項目繼續(xù)使用GOPATH模式;$GOPATH/src之外的項目使用模塊化模式。
go mod指令,除init外,還有哪些子指令?
相關(guān)指令:
download download modules to local cache (下載依賴的module到本地cache))
edit edit go.mod from tools or scripts (編輯go.mod文件)
graph print module requirement graph (打印模塊依賴圖))
init initialize new module in current directory (在當(dāng)前文件夾下初始化一個新的module, 創(chuàng)建go.mod文件))
tidy add missing and remove unused modules (增加丟失的module,去掉未使用的module)
vendor make vendored copy of dependencies (將依賴復(fù)制到vendor下)
verify verify dependencies have expected content (校驗依賴)
why explain why packages or modules are needed (解釋為什么需要依賴)
最常使用的子指令是init、download、tidy和vendor。
啟用go mod后,如何查詢和安裝指定版本的依賴包?
和原來是一樣的。可以使用:
go get github.com/kataras/iris/v12@latest
@符號后面是版本號,latest代表最新。這個版本就是git網(wǎng)站上的發(fā)行版標(biāo)簽。可以用如下指令查詢所有可用標(biāo)簽名:
go list -m -versions github.com/kataras/iris/v12
輸出:
v12.0.0 v12.0.1 v12.1.0 v12.1.1 v12.1.2 v12.1.3 v12.1.4 v12.1.5 v12.1.6 v12.1.7 v12.1.8 v12.2.0-alpha v12.2.0-alpha2
其中地址中的v12是什么?它是該倉庫的一個分支。它還有另一個分支:v0.0.1。
引入國外的一些類庫,如何設(shè)置代理?
使用GOPROXY變量。我的設(shè)置是這樣的:
export GOPROXY="https://goproxy.io,https://mirrors.aliyun.com/goproxy/,https://goproxy.cn,direct"
三個網(wǎng)站的說明是這樣的:
https://goproxy.io?最早的Go模塊鏡像代理網(wǎng)站
https://mirrors.aliyun.com/goproxy/?阿里鏡像代理網(wǎng)站
https://goproxy.cn?七牛云贊助支持的代理網(wǎng)站 |
以逗號分隔。最后的direct代表到源地址下載。
我講明白沒有,歡迎留言討論。
2021年1月23日
本文寫作中參考了以下鏈接,一并致謝:
https://blog.csdn.net/yptsqc/article/details/105270530
https://morven.life/notes/the_go_language/
https://github.com/goproxy/goproxy.cn/issues/9
