# 包&module
在工程化的Go语言开发项目中,Go语言的源码复用是建立在包(package)基础之上的。本文介绍了Go语言中如何定义包、如何导出包的内容及如何导入其他包。
# Go语言的包(package)
包(package)
是多个Go源码的集合,是一种高级的代码复用方案,Go语言为我们提供了很多内置包,如fmt
、os
、io
等。
# 定义包
我们还可以根据自己的需要创建自己的包。一个包可以简单理解为一个存放.go
文件的文件夹。 该文件夹下面的所有go文件都要在代码的第一行添加如下代码,声明该文件归属的包。
package 包名
注意事项:
- 一个文件夹下面直接包含的文件只能归属一个
package
,同样一个package
的文件不能在多个文件夹下。 - 包名可以不和文件夹的名字一样,包名不能包含
-
符号。 - 包名为
main
的包为应用程序的入口包,这种包编译后会得到一个可执行文件,而编译不包含main
包的源代码则不会得到可执行文件。
# 可见性
如果想在一个包中引用另外一个包里的标识符(如变量、常量、类型、函数等)时,该标识符必须是对外可见的(public)。在Go语言中只需要将标识符的首字母大写就可以让标识符对外可见了。
举个例子, 我们定义一个包名为pkg2
的包,代码如下:
package pkg2
import "fmt"
// 包变量可见性
var a = 100 // 首字母小写,外部包不可见,只能在当前包内使用
// 首字母大写外部包可见,可在其他包中使用
const Mode = 1
type person struct { // 首字母小写,外部包不可见,只能在当前包内使用
name string
}
// 首字母大写,外部包可见,可在其他包中使用
func Add(x, y int) int {
return x + y
}
func age() { // 首字母小写,外部包不可见,只能在当前包内使用
var Age = 18 // 函数局部变量,外部包不可见,只能在当前函数内使用
fmt.Println(Age)
}
结构体中的字段名和接口中的方法名如果首字母都是大写,外部包可以访问这些字段和方法。例如:
type Student struct {
Name string //可在包外访问的方法
class string //仅限包内访问的字段
}
type Payer interface {
init() //仅限包内访问的方法
Pay() //可在包外访问的方法
}
# 包的导入
要在代码中引用其他包的内容,需要使用import
关键字导入使用的包。具体语法如下:
import "包的路径"
注意事项:
- import导入语句通常放在文件开头包声明语句的下面。
- 导入的包名需要使用双引号包裹起来。
- 包名是从
$GOPATH/src/
后开始计算的,使用/
进行路径分隔。 - Go语言中禁止循环导入包
# 单行导入
单行导入的格式如下:
import "包1"
import "包2"
# 多行导入
多行导入的格式如下:
import (
"包1"
"包2"
)
# 自定义包名
在导入包名的时候,我们还可以为导入的包设置别名。通常用于导入的包名太长或者导入的包名冲突的情况。具体语法格式如下:
import 别名 "包的路径"
单行导入方式定义别名:
import "fmt"
import m "github.com/Q1mi/studygo/pkg_test"
func main() {
fmt.Println(m.Add(100, 200))
fmt.Println(m.Mode)
}
多行导入方式定义别名:
import (
"fmt"
m "github.com/Q1mi/studygo/pkg_test"
)
func main() {
fmt.Println(m.Add(100, 200))
fmt.Println(m.Mode)
}
# init()初始化函数
在Go语言程序执行时导入包语句会自动触发包内部init()
函数的调用。需要注意的是: init()
函数没有参数也没有返回值。 init()
函数在程序运行时自动被调用执行,不能在代码中主动调用它。
包初始化执行的顺序如下图所示:
# init()函数执行顺序
Go语言包会从main
包开始检查其导入的所有包,每个包中又可能导入了其他的包。Go编译器由此构建出一个树状的包引用关系,再根据引用顺序决定编译顺序,依次编译这些包的代码。
在运行时,被最后导入的包会最先初始化并调用其init()
函数, 如下图示:
# Go module
Go1.1.1
版本发布(2018-08-24发布)已经过去几天,从官方的博客中看到,有两个比较突出的特色,一个就是今天讲的module
,模块概念。目前该功能还在试验阶段,有些地方还需要不断的进行完善。在官方正式宣布之前,打算不断修正这种支持。到时候就可以移除对GOPATH
和go get
命令的支持。
如果你想现在想就试试这个新功能module
,需要你将你的代码仓库放到GOPATH/src
目录之外。然后在那个目录下创建一个go.mod
文件,从文件树中运行go
命令。
# 主要概念介绍
module
是一个相关Go
包的集合,它是源代码更替和版本控制的单元。模块由源文件形成的go.mod
文件的根目录定义,包含go.mod
文件的目录也被称为模块根。moudles
取代旧的的基于GOPATH
方法来指定在工程中使用哪些源文件或导入包。模块路径是导入包的路径前缀,go.mod
文件定义模块路径,并且列出了在项目构建过程中使用的特定版本。
# go.mod文件
go.mod
文件定义module
路径以及列出其他需要在build
时引入的模块的特定的版本。例如下面的例子中,go.mod
声明example.com/m
路径时module
的根目录,同时也声明了module
依赖特定版本的golang.org/x/text
和gopkg.in/yaml.v2
。
module example.com/m
require (
golang.org/x/text v0.3.0
gopkg.in/yaml.v2 v2.1.0
)
123456
go.mod
文件还可以指定要替换和排除的版本,命令行会自动根据go.mod
文件来维护需求声明中的版本。如果想获取更多的有关go.mod
文件的介绍,可以使用命令go help go.mod
。
go.mod
文件用//
注释,而不用/**/
。文件的每行都有一条指令,由一个动作加上参数组成。例如:
odule my/thing
require other/thing v1.0.2
require new/thing v2.3.4
exclude old/thing v1.2.3
replace bad/thing v1.4.5 => good/thing v1.4.5
上面三个动词require
、exclude
、replace
分别表示:项目需要的依赖包及版本、排除某些包的特别版本、取代当前项目中的某些依赖包。
相同动作的命令可以放到一个动词+括号组成的结构中,例如:
require (
new/thing v2.3.4
old/thing v1.2.3
)
# 其他命令的支持
旧的版本,构建编译命令go build
中的参数没有-mod
参数,最新的版本现在多了这个,用来对go.mod
文件进行更新或其他使用控制。形式如:go build -mod [mode]
,其中mode有以下几种取值:readonly
,release
,vendor
。当执行go build -mod=vendor
的时候,会在生成可执行文件的同时将项目的依赖包放到主模块的vendo
r目录下。
go get -m [packages]
会将下载的依赖包放到GOPATH/pkg/mod
目录下,并且将依赖写入到go.mod
文件。go get -u=patch
会更新主模块下的所有依赖包。
如果遇到不熟悉的导入包,任何可以查找包含该引入包模块的go
命令,都会自动将该模块的最新版本添加到go.mod
文件中。同时也会添加缺失的模块,以及删除无用的module
。例如:go build
, go test
或者go list
命令。另外,有一个专门的命令go mod tidy
,用来查看和添加缺失的module
需求声明以及移除不必要的。
go.mod
文件是可读,也是可编辑的。go
命令行会自动更新go.mod
文件来维持一个标准格式以及精确的引入声明。
# Go mod命令
Go mod
提供了一系列操作modules
的命令,记住,所有的go
命令中现在已经内置了对module
的支持,而不仅仅是go mod
命令。例如使用go get
时,会经常自动在后台添加、移除、升级、降级依赖包版本。
命令语法:go mod [arguments]
。Go mod
提供的命令有下面几个,对于比较常用的命令进行详细说明。
download //下载模块到本地缓存,具体可以通过命令go env查看,其中环境变量GOCACHE就是缓存的地址,如果该文件夹的内容太大,可以通过命令go clean -cache
edit //从工具或脚本中编辑go.mod文件
graph //打印模块需求图
init //在当前目录下初始化新的模块
tidy //添加缺失的模块以及移除无用的模块
verify //验证依赖项是否达到预期的目的
why //解释为什么需要包或模块
# 环境变量——GO111MODULE
Go 1.11中
的module
支持临时环境变量——GO111MODULE
,它可以设置以下三个值:off
,on
或者auto
(默认)。
- 如果
GO111MODULE=off
,那么go
命令行将不会使用新的module
功能,相反的,它将会在vendor
目录下和GOPATH
目录中查找依赖包。也把这种模式叫GOPATH模式
。 - 如果
GO111MODULE=on
,那么go
命令行就会使用modules
功能,而不会访问GOPATH
。也把这种模式称作module-aware
模式,这种模式下,GOPATH
不再在build
时扮演导入的角色,但是尽管如此,它还是承担着存储下载依赖包的角色。它会将依赖包放在GOPATH/pkg/mod
目录下。 - 如果
GO111MODULE=auto
,这种模式是默认的模式,也就是说在你不设置的情况下,就是auto
。这种情况下,go
命令行会根据当前目录来决定是否启用module
功能。只有当当前目录在GOPATH/src
目录之外而且当前目录包含go.mod
文件或者其子目录包含go.mod
文件才会启用。
# 具体使用步骤:
- 首先将你的版本更新到最新的Go版本(>=1.11),如何更新版本可以自行百度。
- 通过go命令行,进入到你当前的工程目录下,在命令行设置临时环境变量
set GO111MODULE=on
; - 执行命令
go mod init
在当前目录下生成一个go.mod
文件,执行这条命令时,当前目录不能存在go.mod
文件。如果之前生成过,要先删除; - 如果你工程中存在一些不能确定版本的包,那么生成的
go.mod
文件可能就不完整,因此继续执行下面的命令; - 执行
go mod tidy
命令,它会添加缺失的模块以及移除不需要的模块。执行后会生成go.sum
文件(模块下载条目)。添加参数-v
,例如go mod tidy -v
可以将执行的信息,即删除和添加的包打印到命令行; - 执行命令
go mod verify
来检查当前模块的依赖是否全部下载下来,是否下载下来被修改过。如果所有的模块都没有被修改过,那么执行这条命令之后,会打印all modules verified
。 - 执行命令
go mod vendor
生成vendor文件夹,该文件夹下将会放置你go.mod
文件描述的依赖包,文件夹下同时还有一个文件modules.txt
,它是你整个工程的所有模块。在执行这条命令之前,如果你工程之前有vendor目录,应该先进行删除。同理go mod vendor -v
会将添加到vendor中的模块打印出来;