Go 中的 init 如何用?它的常见应用场景有哪些呢?

嗨,大家好!我是波罗学。本文是系列文章 Go 技巧第十六篇,系列文章查看:Go 语言技巧。

Go 中有一个特别的 init() 函数,它主要用于包的初始化。init() 函数在包被引入后会被自动执行。如果在 main 包中,它也会在 main() 函数之前执行。

本文将以此为主题,介绍 Go 中 init() 函数的使用和常见使用场景。还有,我在工作中更多看到的是 init() 函数的滥用。

init() 函数的执行时机

首先,init() 的执行时机处于包级别变量声明和 main() 函数执行之间。

这意味着在包中声明的全局变量,如果附带初始化表达式,这些表达式将在任何 init() 函数执行之前进行初始化。

我们通过一个示例演示,代码如下:

var Age = GetAge()

func GetAge() int {
    return 18
}

func init() {
    fmt.Printf("You're %d years old.\n", Age)
    Age = 3
}

func main() {
    fmt.Printf("You're %d years old.\n", Age)
}

输出:

You're 18 years old
You're 3 years old

从输出可知,GetAge() 函数作为 Age 的初始化函数,于 init() 函数前执行,赋值 Age3。而 init() 函数于其后执行,赋值 Age3main() 函数则在最后执行,输出最终的 Age 值。

这个顺序是符合我们预期的。

与被引入包的 init() 函数

如果一个包导入了其他包,被导入包的初始化 init() 则会先于导入它的包的变量初始化和 init 函数前执行。

举例来说明吧!

假设,我们有一个 main 包,它导入了 sub 包,并且同样有一个 init() 函数:

// main.go

package main

import (
    "fmt"
    _ "demo/sub"
)

var age = GetAge()

func GetAge() int {
  fmt.Println("main initialize variables.")
  return 18
}

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

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

sub 包中包含定义的 init() 函数

// sub/sub.go

package sub

import "fmt"

var age = GetAge()

func GetAge() int {
  fmt.Println("sub initialize variables.")
  return 18
}

func init() {
    fmt.Println("sub package init")
}

// 其他可能的函数和声明

当你运行 main.go 时,输出将会按照以下顺序出现:

sub initialize variables.
sub package init
main initialize variables.
main package init
main function

这个示例清晰地展示了包的初始化顺序:首先是被导入包(sub)的 init() 函数,然后是导入它的包(main)的 init() 函数,最后是 main 函数。

这也确保了依赖包在使用前已经被正确初始化。

特别说明:

init() 区别于其他函数,不需要我们显式调用,它会自动被 Go runtime 调用。而且,每个包中的 init() 只会被执行一次。

一个包其实可有多个 init(),无论是在分部在包中的同一个文件中还是多个文件中。如果分布在多个文件中,执行顺序通常是按照文件名的字典顺序。

为说明这个问题,我们首先修改 sub.go 文件,内容如下:

// sub/sub.go

package sub

import "fmt"

var age = GetAge()

func GetAge() int {
  fmt.Println("sub initialize variables.")
  return 18
}

func init() {
    fmt.Println("sub init 1")
}

func init() {
    fmt.Println("sub init 2")
}

新增一个 sub1.go 文件,如下所示:

// sub/sub1.go

package sub

import "fmt"

var age = GetAge1()

func GetAge1() int {
  fmt.Println("sub1 initialize variables.")
  return 18
}

func init() {
    fmt.Println("sub1 init")
}

输出:

sub initialize variables.
sub init 1
sub init 2
sub1 initialize variables.
sub1 init
main initialize variables.
main package init
main function

结果符合预期。

init() 的使用场景

init() 函数通常用于进行一些必要的设置或初始化操作,例如初始化包级别的变量与命令行参数、配置加载、环境检查、甚至注册插件等。

请添加图片描述

项目开发中,组件依赖管理通常比较令人头疼。但一些简单的依赖关系,即使没有如 wire 这样依赖注入工具的加持,通过 init 也可管理。

命令行参数

对于开发一个简单的命令行应用,init() 和标准库 flag 包结合,可快速完成命令命令行参数的初始化。

package main

import (
    "flag"
    "fmt"
)

var name string
var help bool

func init() {
    flag.StringVar(&name, "name", "World", "a name to say hello to")
    flag.StringVar(&help, "name", "World", "display help information")
    flag.Parse()
}

func main() {
    if help {
        fmt.Fprintf(os.Stderr, "Usage of %s:\n", os.Args[0])
        flag.PrintDefaults()
        os.Exit(1)
    } 
    fmt.Printf("Hello, %s!\n", name)
}

以上示例中,init() 函数解析了命令行参数并初始化变量 namehelp 变量。

配置加载

init 函数的领哇一个常见场景是配置加载。配置通常是程序启动时要尽早执行的操作。

例如,你有一个 web 服务,要在启动服务器前加载数据库配置、API 密钥或其他服务配置。

var config AppConfig

func init() {
    configFile, err := os.Open("config.json")
    if err != nil {
        log.Fatal(err)
    }
    defer configFile.Close()
    jsonParser := json.NewDecoder(configFile)
    jsonParser.Decode(&config)
}

如果配置加载都出现问题,很大程度说明服务配置不正常,要立刻退出服务。我们可使用 log.Fatal(err) (更优雅)或 panic(err) 退出服务。

环境检查

init() 还可以用于检查和验证程序运行所需的环境。如,我们要确保必要的环境变量已设置,或者必要的外部服务可用。

如我们的必须依赖一个需要认证的外部服务,示例代码:

func init() {
    if os.Getenv("XXX_API_KEY") == "" {
        log.Fatal("XXX_API_KEY environment variable not set")
    }

    apiKey := os.Getenv("XXX_API_KEY")
    // instantiating Component
    // ...
}

通过,如果要实例化的组件不需要赖加载,创建和配置验证同时 init() 中完成即可。

注册插件或服务

如果你的程序用的是插件架构,我们可以在程序启动时注册这些插件。init() 正可以用来自动注册这些插件。

示例代码:

func init() {
    plugin.Register("myPlugin", NewMyPlugin)
}

Go 的数据库驱动管理可作为这种场景的典型案例。

Go 的 database 操作通常依赖 database/sql 包,它提供了一种通用接口与 SQL 或类 SQL 数据库交互。而具体的驱动实现(如 MySQL、PostgreSQL、SQLite 等)通常是通过实现 database/sql 包定义接口来提供支持。

这种架构下,init() 被用于驱动的自动注册。

例如,如下这个 MySQL 驱动的实现:

package mysql

import (
    "database/sql"
)

func init() {
    sql.Register("mysql", &MySQLDriver{})
}

type MySQLDriver struct {
    // 驱动的实现
}

我们只要导入这个 database driver 包,它的 init() 就会被调用,将驱动注册到 database/sql 包中。

我们使用的时候,通过 database/sql 接口即可使用该 MySQL 驱动,而不需关心它的实现细节。

import (
    "database/sql"
    _ "github.com/go-sql-driver/mysql" // 导入 MySQL 驱动
)

func main() {
    db, err := sql.Open("mysql", "user:password@/dbname")
    // ...
}

通过这种方式,Go 的数据库驱动代码更加模块化和灵活性。使用方只需关心与 database/sql 交互即可,而不必关心驱动的实现细节。

实际的场景案例,我觉得肯定不止这么多。对于任何需要提前初始化和验证的场景,可适当考虑是否可通过使用 init() 来简化代码。

注意点

讲了那么多 init() 的使用,但我在平时发现,更多的时候 init() 函数是在被滥用。

我这里不得不提一些注意点。

请添加图片描述

启动耗时

首先,由于 init() 函数在程序启动时自动执行,这就导致它会增加程序启动时间,特别是一些组件初始化耗时较长。

非必要场景,懒加载依然是不错的选择。

什么是必要场景呢?简单来说,如果这个操作失败了,这个程序就没有继续启动的必要了。

依赖关系

还有,过多或过于复杂的 init() 函数可能会导致程序难以理解维护,依赖关系混乱。

这点在单体项目中体现的特别明显,所有人维护一个项目,所以依赖都加载到 init() 中。

如何解决呢?

如前面所有,一方面要仅在必要场景时使用 init() 函数初始化一些操作。

另外,有条件的话,建议尽量保持服务简单,如果依赖过多,如出现要一个服务连接多个相同组件(数据库、Redis),就是时候考虑优化系统设计了,可考虑将部分业务抽离为独立服务。

总结

本文介绍了到 init() 函数在 Go 中的特殊之处和使用方式。它提供了一种不同于其他语言的机制来初始化包,但也需谨慎使用以避免不必要的复杂性。

最后,希望这篇文章能帮助你更好地理解和使用 Go 的 init() 函数。

感谢阅读。

博客地址:Go 中的 init 如何用?它的常见应用场景有哪些呢?

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/401268.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

四、分类算法 - 随机森林

目录 1、集成学习方法 2、随机森林 3、随机森林原理 4、API 5、总结 sklearn转换器和估算器KNN算法模型选择和调优朴素贝叶斯算法决策树随机森林 1、集成学习方法 2、随机森林 3、随机森林原理 4、API 5、总结

Kubernetes 卷存储 NFS | nfs搭建配置 原理介绍 nfs作为存储卷使用

1、NFS介绍 NFS(Network File System)是一种分布式文件系统协议,允许客户端远程访问服务器上的文件,实现数据共享。它整合多个存储设备为统一文件系统,方便数据存储和管理,支持负载均衡和故障转移&#xf…

[设计模式Java实现附plantuml源码~行为型]协调多个对象之间的交互——中介者模式

前言: 为什么之前写过Golang 版的设计模式,还在重新写Java 版? 答:因为对于我而言,当然也希望对正在学习的大伙有帮助。Java作为一门纯面向对象的语言,更适合用于学习设计模式。 为什么类图要附上uml 因为很…

DataX - 全量数据同步工具

前言 今天是2024-2-21,农历正月十二,相信今天开始是新的阶段,尽管它不是新的周一、某月一日、某年第一天,尽管我是一个很讲究仪式感的人。新年刚过去 12 天,再过 3 天就开学咯,开学之后我的大学时光就进入了…

内网穿透——NPS突然无法连接

温馨提示 😊😊😊😊😊😊😊🌭🌭🌭🌭🌭🌭🌭❤️❤️❤️❤️❤️❤️❤️🥨🥨&#x1f9…

Go语言中的TLS加密:深入crypto/tls库的实战指南

Go语言中的TLS加密:深入crypto/tls库的实战指南 引言crypto/tls库的核心组件TLS配置:tls.Config证书加载与管理TLS握手过程及其实现 构建安全的服务端创建TLS加密的HTTP服务器配置TLS属性常见的安全设置和最佳实践 开发TLS客户端应用编写使用TLS的客户端…

基于springboot+vue的B2B平台的购物推荐网站(前后端分离)

博主主页:猫头鹰源码 博主简介:Java领域优质创作者、CSDN博客专家、阿里云专家博主、公司架构师、全网粉丝5万、专注Java技术领域和毕业设计项目实战,欢迎高校老师\讲师\同行交流合作 ​主要内容:毕业设计(Javaweb项目|小程序|Pyt…

数据结构 计算结构体大小

一、规则: 操作系统制定对齐量: 64位操作系统,默认8Byte对齐 32位操作系统,默认4Byte对齐 结构体对齐规则: 1.结构体整体的大小,需要是最大成员对齐量的整数倍 2.结构体中每一个成员的偏移量需要存在…

Bert基础(三)--位置编码

背景 还是以I am good(我很好)为例。 在RNN模型中,句子是逐字送入学习网络的。换言之,首先把I作为输入,接下来是am,以此类推。通过逐字地接受输入,学习网络就能完全理解整个句子。然而&#x…

物联网在智慧景区中的应用:提升游客体验与运营效率

目录 一、物联网技术概述 二、物联网在智慧景区中的应用 1、智能门票系统 2、智能导览系统 3、智能安全监控系统 4、智能环保系统 三、物联网在智慧景区中提升游客体验 1、提高游览便捷性 2、个性化服务体验 3、提升游客安全感 四、物联网在智慧景区中提升运营效率 …

算法--动态规划(背包问题)

这里写目录标题 总览dp问题的优化01背包问题概述算法思想算法思想中的注意点例题代码 完全背包问题概述 多重背包问题概述 分组背包问题概述 总览 dp问题的优化 要清楚:dp问题的优化一般是对dp问题的代码或者计算方程做一个等效变形 有了这个前提,我们在…

浅谈maven的生命周期

正文: 在Maven中,生命周期定义了项目构建过程的不同阶段以及在每个阶段中执行的插件目标。Maven的生命周期是由一系列阶段组成的,每个阶段都有一个唯一的标识符。 Clean生命周期:用于清理项目的构建目录。它包含以下阶段: pre-clean:执行在清理操作之前的任何操作。clea…

web安全学习笔记【13】——信息打点(3)

信息打点-JS架构&框架识别&泄漏提取&API接口枚举&FUZZ爬虫&插件项目[1] #知识点: 1、业务资产-应用类型分类 2、Web单域名获取-接口查询 3、Web子域名获取-解析枚举 4、Web架构资产-平台指纹识别 ------------------------------------ 1、开源…

白盒测试接口测试自动化测试

一、白盒测试:一种测试策略,允许我们检查程序的内部结构,对程序的逻辑结构进行检查,从中获取测试数据。白盒测试的对象基本是源程序,所以它又称为结构测试或逻辑驱动测试,白盒测试方法一般分为静态测试和动…

2024什么样的大路灯比较好?5大爆款落地灯推荐必看!

大路灯作为一个可以照明,让室内环境光线更加舒适的电器,能够减少用眼时不良光线带来的疲劳感,营造接近自然光的舒适光,受到很多家长的关注! 但现在市面有很多不良商家推出的大路灯虚标参数,实际护眼性能很低…

SpringBoot线上打包

1)目录结构 2)pom.xml <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0" xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation"http://maven.apache.…

PyTorch深度学习实战(37)——CycleGAN详解与实现

PyTorch深度学习实战&#xff08;37&#xff09;——CycleGAN详解与实现 0. 前言1. CycleGAN 基本原理2. CycleGAN 模型分析3. 实现 CycleGAN小结系列链接 0. 前言 CycleGAN 是一种用于图像转换的生成对抗网络(Generative Adversarial Network, GAN)&#xff0c;可以在不需要配…

windows server设置桌面显示此电脑

我开发的chatgpt网站&#xff1a; https://chat.xutongbao.top

LeetCode 热题 100 | 二叉树(下)

目录 1 114. 二叉树展开为链表 2 105. 从前序与中序遍历序列构造二叉树 3 437. 路径总和 III 菜鸟做题&#xff08;即将返校版&#xff09;&#xff0c;语言是 C 1 114. 二叉树展开为链表 题眼&#xff1a;展开后的单链表应该与二叉树 先序遍历 顺序相同。 而先序遍历就…

day08-实战-今日指数

今日指数-day08 1. 个股最新分时行情数据 1.1 个股最新分时行情功能说明 1&#xff09;个股最新分时行情功能原型 2&#xff09;个股最新分时行情数据接口分析 功能描述&#xff1a;获取个股最新分时行情数据&#xff0c;主要包含&#xff1a;开盘价、前收盘价、最新价、最…