Go 编程实战系列(五)
上一篇《第一个 Go 程序:Hello World》中,我们创建了第一个 Go 项目,并执行过这样一条命令:
go
go mod init hello-go
执行完成后,项目目录中会出现一个 go.mod 文件。
很多初学者第一次看到它时可能会疑惑:
go.mod 到底有什么用?Go 项目的第三方库从哪里下载?为什么有时还会出现 go.sum?go get 和 go mod tidy 又有什么区别?
这些问题都与 Go Modules 有关。
Go Modules 是现代 Go 项目管理模块和第三方依赖的核心机制。无论以后开发 Gin Web 服务、GORM 数据库项目,还是大型微服务,都离不开它。
一、什么是 Go Modules?
在实际项目中,我们不可能所有功能都自己实现。
例如开发 Web 项目时可能使用:
Gin
GORM
Redis
JWT
Zap
Viper
这些第三方库都需要下载、记录版本并管理依赖关系。
Go Modules 就是 Go 官方提供的模块和依赖管理机制。
它主要解决以下问题:
-
项目依赖哪些第三方库?
-
每个依赖使用哪个版本?
-
第三方库从哪里下载?
-
不同项目如何使用不同依赖版本?
-
如何保证其他开发者构建出相同的项目?
Go Modules 的核心文件主要有两个:
go
go.mod
go.sum
其中:
go.mod 管理模块信息和依赖版本。
go.sum 用于记录依赖内容的校验信息。
二、创建 Go Modules 项目
首先创建项目:
arduino
mkdir go-module-demo
cd go-module-demo
初始化:
go
go mod init go-module-demo
执行后会生成:
go
go.mod
内容类似:
arduino
module go-module-demo
go 1.xx
第一行:
arduino
module go-module-demo
表示当前模块名称。
第二行:
go
go 1.xx
记录模块声明的 Go 语言版本信息。
项目结构:
go
go-module-demo
├── go.mod
└── main.go
这样,一个最基本的 Go Modules 项目就创建完成了。
三、module 名称应该怎么写?
学习阶段可以直接使用:
go
go mod init hello-go
对应:
arduino
module hello-go
但是在真实项目尤其是准备上传 GitHub 的项目中,更常见的是使用仓库地址作为模块路径。
例如项目地址假设为:
bash
github.com/zhangsan/go-shop
初始化:
go
go mod init github.com/zhangsan/go-shop
生成:
arduino
module github.com/zhangsan/go-shop
之后项目内部引用自己的包时,就可以使用:
arduino
import "github.com/zhangsan/go-shop/service"
因此实际开发中,模块路径通常与代码仓库路径保持一致。
四、安装第一个第三方依赖
下面以 Gin 为例。
创建 main.go:
go
package main
import "github.com/gin-gonic/gin"
func main() {
r := gin.Default()
r.GET("/", func(c *gin.Context) {
c.String(200, "Hello Go Modules")
})
r.Run(":8080")
}
此时项目使用了:
bash
github.com/gin-gonic/gin
可以执行:
arduino
go get github.com/gin-gonic/gin
Go 会自动下载 Gin 以及它需要的相关依赖。
再次打开 go.mod,会看到相应依赖信息。
五、require 是什么?
安装依赖以后,go.mod 中可能出现:
bash
require github.com/gin-gonic/gin v1.x.x
require 表示当前项目依赖某个模块。
基本格式:
javascript
require 模块路径 版本号
例如:
bash
require github.com/gin-gonic/gin v1.x.x
表示:
当前项目需要 Gin,并使用指定版本。
如果有多个依赖,通常会写成:
bash
require (
github.com/gin-gonic/gin v1.x.x
github.com/go-redis/redis/v9 v9.x.x
)
因此,通过 go.mod,我们可以非常清楚地知道一个项目需要哪些依赖。
六、直接依赖与间接依赖
打开实际项目中的 go.mod,经常会看到:
bash
require (
github.com/gin-gonic/gin v1.x.x
example.com/some-package v1.x.x // indirect
)
这里的:
arduino
// indirect
表示间接依赖。
假设我们的项目依赖 Gin:
markdown
我们的项目
↓
Gin
↓
其他第三方库
我们直接使用 Gin,因此 Gin 属于直接依赖。
Gin 自身又需要其他库,而我们的代码没有直接使用这些库,它们就是间接依赖。
可以理解为:
css
项目 → Gin → A → B
其中 Gin 是直接依赖,而 A、B 可能属于间接依赖。
Go Modules 会自动维护这些关系。
七、go.sum 是什么?
安装第三方依赖后,项目中通常还会出现:
go
go.sum
于是项目结构可能变为:
go
go-module-demo
├── go.mod
├── go.sum
└── main.go
go.sum 中会出现很多类似记录:
bash
github.com/xxx/xxx v1.2.3 h1:xxxx
github.com/xxx/xxx v1.2.3/go.mod h1:xxxx
它并不是另一个依赖列表,而主要记录模块版本相关内容的校验值。
它的作用之一就是帮助 Go 验证下载到的依赖内容是否与此前记录的一致。
因此实际项目中:
go
go.mod
go.sum
通常都应该提交到 Git 仓库。
不要因为 go.sum 看起来很长就随意删除它。
八、go get 的作用
go get 主要用于调整当前模块的依赖。
例如添加:
arduino
go get github.com/gin-gonic/gin
如果需要指定版本,可以使用:
kotlin
go get github.com/gin-gonic/gin@v1.x.x
升级到符合要求的新版本时,也可以通过指定版本进行调整。
例如:
dart
go get example.com/library@v1.5.0
执行之后,Go 会根据依赖关系更新:
go
go.mod
go.sum
实际项目中,推荐明确关注依赖版本变化,而不是看到升级提示就全部更新。
九、go mod tidy 非常重要
开发 Go 项目时,非常常用的一条命令是:
go
go mod tidy
它主要用于整理当前模块依赖。
例如代码中新增:
arduino
import "github.com/gin-gonic/gin"
执行:
go
go mod tidy
Go 会根据源码中实际使用的包补充所需模块信息。
反过来,如果你删除了某些代码,不再需要某个依赖,go mod tidy 也会清理不再需要的依赖记录。
因此可以简单理解为:
go
go mod tidy
就是让 go.mod 和 go.sum 与当前代码真正需要的依赖保持一致。
开发过程中建议经常执行。
十、常用 go mod 命令
查看 go mod 支持的功能:
bash
go help mod
初始化模块:
csharp
go mod init 项目名称
整理依赖:
go
go mod tidy
下载当前模块所需依赖:
go
go mod download
查看模块依赖关系:
css
go list -m all
检查模块依赖:
go
go mod verify
如果想查看模块依赖图,可以执行:
go
go mod graph
这些命令在以后分析大型项目依赖时非常有用。
十一、依赖下载到哪里?
很多初学者会问:
执行:
arduino
go get
之后,源码到底下载到哪里了?
可以执行:
bash
go env GOPATH
然后查看:
bash
GOPATH/pkg/mod
例如 macOS:
bash
/Users/用户名/go/pkg/mod
Linux:
bash
/home/用户名/go/pkg/mod
Windows:
vbnet
C:\Users\用户名\go\pkg\mod
这里就是 Go 的模块缓存目录。
还可以执行:
bash
go env GOMODCACHE
直接查看当前模块缓存路径。
通常不需要手动修改里面的文件,因为这些内容由 Go 工具链管理。
十二、什么是 GOPROXY?
Go 下载第三方模块时,可以通过模块代理访问依赖。
查看当前配置:
bash
go env GOPROXY
常见配置形式:
arduino
https://proxy.golang.org,direct
其中:
direct
表示代理无法获取模块时,可以尝试直接访问源码仓库。
如果当前网络环境访问默认代理不方便,也可以配置其他可用代理,例如:
bash
go env -w GOPROXY=https://goproxy.cn,direct
配置完成后查看:
bash
go env GOPROXY
需要注意,代理属于开发环境配置,并不是一定要写到项目源码中的内容。
十三、版本号是什么意思?
Go 模块通常使用语义化版本号。
例如:
v1.5.2
可以拆分为:
v 主版本.次版本.修订版本
也就是:
v1.5.2
│ │ │
│ │ └── 修订版本
│ └──── 次版本
└────── 主版本
通常:
-
修订版本用于错误修复;
-
次版本增加向后兼容的新功能;
-
主版本可能包含不兼容变化。
因此大型项目中,升级依赖前应阅读相应项目的更新说明,并通过测试确认兼容性。
十四、不要手动乱改 go.mod
虽然 go.mod 是文本文件,可以直接编辑,但初学阶段不建议随意修改依赖版本。
例如直接将:
v1.5.0
改成:
v99.0.0
如果这个版本根本不存在,项目自然无法正确下载依赖。
通常应该使用:
swift
go get 模块@版本
或者:
go
go mod tidy
让 Go 工具链帮助管理依赖关系。
十五、一个完整的依赖管理流程
假设我们创建一个 Gin 项目。
第一步:
bash
mkdir gin-demo
cd gin-demo
第二步:
csharp
go mod init gin-demo
第三步,在代码中引用:
arduino
import "github.com/gin-gonic/gin"
第四步:
go
go mod tidy
Go 会下载代码需要的依赖。
第五步运行:
arduino
go run .
如果将项目上传 Git,一般提交:
go
main.go
go.mod
go.sum
其他开发者下载项目后,可以执行:
go
go mod download
或者直接:
go
go build
Go 会根据模块信息获取所需依赖。
这就是 Go Modules 最核心的价值之一:让不同开发环境能够按照明确的模块依赖构建项目。
十六、本章小结
Go Modules 是现代 Go 项目最重要的基础知识之一。
本篇主要介绍了:
-
什么是 Go Modules
-
使用
go mod init初始化模块 -
go.mod的作用 -
module 模块路径
-
require 依赖声明
-
直接依赖与间接依赖
-
go.sum的作用 -
使用
go get调整依赖 -
使用
go mod tidy整理依赖 -
使用 GOPROXY 下载模块
-
模块缓存的位置
-
依赖版本的基本规则
以后无论使用 Gin、GORM、Redis、gRPC 还是其他 Go 第三方库,都需要和 Go Modules 打交道。
因此建议重点掌握这几个命令:
go
go mod init
go get
go mod tidy
go mod download
go list -m all
掌握 Go Modules 后,我们就能够更加规范地创建和管理真实的 Go 项目。
本章练习
- 创建一个
module-demo项目。 - 使用
go mod init module-demo初始化模块。 - 安装一个第三方 Go 库。
- 查看
go.mod中发生了什么变化。 - 查看生成的
go.sum。 - 执行
go mod tidy整理依赖。 - 使用
go list -m all查看依赖列表。 - 使用
go env GOMODCACHE找到模块缓存目录。
下一篇预告
《GOPATH 与 Go Modules 的区别》
学习 Go 时,经常会在旧教程中看到:
bash
GOPATH/src
GOPATH/pkg
GOPATH/bin
而新的 Go 项目却通常直接使用:
go
go.mod
go.sum
那么 GOPATH 到底是什么?为什么以前项目必须放在 GOPATH 中,而现在可以放在任意目录?已经有 Go Modules 以后,GOPATH 还有没有作用?
下一篇将详细介绍 GOPATH 与 Go Modules 的发展过程、目录结构、依赖管理方式、项目组织方式以及两者之间的核心区别,帮助大家彻底解决学习 Go 时经常遇到的 GOPATH 与 Modules 混淆问题。