包
Go语言是使用包来组织源代码的,包(package)是多个 Go 源码的集合,是一种高级的代码复用方案
Go语言中为我们提供了很多内置包,如 fmt、os、io 等。
任何源代码文件必须属于某个包,同时源码文件的第一行有效代码必须是package pacakgeName
语句,通过该语句声明自己所在的包
1、包的基本概念
Go语言的包借助了目录树的组织形式,一般包的名称就是其源文件所在目录的名称
虽然Go语言没有强制要求包名必须和其所在的目录名同名,但还是建议包名和所在目录同名,这样结构更清晰
包可以定义在很深的目录中,包名的定义是不包括目录路径的,但是包在引用时一般使用全路径引用
包的习惯用法:
- 包名一般是小写的,使用一个简短且有意义的名称
- 包名一般要和所在的目录同名,也可以不同,包名中不能包含
-
等特殊符号 - 包名为 main 的包为应用程序的入口包,编译不包含 main 包的源码文件时不会得到可执行文件
- 一个文件夹下的所有源码文件只能属于同一个包,同样属于同一个包的源码文件不能放在多个文件夹下
2、包的导入
要在代码中引用其他包的内容,需要使用 import 关键字导入使用的包。具体语法如下:
import "包的路径"
注意事项:
- import 导入语句通常放在源码文件开头包声明语句的下面;
- 导入的包名需要使用双引号包裹起来;
包的导入有两种写法,分别是单行导入和多行导入
//单行导入
import "包 1 的路径"
import "包 2 的路径"
//多行导入
import (
"包 1 的路径"
"包 2 的路径"
)
注意:
包的绝对路径就是GOROOT/src/
或GOPATH
后面包的存放路径
3、包的引用格式
-
标准引用格式
package main import "fmt" func main() { fmt.Println("hello world") }
此时可以用
fmt.
作为前缀来使用fmt
包中的方法,这是常用的一种方式 -
自定义别名引用格式
package main import F "fmt" func main() { F.Println("hello world") }
其中 F 就是 fmt 包的别名,使用时我们可以使用
F.
来代替标准引用格式的fmt.
来作为前缀使用 fmt 包中的方法 -
省略引用格式
package main import . "fmt" func main() { //不需要加前缀 fmt. Println("hello world") }
这种格式相当于把 fmt 包直接合并到当前程序中,在使用 fmt 包内的方法是可以不用加前缀
fmt.
,直接引用 -
匿名引用格式
package main import ( _ "database/sql" "fmt" ) func main() { fmt.Println("hello world") }
在引用某个包时,如果只是希望执行包初始化的 init 函数,而不使用包内部的数据时,可以使用匿名引用格式
匿名导入的包与其他方式导入的包一样都会被编译到可执行文件中
使用标准格式引用包,但是代码中却没有使用包,编译器会报错。如果包中有 init 初始化函数,则通过
import _ "包的路径"
这种方式引用包,仅执行包的初始化函数,即使包没有 init 初始化函数,也不会引发编译器报错
4、init初始化函数
在每一个go 源文件中,都可以定义若干个如下格式的函数
func init() {
......
}
这种特殊的函数不接收任何参数也没有任何返回值,我们也不能在代码中主动调用它,当程序启动的时候,init函数会按照他们申明的顺序自动执行。
一个包的初始化过程是按照代码中引入的顺序来进行的,所有在该包中申明的init
函数都将被串行调用,并且 仅调用执行一次。每一个包初始化的时候都是都是先执行依赖包中的init
函数,再执行当前包中申明的init
函数。确保在程序的main函数在执行时所有依赖包都已经初始化完成
每一个包的初始化都是先从初始化包级别变量开始的。例如:
package main
import "fmt"
var x int8 = 10
const pi = 3.14
func init() {
fmt.Println("x:", x)
fmt.Println("pi:", pi)
sayHi()
}
func sayHi() {
fmt.Println("Hello World!")
}
func main() {
fmt.Println("你好,世界!")
}
在上面的代码中,我们了解了Go语言中包的定义及包的初始化过程,这让我们能够在开发时按照自己的需要定义包。
同时我们还学到了如何在我们的代码中引入其它的包,不过在本小节的所有示例中我们都是引入Go内置的包。
现代编程语言大多都允许开发者对外发布包/库,也支持开发者在自己的代码中引入第三方库。
这样的设计能够让广大开发者一起参与到语言的生态环境建设当中,把生态建设的更加完善
-
注意:
- 一个包可以有多个 init 函数,包加载时会执行全部的 init 函数,但并不能保证执行顺序,所以不建议在一个包中放入多个 init 函数,将需要初始化的逻辑放到一个 init 函数里面。
- 包不能出现环形引用的情况,比如包 a 引用了包 b,包 b 引用了包 c,如果包 c 又引用了包 a,则编译不能通过。
- 包的重复引用是允许的,比如包 a 引用了包 b 和包 c,包 b 和包 c 都引用了包 d。这种场景相当于重复引用了 d,这种情况是允许的,并且 Go 编译器保证包 d 的 init 函数只会执行一次
5、go module
在Go语言早期的版本中,我们编写Go项目代码时所依赖的所有第三方包都需要保存在GOPATH
这个目录下。这样的依赖管理存在致命的缺陷—不支持版本管理,同一个依赖包只能存在一个版本的代码,可是我们本地的多个项目完全可能依赖同一个第三方包的不同版本
因此就需要go module来解决这个棘手的问题
go module
是go 1.11
版本发布的依赖管理方案。从go 1.14版本开始推荐在生产环境使用,于go 1.16版本默认开启
使用go module之前需要设置环境变量:
- GO111MODULE=on
- GOPROXY=https://goproxy.io,directopen in new window
- GOPROXY=https://goproxy.cn,direct(国内的七牛云提供)
GO111MODULE 有三个值:off, on和auto(默认值)
- GO111MODULE=off,go命令行将不会支持module功能,寻找依赖包的方式将会沿用旧版本那种通过vendor目录或者GOPATH模式来查找。
- GO111MODULE=on,go命令行会使用modules,而一点也不会去GOPATH目录下查找。
- GO111MODULE=auto,默认值,go命令行将会根据当前目录来决定是否启用module功能。这种情况下可以分为两种情形:
- 当前目录在GOPATH之外且该目录包含go.mod文件 开启
- 当处于 GOPATH 内且没有 go.mod 文件存在时其行为会等同于 GO111MODULE=off
- 如果不使用 Go Modules, go get 将会从模块代码的 master 分支拉取
- 而若使用 Go Modules 则你可以利用 Git Tag 手动选择一个特定版本的模块代码
go module 有以下命令
命令 | 说明 |
---|---|
go mod download | download modules to local cache(根据go.mod文件下载依赖包) |
go mod edit | edit go.mod from tools or scripts(编辑go.mod) |
go mod graph | print module requirement graph (打印模块依赖图) |
go mod init | initialize new module in current directory(在当前目录初始化mod,生成go.mod文件) |
go mod tidy | add missing and remove unused modules(将项目文件中引入的依赖与go.mod进行比对,拉取缺少的模块,移除不用的模块) |
go mod vendor | make vendored copy of dependencies(将依赖复制到vendor下) |
go mod verify | verify dependencies have expected content (校验一个依赖包是否被篡改过) |
go mod why | explain why packages or modules are needed(解释为什么需要依赖) |
- 常用的有
init tdiy edit
GOPROXY
- 这个环境变量主要是用于设置Go代理模块(Go module proxy),其作用是用于Go在后续拉取模块版本时能够脱离原始的VCS模式,直接通过镜像站点快速拉取。
- GOPROXY 的默认值是:https://proxy.golang.org,direct,由于某些原因国内无法正常访问该地址,所以我们通常需要配置一个可访问的地址。目前社区使用比较多的有两个https://goproxy.cn和https://goproxy.io,当然如果你的公司有提供GOPROXY地址那么就直接使用。设置GOPAROXY的命令如下:
go env -w GOPROXY=https://goproxy.cn,direct
GOPROXY 允许设置多个代理地址,多个地址之间需使用英文逗号 “,” 分隔。最后的direct
是一个特殊指示符,用于指示 Go 回源到源地址去抓取(比如 GitHub 等)。当配置有多个代理地址时,如果第一个代理地址返回 404 或 410 错误时,Go 会自动尝试下一个代理地址,当遇见direct
时触发回源,也就是回到源地址去抓取
GOPRIVATE
- 设置了GOPROXY 之后,go 命令就会从配置的代理地址拉取和校验依赖包。
- 当我们在项目中引入了非公开的包(公司内部git仓库或 github 私有仓库等),此时便无法正常从代理拉取到这些非公开的依赖包,这个时候就需要配置 GOPRIVATE 环境变量
- GOPRIVATE用来告诉 go 命令哪些仓库属于私有仓库,不必通过代理服务器拉取和校验
GOPRIVATE 的值也可以设置多个,多个地址之间使用英文逗号 “,” 分隔。我们通常会把自己公司内部的代码仓库设置到 GOPRIVATE 中,例如:
go env -w GOPRIVATE="git.mycompany.com"
这样在拉取以git.mycompany.com为路径前缀的依赖包时就能正常拉取了。
此外,如果公司内部自建了 GOPROXY 服务,那么我们可以通过设置 GONOPROXY=none,允许通内部代理拉取私有仓库的包。
使用go get命令下载指定版本的依赖包:
执行go get
命令,在下载依赖包的同时还可以指定依赖包的版本。
- 运行
go get -u
命令会将项目中的包升级到最新的次要版本或者修订版本; - 运行
go get -u=patch
命令会将项目中的包升级到最新的修订版本; - 运行
go get [包名]@[版本号]
命令会下载对应包的指定版本或者将对应包升级到指定的版本。
提示:
go get [包名]@[版本号]
命令中版本号可以是 x.y.z 的形式,例如 go get foo@v1.2.3,也可以是 git 上的分支或 tag,例如 go get foo@master,还可以是 git 提交时的哈希值,例如 go get foo@e3702bed2
-
项目中使用
在 GOPATH 目录下新建一个目录,并使用
go mod init
初始化生成 go.mod 文件go.mod 文件一旦创建后,它的内容将会被 go toolchain 全面掌控,go toolchain 会在各类命令执行时,比如
go get
、go build
、go mod
等修改和维护 go.mod 文件go.mod 提供了 module、require、replace 和 exclude 四个命令:
- module 语句指定包的名字(路径);
- require 语句指定的依赖项模块;
- replace 语句可以替换依赖项模块;
- exclude 语句可以忽略依赖项模块。
初始化生成的 go.mod 文件如下所示:
添加依赖
新建一个 main.go 文件,写入以下代码
package main
import (
"net/http"
"github.com/labstack/echo"
)
func main() {
e := echo.New()
e.GET("/", func(c echo.Context) error {
return c.String(http.StatusOK, "Hello, World!")
})
e.Logger.Fatal(e.Start(":1323"))
}
执行go mod tidy
运行代码会发现 go mod 会自动查找依赖自动下载
go module 安装 package 的原则是先拉取最新的 release tag,若无 tag 则拉取最新的 commit
go 会自动生成一个 go.sum 文件来记录 dependency tree。
执行脚本go run main.go
,就可以运行项目。
可以使用命令go list -m -u all
来检查可以升级的 package,使用go get -u need-upgrade-package
升级后会将新的依赖版本更新到 go.mod
比如:go get -u github.com/labstack/gommon
也可以使用go get -u
升级所有依赖。
使用 replace 替换无法直接获取的 package:
由于某些已知的原因,并不是所有的 package 都能成功下载,比如:golang.org 下的包。
modules 可以通过在 go.mod 文件中使用 replace 指令替换成 github 上对应的库,比如:golang.org 下的包
replace (
golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a => github.com/golang/crypto v0.0.0-20190313024323-a1f597ede03a
)
modules 可以通过在 go.mod 文件中使用 replace 指令替换成 github 上对应的库,比如:
replace (
golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a => github.com/golang/crypto v0.0.0-20190313024323-a1f597ede03a
)
go install
命令将项目打包安装为可执行文件,在安装在GOPATH的bin目录下,go install执行的项目 必须有main方法