Go语言的安装和包依赖管理

目录

 【安装Go】

 Go语言中的环境变量的含义

入门代码 hello.go

【Go依赖管理】

Go Modules

go get

go.mod 和 go.sum 

GoLand 不能识别依赖包的问题

扩展版本变更

【Go程序的执行顺序】 

Go语言的作用域和包

Go包的初始化顺序

init函数的用途


Go语言的特点:

  • 首字母大小写决定可见性,无需通过额外关键字修饰;
  • 变量初始为类型零值,避免以随机值作为初值的问题;
  • 内置数组边界检查,减少了越界访问带来的安全隐患;
  • 内置并发支持 goroutine;
  • 组合的设计:内置接口类型;
  • Go 不允许不同类型的整型变量进行混合计算,也不会对其进行隐式的自动转换。
var a int16 = 5
var b int32 = 8
var c int64
c = int64(a) + int64(b)
fmt.Printf("%d\n", c) //13

Go语言中组合的理解:

  • Go 语言无类型层次体系,各类型之间是相互独立的,没有子类型的概念;
  • 每个类型都可以有自己的方法集合,类型定义与方法实现是正交独立的;
  • 实现某个接口时,无需像 Java 那样采用特定关键字(public/private)修饰;
  • 包之间是相对独立的,没有子包的概念。
  • 类型嵌入:可以将已经实现的功能嵌入到新类型中,类似于“继承”,如下面代码所示:
// $GOROOT/src/io/io.go
type ReadWriter interface {
    Reader
    Writer
}

//再比如
type User struct{
    name string
    Mutex //在User结构体中嵌入类型 Mutex
}

Go语言的并发:

Go原生支持并发,采用了用户层轻量级线程,Go 将之称为 goroutine。Go 运行时默认为每个 goroutine 分配的栈空间仅 2KB,一个 Go 程序中可以创建成千上万个并发的 goroutine。并且所有的 Go 代码都在 goroutine 中执行。可以通过语言内置的 channel 传递消息或实现同步,并通过 select 实现多路 channel 的并发控制。

 【安装Go】

本机系统 MacOS 10.14,安装 go1.20.4,https://dl.google.com/go/go1.20.4.darwin-amd64.pkg

或者通过brew安装:brew install go,其它系统访问 The Go Programming Language 自行下载安装。

安装完成后配置环境变量:vim ~/.bash_profile,新增如下内容:

export GOROOT=/usr/local/go #go程序所在目录
export GOPATH=/Users/yourname/go #go代码目录
export GOBIN=$GOPATH/bin #go生成的二进制文件的目录

## 记得 source ~/.bash_profile 立即生效。

然后使用 go version 命令 和 go env 命令查看版本号和环境配置信息:

 Go语言中的环境变量的含义

Go语言的安装和包依赖管理_第1张图片

  • 对于国内的Go开发者来说,⽬前有3个常⽤的GOPROXY可供选择,分别是官⽅、七⽜和阿⾥云。 官⽅的GOPROXY,国内用户可能访问不到,所以更推荐使⽤七⽜的goproxy.cn。
  • GOPRIVATE示例:go env -w GOPRIVATE=*.mywebsite.com #设置不走proxy的私有仓库,多个可以用逗号分隔

GOPATH时代的项目目录结构一般如下设置:

bin 存放编译后可执行的文件。

pkg 存放编译后的应用包。

src 存放应用源代码。

入门代码 hello.go

package main
import "fmt"
func main() {
	fmt.Println("Hello Golang")
}

go run hello.go 直接执行,不生成任何文件

go build hello.go 在当前目录下生成可执行二进制文件

go install hello.go 会把编译好的结果移动到 bin目录中

go fmt hello.go 格式化代码

【Go依赖管理】

  • GOPATH模式:在Go1.5版本之前,所有的依赖包都放在GOPATH下。采⽤这种⽅式,⽆法实现包的多版本管理,并且包的位置只能局限在GOPATH⽬录下。如果A项⽬和B项⽬⽤到了同⼀个Go包的不同版本,这时候只能给每个项⽬设置⼀个GOPATH,将对应版本的包放在各⾃的GOPATH⽬录下,切换项⽬⽬录时也需要切换GOPATH。这些都增加了开发的复杂度。
  • vendor机制:Go1.5中启用了vendor机制,每个项⽬的根⽬录都可以有⼀个vendor⽬录,⾥⾯存放了该项⽬的Go依赖包,在编译Go源码时,Go优先从项⽬根⽬录的vendor⽬录查找依赖;如果没有找到,再去GOPATH下的vendor⽬录下找;如果还没有找到,就去GOPATH下找。但是随着项⽬依赖的增多,vendor⽬录会越来越⼤,造成整个项⽬仓库越来越⼤。
  • Go Modules:在 Go 1.11 版本中,Go 引入 Go Modules 构建模式,此时 GOPATH 是⽆意义的,不过它还是会把下载的依赖储存在 GOPATH/pkg/mod ⽬录中,也会把 go install 的⼆进制⽂件存放在 GOPATH/bin ⽬录中。

Go Modules

Go Modules 的管理命令为go mod,有很多⼦命令,可以通过go help mod来获取所有的命令。

Go语言的安装和包依赖管理_第2张图片

download:下载go.mod⽂件中记录的所有依赖包。

edit:编辑go.mod⽂件。

graph:查看现有的依赖结构。

init:把当前⽬录初始化为⼀个新模块。

tidy:添加丢失的模块,并移除⽆⽤的模块。默认情况下,Go不会移除go.mod⽂件中的⽆⽤依赖。当依赖包不再使⽤了,可以使⽤go mod tidy命令来清除它。

vendor:将所有依赖包存到当前⽬录下的vendor⽬录下。

verify:检查当前模块的依赖是否已经存储在本地下载的源代码缓存中,以及检查下载后是否有修改。

why:查看为什么需要依赖某模块。 

可以通过设置环境变量 GO111MODULE 的值在两种构建模式间切换。GO111MODULE有3个值。

go env -w GO111MODULE=off/on #使用 Go modules 时请设置为on

# auto:在Go1.14版本中是默认值,在$GOPATH/src下,且没有包含go.mod时则关闭Go Modules,其他情况下都开启Go Modules。
# on:启⽤Go Modules,Go1.14版本推荐打开,未来版本会设为默认值。
# off:关闭Go Modules,不推荐。

go get

可以通过 go get 命令将本地缺失的第三方依赖包下载到本地,比如:

go get github.com/sirupsen/logrus 

#备注:go get 下载的包只是当前的最新版本

#可以使⽤go get -u 更新package到latest版本,也可以使⽤go get -u=patch只更新⼩版本,例如从 v1.2.4到v1.2.5。

go get的参数:

Go语言的安装和包依赖管理_第3张图片

go.mod 和 go.sum 

一个 Go Module 的顶层目录下会放置一个 go.mod 文件和 go.sum文件,每个 go.mod 文件会定义唯一一个 module。一个go.mod文件示例:

module test-module

go 1.20

require (
	github.com/google/uuid v1.3.0
	github.com/sirupsen/logrus v1.8.2
)

require golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 // indirect

go mod子命令示例:

go mod edit -fmt # go.mod 格式化
go mod edit -require=golang.org/x/[email protected] # 添加⼀个依赖
go mod edit -droprequire=golang.org/x/text # require的反向操作,移除⼀个依赖
go mod edit -replace=github.com/gin-gonic/gin=/home/colin/gin # 替换模块版本
go mod edit -dropreplace=github.com/gin-gonic/gin # replace的反向操作
go mod edit -exclude=golang.org/x/[email protected] # 排除⼀个特定的模块版本
go mod edit -dropexclude=golang.org/x/[email protected] # exclude的反向操作

基于当前项目创建一个 Go Module的步骤:

go mod init test-module  #创建 go.mod 文件
go mod tidy #自动更新当前 module 的依赖
go mod vendor #在当前项目下生成vendor目录,也会创建vendor/modules.txt⽂件,来记录包和模块的版本信息
go build #执行新 module 的构建,会生成一个可执行文件 test-module
//新建main.go
package main

import (
	"github.com/sirupsen/logrus"
)

func main() {
	logrus.Println("hello, go module")
}

Go语言的安装和包依赖管理_第4张图片

go.sum⽂件⽤来记录每个依赖包的hash值,在构建时,如果本地的依赖包hash值与go.sum⽂件中记录的不⼀致,则会拒绝构建。

go mod tidy 会自动更新当前 module 的依赖并生成新的 go.sum 文件,并且会自动分析源码依赖,而且将不再使用的依赖从 go.mod 和 go.sum 中移除。推荐把 go.mod 和 go.sum 两个文件也提交到代码版本中。

GoLand 不能识别依赖包的问题

Go语言的安装和包依赖管理_第5张图片

需要修改GoLand的Go Module,增加:GOPROXY=https://goproxy.cn,direct 就可以了。

Go语言的安装和包依赖管理_第6张图片

扩展版本变更

查看扩展的发布版本有哪些:

demo01 $ go list -m -mod=mod -versions github.com/sirupsen/logrus 
github.com/sirupsen/logrus v0.1.0 v0.1.1 ... v1.8.2 v1.9.0

对扩展降级:

demo01 $ go mod edit -require=github.com/sirupsen/[email protected]
demo01 $ go mod tidy
go: downloading github.com/sirupsen/logrus v1.8.2

查看当前 module 的所有依赖: go list -m all

Go语言的安装和包依赖管理_第7张图片

【Go程序的执行顺序】 

Go语言的作用域和包

在Java或者PHP中,通过public、private 这些修饰符修饰一个类的作用域,而在Go语言中,并没有这样的作用域修饰符,它是通过首字母是否大写来区分的;代码的package可以和所在的目录不一致,但是同一目录里的Go代码的package要保持一致。Go语言中,所有的定义,比如函数、变量、结构体等如果首字母是大写,那么就可以被其他包使用,例如:fmt.Println();反之,如果首字母是小写的,就只能在同一个包内使用

可执行程序的 main 包必须定义 main 函数(无参数无返回值),否则 Go 编译器会报错。在启动了多个 Goroutine 中,main.main 函数将在 Go 应用的主 Goroutine 中执行。除了 main 包外,其他包也可以拥有自己的名为 main 的函数或方法。但按照 Go 的可见性规则(小写字母卡头的标识符为非导出标识符),非 main 包中自定义的 main 函数仅限于包内使用。

需要注意的是,main包的main函数虽然是用户层逻辑的入口函数,但它却不一定是用户层第一个被执行的函数,因为还有一个Go 包的初始化函数:init 函数(也是无参数无返回值),类似于构造函数的意思。在 Go 程序中不能手工显式地调用 init,否则会编译错误。另外,Go包中可以拥有多个init函数,Go 会按照一定的次序,逐一顺序地调用这个包的 init 函数。只有当一个 init 函数执行完毕后,才会去执行下一个 init 函数。

Go包的初始化顺序

Go 在进行包初始化的过程中,会采用“深度优先”的原则,递归初始化各个包的依赖包。在包内按照“常量 -> 变量 -> init 函数 -> main函数” 的顺序进行初始化。如下图所示:

Go语言的安装和包依赖管理_第8张图片

例如下面的代码:

/* order/orders.go */
package order

import "fmt"

func init() {
	fmt.Println("order/orders.init()")
}

/* user/user.go */
package user

import (
	"fmt"
	_ "test-init/order"
)

var username = "zhangsan"

const AGE = 18

func init() {
	fmt.Println("user/user.init()")
}

func Hello() {
	fmt.Println("user/user.hello()")
	fmt.Println(username)
	fmt.Println(AGE)
}

/* goods/goodsInfo.go */
package goods

import (
	"fmt"
	_ "test-init/order"
)

func init() {
	fmt.Println("goods/goodsInfo.init()")
}

/* main.go */
package main

import (
	"fmt"
	_ "test-init/goods"
	"test-init/user"
)

// 包初始化逻辑,可以有多个init()方法
func init() {
	fmt.Println("main.init1()")
	fmt.Println(user.AGE)
}

func init() {
	fmt.Println("main.init2()")
	user.Hello()
}

func main() {
	fmt.Println("main.main()")
}

Go语言的安装和包依赖管理_第9张图片

注意看上面,user和goods都依赖了order包,但是却只输出了一次order的信息。原因在于:每个 init 函数在整个 Go 程序生命周期内仅会被执行一次。

init函数的用途

(1) 重置包级变量值:负责对包内部以及暴露到外部的包级数据(主要是包级变量)的初始状态进行检查。
(2) 实现对包级变量的复杂初始化,例如 标准库 http 包中的示例:

var (
	http2VerboseLogs    bool // 初始化时默认值为false
	http2logFrameWrites bool // 初始化时默认值为false
	http2logFrameReads  bool // 初始化时默认值为false
	http2inTests        bool // 初始化时默认值为false
)

(3) 在 init 函数中实现“注册模式”,有效降低了 Go 包对外的直接暴露。

比如上面示例中,user/user.go 中使用空导入的方式导入了 order包,这样一来,就可以有效降低order包对外的直接暴露,从而避免了外部通过包级变量对包状态的改动。其实更像是设计模式中的工厂模式。

比如标准库 image 包获取各种格式图片的宽和高:

import (
	"fmt"
	"image"
	"os"
)

import (
	_ "image/gif"  // 以空导入方式注入gif图片格式驱动
	_ "image/jpeg" // 以空导入方式注入jpeg图片格式驱动
	_ "image/png"  // 以空导入方式注入png图片格式驱动
)

func main() {
	// 支持png, jpeg, gif
	width, height, err := imageSize(os.Args[1]) // 获取传入的图片文件的宽与高
	if err != nil {
		fmt.Println("get image size error:", err)
		return
	}
	fmt.Printf("image size: [%d, %d]\n", width, height)
}
func imageSize(imageFile string) (int, int, error) {
	f, _ := os.Open(imageFile) // 打开图文文件
	defer f.Close()
	img, _, err := image.Decode(f) // 对文件进行解码,得到图片实例
	if err != nil {
		return 0, 0, err
	}
	b := img.Bounds() // 返回图片区域
	return b.Max.X, b.Max.Y, nil
}

/*image/png、image/jpeg 和 image/gif 包都在各自的 init 函数中,将自己“注册”到 image 的支持格式列表中*/
// $GOROOT/src/image/png/reader.go
func init() {
	image.RegisterFormat("png", pngHeader, Decode, DecodeConfig)
}

// $GOROOT/src/image/jpeg/reader.go
func init() {
	image.RegisterFormat("jpeg", "\xff\xd8", Decode, DecodeConfig)
}

// $GOROOT/src/image/gif/reader.go
func init() {
	image.RegisterFormat("gif", "GIF8?a", Decode, DecodeConfig)
}

综上所述,init 函数十分适合做一些包级数据初始化工作以及包级数据初始状态的检查的工作,同时需要注意主 Goroutine 是否要等待其他子 Goroutine 做完清理收尾工作退出后再行退出。

参考源代码:https://gitee.com/rxbook/go-demo-2023/tree/master/module

你可能感兴趣的:(Golang,golang,笔记)