Go语言基础之依赖管理
依赖管理
包的导入
1. 单行导入与多行导入
在 Go 语言中,一个包可包含多个 .go
文件(这些文件必须得在同一级文件夹中),只要这些 .go
文件的头部都使用 package
关键字声明了同一个包。
导入包主要可分为两种方式:
- 单行导入
import "fmt" |
- 多行导入
import( |
Go 语言中 导入的包,必须得用双引号包含。
2. 使用别名
在一些场景下,我们可能需要对导入的包进行重新命名,比如
- 我们导入了两个具有同一包名的包时产生冲突,此时这里为其中一个包定义别名
import ( |
- 我们导入了一个名字很长的包,为了避免后面都写这么长串的包名,可以这样定义别名
import hw "helloworldtestmodule" |
- 防止导入的包名和本地的变量发生冲突,比如 path 这个很常用的变量名和导入的标准包冲突。
import pathpkg "path" |
3. 使用点操作
如里在我们程序内部里频繁使用了一个工具包,比如 fmt,那每次使用它的打印函数打印时,都要 包名+方法名。
对于这种使用高频的包,可以在导入的时,就把它定义会 “自己人
”(方法是使用一个 .
),自己人的话,不分彼此,它的方法,就是我们的方法。
从此,我们打印再也不用加 fmt 了。
import . "fmt" |
但这种用法,会有一定的隐患,就是导入的包里可能有函数,会和我们自己的函数发生冲突。
4. 包的初始化
每个包都允许有一个 init
函数,当这个包被导入时,会执行该包的这个 init
函数,做一些初始化任务。
对于 init
函数的执行有几点需要注意
init
函数优先于main
函数执行- 在一个包引用链中,包的初始化是深度优先的。比如,有这样一个包引用关系:main→A→B→C,那么初始化顺序为
C.init→B.init→A.init→main
- 同一个包甚至同一个源文件,可以有多个 init 函数
- init 函数不能有入参和返回值
- init 函数不能被其他函数调用
- 同一个包内的多个 init 顺序是不受保证的
- 在 init 之前,其实会先初始化包作用域的常量和变量(常量优先于变量),具体可参考如下代码
package main |
5. 包的匿名导入
当我们导入一个包时,如果这个包没有被使用到,在编译时,是会报错的。
但是有些情况下,我们导入一个包,只想执行包里的 init
函数,来运行一些初始化任务,此时怎么办呢?
可以使用匿名导入,用法如下,其中下划线为空白标识符,并不能被访问
// 注册一个PNG decoder |
由于导入时,会执行 init 函数,所以编译时,仍然会将这个包编译到可执行文件中。
6. 导入的是路径还是包?
当我们使用 import 导入 testmodule/foo
时,初学者,经常会问,这个 foo
到底是一个包呢,还是只是包所在目录名?
import "testmodule/foo" |
结论是:
- 导入时,是按照目录导入。导入目录后,可以使用这个目录下的所有包。
- 出于习惯,包名和目录名通常会设置成一样,所以会让你有一种你导入的是包的错觉。
7. 相对导入和绝对导入
据我了解在 Go 1.10 之前,好像是不支持相对导入的,在 Go 1.10 之后才可以。
绝对导入:从 $GOPATH/src
或 $GOROOT
或者 $GOPATH/pkg/mod
目录下搜索包并导入
相对导入:从当前目录中搜索包并开始导入。就像下面这样
import ( |
使用相对导入的方式,项目可读性会大打折扣,不利用开发者理清整个引用关系。
所以一般更推荐使用绝对引用的方式。
Go Modules
在以前,Go 语言的的包依赖管理一直都被大家所诟病,Go官方也在一直在努力为开发者提供更方便易用的包管理方案,从最初的 GOPATH 到 GO VENDOR,再到最新的 GO Modules,虽然走了不少的弯路,但最终还是拿出了 Go Modules 这样像样的解决方案。
go module
是Go1.11版本之后官方推出的版本管理工具,并且从Go1.13版本开始,go module
将是Go语言默认的依赖管理工具。
在v1.14中足够成熟,可以用于生产环境,Gomod可以让项目目录不再约束在$GOPATH/src下,此时不同的项目可以使用同一个第三方库的不同版本。在推出gomod之后,基本没必要再使用老的GOPATH模式进行开发。
1. 配置环境变量
要启用go module
支持首先要设置环境变量GO111MODULE
,通过它可以开启或关闭模块支持,它有三个可选值:off
、on
、auto
,默认值是auto
。
GO111MODULE=off
禁用模块支持,编译时会从GOPATH
和vendor
文件夹中查找包。GO111MODULE=on
启用模块支持,编译时会忽略GOPATH
和vendor
文件夹,只根据go.mod
下载依赖。GO111MODULE=auto
,当项目在$GOPATH/src
外且项目根目录有go.mod
文件时,开启模块支持。
简单来说,设置GO111MODULE=on
之后就可以使用go module
了,以后就没有必要在GOPATH中创建项目了,并且还能够很好的管理项目依赖的第三方包信息。
使用 go module 管理依赖后会在项目根目录下生成两个文件go.mod
和go.sum
。
- 开启:
go env -w GO111MODULE=on
- 查看:
go env GO111MODULE
注意:
- 没使用go mod时依赖安装在
%GOPATH/src/github.com
- 使用go mod后依赖安装在
%GOPATH/pkg/mod
不管是第三方还是Google相关的包
注意:在使用GoLand工具时,不要配置Project GOPATH为当前工程目录,最好不要配置Project GOPATH,而是配置Module GOPATH
- 安装govendor : go get -u -v github.com/kardianos/govendor:
$GOPATH/src/vendor/vendor.json, 是从远端库添加依赖包
2. go mod相关命令
常用的go mod
命令如下
1. go mod init [module] |
3. go mod文件
go.mod文件记录了项目所有的依赖信息,其结构大致如下:
module gitee.com/lemon/learn-go |
其中:
以上的 go.mod 文件中,line 1 表示当前工程项目的模块名称; line 3 表示当前使用的 go mod init 时使用的go语言版本。require 代码块表示当前项目依赖的模块。
module
用来定义包名require
用来定义依赖包及版本
版本号格式为: v{major}.{minor}.{patch} ,一般而言,当 major 为0或者 patch 带有 pre, beta 时,表示版本不稳定。上述的 line 11、line 14 是伪版本号,会增加时间戳和修订标识符。indirect
表示间接引用
ndirect 表示间接依赖,当项目A引用B,B引用C1和C2时:如果B没有启用go.mod,则C1,C2作为间接引用出现在A的go.mod文件中。当B启用了go.mod,但是C2不在其go.mod中时,C2作为间接引用出现在 A 的go.mod中。使用go mod why -m pkg
可以显示为啥依赖该模块。replace替换成本地库(如自己的库,或者修改源代码的库)或者github对应的库(如翻墙失败)
replace 用于替换require中的依赖,在国内访问golang.org/x的各个包都需要翻墙,你可以在go.mod中使用replace替换成github上对应的库。一般有以下三种情况会使用到:
- 替换无法下载的包,如使用 github 中的包替换其它 URL 地址或版本的包
- 替换本地自己的包,如 line 33
- 替换fork的包,部分场景中需要对第三方包做修改且作为公共包对外时,会考虑fork一个到自己的仓库
- exclude
用于排除指定版本的包,在实际的工程环境中很少使用!
4. go sum文件
go.sum 用于记录各个依赖包版本的摘要,如果该依赖有 go.sum 还会记录 go.sum 的摘要。目的是用于防止依赖包被篡改,因为第三方模块的tag可能会被篡改,导致项目出现风险。go.sum 记录的依赖版本可能会比go.sum中使用到的多得多,这个并不会影响项目本地。在提交代码时,需要把 go.sum 和 go.mod 同时提交。当go.sum 合并分支发生冲突时,一般将两个go.sum文件内容合并后保存。
参考感谢
01-环境准备 · 语雀 (yuque.com)
3.2 依赖管理:超详细解读 Go Modules 应用 — Go编程时光 1.0.0 documentation (iswbm.com)
go mod · 语雀 (yuque.com)