这篇文章带你搞懂Go package和Go Module,保姆级教程教会你如何基于Go Module引用本项目里的package,第三方Module和本地Module。欢迎关注本人知乎和vx,长期分享Go技术栈和面试题。
package定义
package本质上就是一个目录,目录里包含有一个或者多个Go源程序文件,或者package。也就是说package里面还可以嵌套包含子package。
每个Go源文件都属于一个package,在源文件开头指定package名称
package的代码结构示例如下:
package里的变量、函数、结构体、方法等如果要被本package外的程序引用,需要在命名的时候首字母大写。
如果首字母小写,那就只能在同一个package里面被使用。
注意:这里说的是同一个package,不是同一个文件。同一个package下,如果有多个源程序文件是声明的该package,那这些源程序文件里的变量、函数、结构体等,即使不是首字母大写,也可以互相跨文件直接调用,不用额外import。
package的使用分为4类情况:
- 使用Go标准库自带的package,比如fmt。
- 使用go get获取到的第三方package/module
- 使用项目内部的package
- 使用其它项目的package/module
import语法示例
普通
使用import路径里面定义的package名称来访问package里的方法,结构体等,而不是路径名称。
举个例子,假设上面import的路径package2/package21这个目录下的Go源程序文件开头声明的package名称是realpackage,那访问这个package里的方法,结构体等要用http://realpackage.xxx来访问,而不是用http://package21.xxx来访问。
一句话总结:import的是路径,访问用package名称。最佳实践就是让两者保持一致。
别名
可以用别名newName来访问package里的成员,http://newName.xxx。这个在包名很长或者包名有重复的时候可以用到。
点操作
.比如以前要用package21.Hello()来调用package21这个包里的函数Hello,用了点操作后,可以直接调用函数Hello(),前面不用跟package名称。
下划线
_Go如何寻找import的package
在代码里import某个package的时候,Go是如何去寻找对应的package呢?这个和Go环境变量GO111MODULE有关系。GO111MODULE的值可以通过如下命令查到
on表示开启,off表示关闭。GO111MODULE是从Go 1.11开始引入,在随后的Go版本中Go Modules的行为有一些变化,具体可以参考GO111MODULE and Go Modules。
下面以Go1.16及以上版本详细讲下GO111MODULE关闭和开启的情况下,Go是如何寻找import的package的。
关闭GO111MODULE
- 先从$GOROOT/src里找。$GOROOT是Go的安装路径,$GOROOT/src是Go标准库存放的路径,比如fmt, strings等package都存放在$GOROOT/src里。$GOROOT的路径可以通过下面的命令查看到:
go env | grep ROOT // linux or mac
go env | findstr ROOT // windows - 如果从$GOROOT/src找不到,再从$GOPATH/src里找。$GOPATH是安装Go后就会有的一个环境变量,Linux和Mac的默认路径是/Users/用户名/go,WIndows默认路径是C:/Users/用户名/go
go env | grep PATH // linux or mac
go env | findstr PATH // windows
在Go 1.11之前,还没有Go Modules,如果想import一些自己开发的package,被import的package必须建在$GOPATH/src路径下。一般而言,一个工程项目一定会有自己写的若干个package,因此这也导致工程项目本身也通常建在了$GOPATH/src路径下。
开启GO111MODULE
Go 1.11开始,有了Go Modules,工程项目可以建在任何地方,代码在import某个package的时候,会按照如下顺序寻找package:
- 先从$GOROOT/src/路径找。(Go标准库会在这个路径找到)
- 再从$GOPATH/pkg/mod/路径找。(Go第三方库会在这个路径找到)
- 如果都找不到,再看当前项目有没有go.mod文件,有的话就从go.mod文件里指定的模块所在路径往下找。如果没有go.mod文件,那就直接提示package xxx is not in GOROOT。(自己开发的本地库可以通过这个方式找到)
官方推荐使用Go Modules,从Go1.16版本开始,GO111MODULES环境变量默认开启为on模式。
使用示例
不开启GO111MODULES时import package
- 项目建在$GOPATH/src下面
- import package的时候路径从$GOPATH/src往下找
开启GO111MODULES时import本项目里的package
- 项目可以建在任何地方
- 在项目所在根目录创建go.mod文件, module_name是模块名称
go mod init module_name - import项目本地的package时指定go.mod文件里的模块名称
比如module_name叫project,在这个模块里,main.go使用了本项目里的util包,那在main.go里按照如下格式import这个package
import "project/util" // project是模块名称, util是这个模块下的本地package
开启GO111MODULES时import第三方开发的Module
- 项目可以建在任何地方
- 在项目所在根目录创建go.mod文件
go mod init module_name - 下载所需第三方Module,比如gin
go get -u http://github.com/gin-gonic/gin - 代码里import对应的Module
import "http://github.com/gin-gonic/gin"
go mod tidy开启GO111MODULES时import本地的Module
replacemodule1module2module1module2Addmodule1module2Addmodule1module2module2module1go.modrequirereplacemodule2replacemodule2代码如下:
module1/main.go
module1/go.mod,注意require后面的module必须指定版本号
module2/func.go
module2/go.mod
go run main.goinit函数
init函数没有参数,没有返回值。
- 每个package里可以有多个init函数
- 每个源程序文件里也可以有多个init函数
- init函数不能被显示调用,在main()函数执行之前,自动被调用
- 同一个pacakge里的init函数调用顺序不确定
- 不同package的init函数,根据package import的依赖关系来决定调用顺序,比如package A里import了package B,那package B的init()函数就会比package A的init函数先调用。
- 无论package被import多少次,package里的init函数只会执行一次
注意事项
- package目录名和package目录下的Go源程序文件开头声明的包名可以不一样,不过一般还是写成一样,避免出错。
- 禁止循环导入package。
开源地址
文章和示例代码开源地址在GitHub: https://github.com/jincheng9/go-tutorial
公众号:coding进阶。关注公众号可以获取最新Go面试题和技术栈。