1. 包

Go语言是使用包来组织源代码的,包(package)是多个 Go 源码的集合,是一种高级的代码复用方案。Go语言中为我们提供了很多内置包,如 fmt、os、io 等。

任何源代码文件必须属于某个包,同时源码文件的第一行有效代码必须是package pacakgeName 语句,通过该语句声明自己所在的包。

1.1 包的基本概念

Go语言的包借助了目录树的组织形式,一般包的名称就是其源文件所在目录的名称,虽然Go语言没有强制要求包名必须和其所在的目录名同名,但还是建议包名和所在目录同名,这样结构更清晰。

包可以定义在很深的目录中,包名的定义是不包括目录路径的,但是包在引用时一般使用全路径引用。

包的习惯用法:

  • 包名一般是小写的,使用一个简短且有意义的名称。
  • 包名一般要和所在的目录同名,也可以不同,包名中不能包含- 等特殊符号。
  • 包名为 main 的包为应用程序的入口包,编译不包含 main 包的源码文件时不会得到可执行文件。
  • 一个文件夹下的所有源码文件只能属于同一个包,同样属于同一个包的源码文件不能放在多个文件夹下。

1.2 包的导入

要在代码中引用其他包的内容,需要使用 import 关键字导入使用的包。具体语法如下:

import "包的路径"
1

注意事项:

  • import 导入语句通常放在源码文件开头包声明语句的下面;
  • 导入的包名需要使用双引号包裹起来;

包的导入有两种写法,分别是单行导入和多行导入。

单行导入

import "包 1 的路径"
import "包 2 的路径"
1
2

多行导入

import (
    "包 1 的路径"
    "包 2 的路径"
)
1
2
3
4

1.3 包的导入路径

包的绝对路径就是GOROOT/src/GOPATH后面包的存放路径,如下所示:

import "lab/test"
import "database/sql/driver"
import "database/sql"
1
2
3

上面代码的含义如下:

  • test 包是自定义的包,其源码位于GOPATH/lab/test 目录下;
  • driver 包的源码位于GOROOT/src/database/sql/driver 目录下;
  • sql 包的源码位于GOROOT/src/database/sql 目录下。

1.4 包的引用格式

包的引用有四种格式,下面以 fmt 包为例来分别演示一下这四种格式。

  1. 标准引用格式

    import "fmt"
    
    1

    此时可以用fmt.作为前缀来使用 fmt 包中的方法,这是常用的一种方式。

    package main
    import "fmt"
    func main() {
        fmt.Println("码神之路")
    }
    
    1
    2
    3
    4
    5
  2. 自定义别名引用格式

    在导入包的时候,我们还可以为导入的包设置别名,如下所示:

    import F "fmt"
    
    1

    其中 F 就是 fmt 包的别名,使用时我们可以使用F.来代替标准引用格式的fmt.来作为前缀使用 fmt 包中的方法

    package main
    import F "fmt"
    func main() {
        F.Println("码神之路")
    }
    
    1
    2
    3
    4
    5
  3. 省略引用格式

    import . "fmt"
    
    1

    这种格式相当于把 fmt 包直接合并到当前程序中,在使用 fmt 包内的方法是可以不用加前缀fmt.,直接引用。

    package main
    import . "fmt"
    func main() {
        //不需要加前缀 fmt.
        Println("码神之路")
    }
    
    1
    2
    3
    4
    5
    6
  4. 匿名引用格式

    在引用某个包时,如果只是希望执行包初始化的 init 函数,而不使用包内部的数据时,可以使用匿名引用格式,如下所示:

    import _ "fmt"
    
    1

    匿名导入的包与其他方式导入的包一样都会被编译到可执行文件中。

    使用标准格式引用包,但是代码中却没有使用包,编译器会报错。如果包中有 init 初始化函数,则通过import _ "包的路径" 这种方式引用包,仅执行包的初始化函数,即使包没有 init 初始化函数,也不会引发编译器报错。

    package main
    import (
        _ "database/sql"
        "fmt"
    )
    func main() {
        fmt.Println("码神之路")
    }
    
    1
    2
    3
    4
    5
    6
    7
    8

注意:

  • 一个包可以有多个 init 函数,包加载时会执行全部的 init 函数,但并不能保证执行顺序,所以不建议在一个包中放入多个 init 函数,将需要初始化的逻辑放到一个 init 函数里面。
  • 包不能出现环形引用的情况,比如包 a 引用了包 b,包 b 引用了包 c,如果包 c 又引用了包 a,则编译不能通过。
  • 包的重复引用是允许的,比如包 a 引用了包 b 和包 c,包 b 和包 c 都引用了包 d。这种场景相当于重复引用了 d,这种情况是允许的,并且 Go 编译器保证包 d 的 init 函数只会执行一次。

2. go mod

go module 是Go语言从 1.11 版本之后官方推出的版本管理工具,并且从 Go1.13 版本开始,go module 成为了Go语言默认的依赖管理工具。

Modules 官方定义为:

Modules 是相关 Go 包的集合,是源代码交换和版本控制的单元。Go语言命令直接支持使用 Modules,包括记录和解析对其他模块的依赖性,Modules 替换旧的基于 GOPATH 的方法,来指定使用哪些源文件。

使用go module之前需要设置环境变量:

  1. GO111MODULE=on
  2. GOPROXY=https://goproxy.io,directopen in new window
  3. 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 mod 有以下命令:

命令说明
downloaddownload modules to local cache(下载依赖包)
editedit go.mod from tools or scripts(编辑go.mod)
graphprint module requirement graph (打印模块依赖图)
initinitialize new module in current directory(在当前目录初始化mod)
tidyadd missing and remove unused modules(拉取缺少的模块,移除不用的模块)
vendormake vendored copy of dependencies(将依赖复制到vendor下)
verifyverify dependencies have expected content (验证依赖是否正确)
whyexplain why packages or modules are needed(解释为什么需要依赖)
  • 常用的有 init tdiy edit

使用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。

2.1 项目中使用

  1. 在 GOPATH 目录下新建一个目录,并使用go mod init初始化生成 go.mod 文件。

    go.mod 文件一旦创建后,它的内容将会被 go toolchain 全面掌控,go toolchain 会在各类命令执行时,比如go getgo buildgo mod等修改和维护 go.mod 文件。

    go.mod 提供了 module、require、replace 和 exclude 四个命令:

    • module 语句指定包的名字(路径);
    • require 语句指定的依赖项模块;
    • replace 语句可以替换依赖项模块;
    • exclude 语句可以忽略依赖项模块。

    初始化生成的 go.mod 文件如下所示:

    module hello
    
    go 1.13
    
    1
    2
    3
  2. 添加依赖。

    新建一个 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"))
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12

    执行go mod tidy运行代码会发现 go mod 会自动查找依赖自动下载

    go: finding module for package github.com/labstack/echo
    go: found github.com/labstack/echo in github.com/labstack/echo v3.3.10+incompatible
    go: finding module for package github.com/stretchr/testify/assert
    go: finding module for package github.com/labstack/gommon/log
    go: finding module for package github.com/labstack/gommon/color
    go: finding module for package golang.org/x/crypto/acme/autocert
    go: found github.com/labstack/gommon/color in github.com/labstack/gommon v0.3.1
    go: found github.com/labstack/gommon/log in github.com/labstack/gommon v0.3.1
    go: found golang.org/x/crypto/acme/autocert in golang.org/x/crypto v0.0.0-20220112180741-5e0467b6c7ce
    go: found github.com/stretchr/testify/assert in github.com/stretchr/testify v1.7.0
    
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11

    go.mod中的内容:

    module learning09
    
    go 1.17
    
    require github.com/labstack/echo v3.3.10+incompatible
    
    require (
    	github.com/labstack/gommon v0.3.1 // indirect
    	github.com/mattn/go-colorable v0.1.11 // indirect
    	github.com/mattn/go-isatty v0.0.14 // indirect
    	github.com/valyala/bytebufferpool v1.0.0 // indirect
    	github.com/valyala/fasttemplate v1.2.1 // indirect
    	golang.org/x/crypto v0.0.0-20220112180741-5e0467b6c7ce // indirect
    	golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 // indirect
    	golang.org/x/sys v0.0.0-20211103235746-7861aae1554b // indirect
    	golang.org/x/text v0.3.6 // indirect
    )
    
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18

    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升级所有依赖。

  3. 一般使用包之前,是首先执行go get命令,先下载依赖。比如 github.com/labstack/echo

使用 replace 替换无法直接获取的 package:

由于某些已知的原因,并不是所有的 package 都能成功下载,比如:golang.org 下的包。

modules 可以通过在 go.mod 文件中使用 replace 指令替换成 github 上对应的库,比如:

replace (
    golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a => github.com/golang/crypto v0.0.0-20190313024323-a1f597ede03a
)
1
2
3

或者

replace golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a => github.com/golang/crypto v0.0.0-20190313024323-a1f597ede03a
1

go install命令将项目打包安装为可执行文件,在安装在GOPATH的bin目录下,go install执行的项目 必须有main方法