Java 转 go 学习 - 项目管理

文章目录

  • 包管理
    • [1.1 go.mod](#1.1 go.mod)
    • [1.2 go.sum](#1.2 go.sum)
    • [1.3 初始化一个项目](#1.3 初始化一个项目)
    • [1.4 三种编译方式](#1.4 三种编译方式)
      • [1.4.1 File 模式](#1.4.1 File 模式)
      • [1.4.2 Package 模式](#1.4.2 Package 模式)
      • [1.4.3 Directory 模式](#1.4.3 Directory 模式)
    • [1.5 查看依赖信息](#1.5 查看依赖信息)

包管理

Go 的包管理体系经历了「无官方管理 → 第三方工具混战 → 官方标准化」的演进过程,核心是解决 依赖隔离、版本控制、环境统一 三大问题。

  1. GOPATH :GO 1.0 发布仅仅提供 GOPATH 环境变量机制,所有项目和依赖必须放在 GOPATH/src 目录下(固定目录结构:GOPATH/{src/bin/pkg}),依赖通过 go get 下载,但仅拉取最新版本 ,无版本号概念,也无法指定版本,项目共享 GOPATH 下的依赖,不同项目依赖同一个包的不同版本会冲突.
  2. vendor :GO 1.5 引入,GO 1.6 默认,核心作用是本地化存储项目依赖的源代码副本 ,解决依赖版本一致性问题,比如 Java 的 lib 版本,本质上是让项目自带依赖,避免版本不一样导致的编译或者运行异常.
    • 项目根目录下的 vendor 文件夹会优先被 go 编译器识别,存放当前项目的专属依赖.
  3. GO MODULE: Go 1.11 正式引入、Go 1.13 成为默认依赖管理工具的官方依赖管理方案,默认开启 GO111MODULE=auto,GO 1.16 成为默认模式,废弃 GOPATH 默认,彻底解决了传统 GOPATH 模式的核心痛点(全局依赖、版本冲突、多版本共存难)。它的核心目标是:让每个项目拥有独立的依赖版本管理,实现依赖的可复现、可追溯、可隔离。

1.1 go.mod

一个包含 go.mod 文件的目录就是一个 Module,是 Go 依赖管理的基本单元,比如下面 1.3 节生成的 go.mod:

go 复制代码
module go-learn-1

go 1.25.0

require github.com/gin-gonic/gin v1.12.0

require (
	github.com/bytedance/gopkg v0.1.3 // indirect
	github.com/bytedance/sonic v1.15.0 // indirect
	github.com/bytedance/sonic/loader v0.5.0 // indirect
	github.com/cloudwego/base64x v0.1.6 // indirect
	github.com/gabriel-vasile/mimetype v1.4.12 // indirect
	github.com/gin-contrib/sse v1.1.0 // indirect
	github.com/go-playground/locales v0.14.1 // indirect
	github.com/go-playground/universal-translator v0.18.1 // indirect
	github.com/go-playground/validator/v10 v10.30.1 // indirect
	github.com/goccy/go-json v0.10.5 // indirect
	github.com/goccy/go-yaml v1.19.2 // indirect
	github.com/json-iterator/go v1.1.12 // indirect
	github.com/klauspost/cpuid/v2 v2.3.0 // indirect
	github.com/leodido/go-urn v1.4.0 // indirect
	github.com/mattn/go-isatty v0.0.20 // indirect
	github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
	github.com/modern-go/reflect2 v1.0.2 // indirect
	github.com/pelletier/go-toml/v2 v2.2.4 // indirect
	github.com/quic-go/qpack v0.6.0 // indirect
	github.com/quic-go/quic-go v0.59.0 // indirect
	github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
	github.com/ugorji/go/codec v1.3.1 // indirect
	go.mongodb.org/mongo-driver/v2 v2.5.0 // indirect
	golang.org/x/arch v0.22.0 // indirect
	golang.org/x/crypto v0.48.0 // indirect
	golang.org/x/net v0.51.0 // indirect
	golang.org/x/sys v0.41.0 // indirect
	golang.org/x/text v0.34.0 // indirect
	google.golang.org/protobuf v1.36.10 // indirect
)
  • module :项目模块名称为 go-learn-1,相当于项目的唯一 ID,在项目内导入自己的包时,需要以这个名称为前缀(比如 import "go-learn-1/utils"),Go Modules 基于这个名称识别项目边界,管理依赖.
  • require:声明项目依赖的模块.
  • indirect:注释表示这是间接依赖(不是直接导入的,而是 gin 框架本身依赖的包).

1.2 go.sum

go.sum 是依赖的 "哈希校验清单",自动生成,记录每个依赖版本的加密哈希值,作用:

  • 校验依赖完整性:下载依赖时,Go 会对比本地哈希与 go.sum 中的值,防止依赖被篡改(中间人攻击).
  • 锁定版本:确保同一模块在不同环境下载的依赖版本完全一致.

比如:

go 复制代码
github.com/gin-gonic/gin v1.9.1 h1:k4675tC5Uf2H54fx43Cfd1UoTWpAa8lS8DMhBjAp+ro=
github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU=

每行格式:模块路径 版本 哈希值(h1: 开头是二进制哈希,go.mod 哈希单独列).

为什么需要两种哈希值:h1 开头的是用于校验源码完整性,而 go.mod 命令开头的是轻量校验依赖声明。 简单来说就是 h1 是源码文件生成的,用于确保源码没有被篡改,而 go.mod 不需要下载源码,只通过 go.mod 生成哈希,这种就是只校验依赖清单,比如执行 go mod graph,Go 只需要知道各个模块的依赖关系,不会下载完整源码,此时只校验 go.mod 的哈希即可,如果校验通过就可以直接从 go.mod 里面读出依赖,大幅减少网络请求。

那如果哈希值被修改了,比如我们修改 go.sum 里面的一个哈希,这种情况下 go 会探测到缓存(GOMODCACHE )里面的依赖的哈希和 go.sum 不一致,编译失败,比如下面的例子,我们修改下一个依赖的 hash,将后面的 = 去掉:

下面就是 1.3 节初始化项目之后生成的 go.sum:

go 复制代码
github.com/bytedance/gopkg v0.1.3 h1:TPBSwH8RsouGCBcMBktLt1AymVo2TVsBVCY4b6TnZ/M=
github.com/bytedance/gopkg v0.1.3/go.mod h1:576VvJ+eJgyCzdjS+c4+77QF3p7ubbtiKARP3TxducM=
github.com/bytedance/sonic v1.15.0 h1:/PXeWFaR5ElNcVE84U0dOHjiMHQOwNIx3K4ymzh/uSE=
github.com/bytedance/sonic v1.15.0/go.mod h1:tFkWrPz0/CUCLEF4ri4UkHekCIcdnkqXw9VduqpJh0k=
github.com/bytedance/sonic/loader v0.5.0 h1:gXH3KVnatgY7loH5/TkeVyXPfESoqSBSBEiDd5VjlgE=
github.com/bytedance/sonic/loader v0.5.0/go.mod h1:AR4NYCk5DdzZizZ5djGqQ92eEhCCcdf5x77udYiSJRo=
github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M=
github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/gabriel-vasile/mimetype v1.4.12 h1:e9hWvmLYvtp846tLHam2o++qitpguFiYCKbn0w9jyqw=
github.com/gabriel-vasile/mimetype v1.4.12/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s=
github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w=
github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM=
github.com/gin-gonic/gin v1.12.0 h1:b3YAbrZtnf8N//yjKeU2+MQsh2mY5htkZidOM7O0wG8=
github.com/gin-gonic/gin v1.12.0/go.mod h1:VxccKfsSllpKshkBWgVgRniFFAzFb9csfngsqANjnLc=
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
......

1.3 初始化一个项目

创建项目使用的是 goland,初始化的时候指定文件路径以及 GOROOT,左边选第一个 Go,默认就是 GO MODULE,如果选第二个就是通过 GOPATH 管理的项目.

项目创建成功之后会生成 go.mod 和一个 main 文件.

下面改下 main 文件.

go 复制代码
// main.go(示例代码,导入gin框架)
package main

import "github.com/gin-gonic/gin"

func main() {
	r := gin.Default()
	r.GET("/", func(c *gin.Context) {
		c.String(200, "Hello Go Module!")
	})
	r.Run(":8080")
}

通过 go get 获取依赖,默认最新版本,也可以指定版本:

go 复制代码
go get "github.com/gin-gonic/gin"

// 格式:go get 模块路径@版本号
// go get github.com/gin-gonic/gin@v1.8.1  // 指定版本 1.8.1
// go get github.com/gin-gonic/gin@latest   // 升级到最新版
// go get github.com/gin-gonic/gin@master   // 拉取master分支(自动生成伪版本)

然后通过 go mod tidy 清理不需要的依赖,通过删除 go.mod 中没有被代码引用的依赖,同时补充代码中使用但是 go.mod 中没有声明的依赖,保持 go.mod/go.sum 简洁.

go 复制代码
go mod tidy

下面直接运行 main 方法,如果是用的 go 命令,就需要使用 go build 先编译,再通过 go run main.go 运行 main 方法:

go 复制代码
// 编译
go build -o go-learn-1

// 运行 main 方法
go run main.go

接下来浏览器访问 localhost:8080,输出如下:

1.4 三种编译方式

goland 编译项目的时候提供了三种方式,下面看看这三种方式有什么不同:

  1. File :单个 .go 源文件,命令等价:go build main.go,仅仅编译当前选中的文件,同包的其他文件不会被自动包含,依赖同包的代码会报错 undefined,就是用来测试单个 main 文件.
  2. Package :编译某个 go 包,比如 go build go-learn-1/learn-1/pkg,编译该包下所有 .go 文件(除测试文件),自动处理同包依赖,这种模式是正式 Go 项目的标准包结构,main 包在指定目录.
  3. Directory :编译本地文件系统的目录路径,比如 go build D:\go-code\go-learn-1\learn-1\directory,和 Package 一致,因为 Go 中目录与包一一对应,会编译该目录下所有 .go 文件,但是这种模式下所有的 go 文件必须要定义成同一个 package 才能运行,比如下面项目结构中的 pkg 如果用 Directory 模式去编译就没办法通过,因为 app.go 和 util.go 不是在同一个包下的.

下面就是几个例子,首先项目结构如下:

1.4.1 File 模式

learn-1/single/main.go 的代码如下:

go 复制代码
package main

import "fmt"

func main() {
	fmt.Println("✅ Running as [Single]: learn-1/single")
}

将 goland 配置改成 File 编译,指定这个 main 文件:

输出结果如下:

go 复制代码
✅ Running as [Single]: learn-1/single

1.4.2 Package 模式

package 模式下可以指定某个包进行编译,下面我们指定编译包为:go-learn-1/learn-1/pkg.

app.go 代码如下:

go 复制代码
package cmd

func GetHello2() string {
	return "✅ Running as [pkg]: learn-1/pkg/cmd/app.go"
}

util.go 代码如下:

go 复制代码
package cmd

func GetHello1() string {
	return "✅ Running as [pkg]: learn-1/pkg/cmd/util.go"
}

注意一个目录只能又一个 main 包,main 包对应可执行文件,同一个目录下是不能生成多个可执行文件的,同时非同包的情况下引用其他包的方法需要通过 import 引入,比如下面的 "go-learn-1/learn-1/pkg" 里面定义的两个方法.

go 复制代码
package main

import (
	"fmt"
	"go-learn-1/learn-1/pkg/cmd"
)

func main() {
	fmt.Println(cmd.GetHello1())
	fmt.Println(cmd.GetHello2())
}

执行结果如下:

go 复制代码
✅ Running as [pkg]: learn-1/pkg/cmd/util.go
✅ Running as [pkg]: learn-1/pkg/cmd/app.go

1.4.3 Directory 模式

将模式改成 Directory,指定目录为 D:\go-code\go-learn-1\learn-1\directory.

app.go 文件如下:

go 复制代码
package main

func GetHello2() string {
	return "✅ Running as [directory]: learn-1/directory/app.go"
}

util.go 文件如下:

go 复制代码
package main

func GetHello1() string {
	return "✅ Running as [directory]: learn-1/directory/util.go"
}

main.go 文件如下:

go 复制代码
package main

import (
	"fmt"
)

func main() {
	fmt.Println(GetHello1())
	fmt.Println(GetHello2())
}

输出结果如下:

go 复制代码
✅ Running as [directory]: learn-1/directory/util.go
✅ Running as [directory]: learn-1/directory/app.go

1.4.4 总结

表项
对比维度 File Package Directory
指定目标 单个 .go 源文件 Go 包的导入路径,如:go-learn-1/learn-1/pkg 本地文件系统的目录路径,如:D:\go-code\go-learn-1\learn-1\directory
等价命令 go build main.go go build go-learn-1/learn-1/pkg go build D:\go-code\go-learn-1\learn-1\directory
依赖处理 仅编译当前选中文件,同包其他文件不会被自动包含,依赖同包代码会报 undefined 错误 编译该包下所有 .go 文件(除测试文件),自动处理同包依赖 和 Package 一致,因为 Go 中目录与包一一对应,会编译该目录下所有 .go 文件
适用场景 快速测试无同包依赖的单文件 Demo 正式 Go 项目,标准包结构,main 包在指定目录 不想手动输入包路径,直接通过 IDE 浏览选择目录
可运行性 必须是 main 包且包含 main() 函数 若为 main 包可运行;若为库包,仅编译检查,无法运行 同 Package,必须是 main 包才能运行

1.5 查看依赖信息

最后来看一些命令,可以查询相关的依赖信息:

  1. go mod graph:查看当前模块的所有依赖

  2. go mod why github.com/gin-gonic/gin:查看为什么依赖这个库

  3. go list -m github.com/gin-gonic/gin:查看依赖版本

  4. go list -m all:列出所有依赖以及版本

go 查找依赖的时候,优先级从高到底是:

  • 项目根目录的 vendor/ → GOMODCACHE(go env GOMODCACHE 查看,默认 $GOPATH/pkg/mod)→ 报错.

依赖下载流程:

  • 解析 go.mod 中的依赖 → 检查 GOMODCACHE 是否已有该版本(有则直接用)→ 无则从远程仓库下载 → 校验哈希(对比 go.sum)→ 存入 GOMODCACHE.

版本缓存:

相关推荐
喵个咪4 小时前
Go 写业务,Rust 扛底盘:一套可落地的混合架构
后端·rust·go
EatFan5 小时前
Go语言全栈实战:基于 Gin + Vue + JWT + RBAC 从零搭建前后端分离权限管理系统
vue.js·golang·go·vue·gin·jwt·rbac
ZealSinger5 小时前
Go slog生产落地LevelVar与共存
开发语言·后端·golang·go
福兮说6 小时前
Go encoding/json 的八个默认行为,以及 Go 1.25 的 json/v2 改了哪几个(附实测输出)
go
合橱瑰7 小时前
踩坑实录:包是好的,代码却报错?一次“数字ID”引发的插件启动血案
架构·go
NetX行者9 小时前
五大主流语言:Go、Python、Rust、Java、C# 的比较
go
Thinker QAQ1 天前
并发编程(六):Atomic 的实现——从 Runtime 到 CPU
java·go·cas·并发编程·atomic·cpython
喵个咪1 天前
GoWind Admin|风行 — 开箱即用的企业级全栈中后台框架:多租户与行级数据隔离
后端·go
喵个咪1 天前
GoWind Admin|风行 — 开箱即用的企业级全栈中后台框架:脚本系统实战
javascript·后端·go