十一、包管理

十一、包管理

11.1、package

Go语言是使用包来组织源代码的,包(package)是多个 Go 源码的集合,是一种高级的代码复用方案。Go语言中为我们提供了很多内置包,如 fmt、os、io 等。任何源代码文件必须属于某个包,同时源码文件的第一行有效代码必须是package pacakgeName 语句,通过该语句声明自己所在的包。

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

11.1.1、包的基本使用

导入包的语法:

import "包的路径"    // 双引号

导入包路径规则:

Go 程序首先在 GOROOT/src 目录中寻找包目录,如果没有找到,则会去 GOPATH/src 目录中继续寻找。比如 fmt 包是位于 GOROOT/src 目录的 Go 语言标准库中的一部分,它将会从该目录中导入。

目录结构

db包

package db

import "fmt"

func HandleMySQL() {
    fmt.Println("操作MySQL数据库")
}
package db

import "fmt"

func HandleRedis() {
    fmt.Println("操作redis数据库")
}

api包

package api

import "fmt"
import "mysite/db"

func RestfulAPI() {
    db.HandleMySQL()
    fmt.Println("RestfulAPI:MySQL数据接口")
}
package api

import "fmt"
import "mysite/db"

func RpcAPI() {
    db.HandleRedis()
    fmt.Println("RpcAPI:redis数据接口")
}

main

package main

import "mysite/api"

func main() {
    // db.HandleMySQL()
    // db.HandleRedis()

    api.RestfulAPI()
    api.RpcAPI()

}

1、包名一般是小写的,见名知意,包名中不能包含等特殊符号。

2、包名规范上要和所在的目录同名,也可以不同。比如package api改为package newApi,代码改动为

package main

import newApi "mysite/api"

func main() {
  newApi.RestfulAPI()
  newApi.RpcAPI()
}

3、包名为 main 的包为应用程序的入口包,编译不包含 main 包的源码文件时不会得到可执行文件。

4、一个文件夹下的所有源码文件只能属于同一个包,同样属于同一个包的源码文件不能放在多个文件夹下。

5、一个包下的不同文件不能含有同名函数。

6、如果想在一个包中引用另外一个包里的标识符(如变量、常量、类型、函数等)时,该标识符必须是对外可见的(public),在Go语言中只需要将标识符的首字母大写就可以让标识符对外可见了。

7、环境变量GO111MODULE=off: go env -w GO111MODULE=off

8、将mysite剪切到src外的任何位置,都会导包失败。

11.1.2、包的导入格式

// 一次导入多个包
import (
    "fmt"
    "mysite/api"
)

// 设置包的别名
import F "fmt"

// 省略引用格式
import . "mysite/api"
RestfulAPI()

// 匿名导入 :在引用某个包时,如果只是希望执行包初始化的 init 函数,而不使用包内部的数据时,可以使用匿名引用格式
import _ "包名"
import (
      "database/sql"
      _ "github.com/go-sql-driver/mysql"
  )  

11.1.3、包的加载顺序

init()函数会在每个包完成初始化后自动执行,并且执行优先级比main函数高。

init 函数通常被用来:

  • 对变量进行初始化
  • 检查/修复程序的状态
  • 注册
  • 运行一次计算

注意:

1、一个包可以有多个 init 函数,包加载时会执行全部的 init 函数,但并不能保证执行顺序,所以不建议在一个包中放入多个 init 函数,将需要初始化的逻辑放到一个 init 函数里面。

2、包不能出现环形引用的情况,比如包 a 引用了包 b,包 b 引用了包 c,如果包 c 又引用了包 a,则编译不能通过。

3、包的重复引用是允许的,比如包 a 引用了包 b 和包 c,包 b 和包 c 都引用了包 d。这种场景相当于重复引用了 d,这种情况是允许的,并且 Go 编译器保证包 d 的 init 函数 只会执行一次

4、init()函数没有参数也没有返回值。 init()函数在程序运行时自动被调用执行,不能在代码中主动调用它。

 

11.2、go module

module是一个相关Go包的集合,它是源代码更替和版本控制的单元。

11.2.1、Go mod命令

download    download modules to local cache (下载依赖的module到本地cache))
edit        edit go.mod from tools or scripts (编辑go.mod文件)
graph       print module requirement graph (打印模块依赖图))
init        initialize new module in current directory (再当前文件夹下初始化一个新的module, 创建go.mod文件))
tidy        add missing and remove unused modules (增加丢失的module,去掉未用的module)
vendor      make vendored copy of dependencies (将依赖复制到vendor下)
verify      verify dependencies have expected content (校验依赖)
why         explain why packages or modules are needed (解释为什么需要依赖)

Mod Cache 路径默认在$GOPATH/pkg 下面:$GOPATH/pkg/mod

11.2.2、go mod流程

同样是mysite项目,现在从src中剪切到其它任何位置,会编译报错。用go module来解决这个问题。

(1) 首先将你的版本更新到最新的Go版本(>=1.11)

(2)通过go命令行,进入到你当前的工程目录下,在命令行设置临时环境变量go env -w GO111MODULE=off

(3)go mod init在当前目录下生成一个go.mod文件,执行这条命令时,当前目录不能存在go.mod文件(有删除)。

go mod init xxx  // xxx即声明的模块名

该命令在当前目录下生成go.mod文件,文件中声明了模块名称。

module xxx
go 1.16

以后相关依赖可以声明在这里,就像python的requirements

此时运行程序,会将相关的依赖直接下载到GOPATH/pkg/mod路径下。

(4)执行go mod tidy命令,它会添加缺失的模块以及移除不需要的模块

执行后会生成go.sum文件(模块下载条目)。添加参数-v,例如go mod tidy -v可以将执行的信息,即删除和添加的包打印到命令行;

将拉取的依赖存放到GOPATH/pkg/mod路径下。

(5)执行命令go mod verify来检查当前模块的依赖是否全部下载下来,是否下载下来被修改过。如果所有的模块都没有被修改过,那么执行这条命令之后,会打印all modules verified

(6)执行命令go mod vendor生成vendor文件夹,用来区分某些库的不同版本。该文件夹下将会放置你go.mod文件描述的依赖包,文件夹下同时还有一个文件modules.txt,它是你整个工程的所有模块。在执行这条命令之前,如果你工程之前有vendor目录,应该先进行删除。同理go mod vendor -v会将添加到vendor中的模块打印出来;

 

 

内部包调用从初始化模块名go.mod所在的目录开始查找,所以导入api包:import "xxx/api"

posted @   xiaohaoge  阅读(68)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 百万级群聊的设计实践
· 永远不要相信用户的输入:从 SQL 注入攻防看输入验证的重要性
· 全网最简单!3分钟用满血DeepSeek R1开发一款AI智能客服,零代码轻松接入微信、公众号、小程
· .NET 10 首个预览版发布,跨平台开发与性能全面提升
· 《HelloGitHub》第 107 期
点击右上角即可分享
微信分享提示