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

news2024/9/20 1:07:57

嗨,大家好!我是波罗学。本文是系列文章 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 如何用?它的常见应用场景有哪些呢?

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

JavaGuide-SQL在mysql中的执行过程

SQL在mysql中的执行过程 原文连接 SQL在mysql中的执行过程 基础架构概览 我们先总结基本组件 连接器: 身份认证 权限相关的,我们连接的时候会验证查询缓存: 8.0之后移除,执行查询的时候,会先查缓存分析器: 分析你的sql语句,包括词法分析 语法分析优化器: 按照mysql认为最…

LED智能互联办公室照明恒流调光IC芯片无频闪H5114

调光高辉度65536级/高精度3% LED降压型恒流驱动器H5114 产品描述 H5114是一款外围电路简单的多功能平 均电流型LED恒流驱动器,适用于5-90V电压范围的非隔离式大功率恒流LED驱动领域。 芯片采用了平均电流模式控制,输出电流精度在3%&#xff…

http相关概念以及apache的功能(最详细讲解!!!!)

概念 互联网:是网络的网络,是所有类型网络的母集 因特网:世界上最大的互联网网络 万维网:www (不是网络,而是数据库)是网页与网页之间的跳转关系 URL:万维网使用统一资源定位符,…

GEE入门篇|遥感专业术语(实践操作1):搜索及查看图像集合信息

Earth Engine 的搜索栏可用于查找影像和定位重要内容有关 Earth Engine 中数据集的信息,让我们使用位于搜索栏上方的Earth Engine代码,用于查找有关 Landsat 7 集合 2 的信息原始场景。首先,在搜索栏中输入“Landsat 7 collection 2”&#x…

【Power Apps】实现一个简单的可编辑列表

简单来说,我们这次是要实现一个可以直接在列表上增加、修改、删除数据的功能。 大概就像这样。 之前我们都是拿列表做一个数据展示的功能,真要增加、修改、删除数据是在另一张表单上做的,我们这回要去掉另一个表单,直接在列表上做…

RabbitMQ学习整理————基于RabbitMQ实现RPC

基于RabbitMQ实现RPC 前言什么是RPCRabbitMQ如何实现RPCRPC简单示例通过Spring AMQP实现RPC 前言 这边参考了RabbitMQ的官网,想整理一篇关于RabbitMQ实现RPC调用的博客,打算把两种实现RPC调用的都整理一下,一个是使用官方提供的一个Java cli…

适用于生物行业的样本管理系统

在生物样本管理系统的应用中,我们首先需要了解生物样本的特点和要求。生物样本具有多样性和易变性,需要被妥善保存和跟踪,以确保其质量和可用性。 因此,一个有效的生物样本管理系统需要具备以下特点: 全面性&#xff1…

测试用例设计方法:招式组合,因果判定出世

1 引言 上篇讲了等价类划分和边界值分析法,而这两种方法只考虑了单个的输入条件,并未考虑输入条件的各种组合、输入条件之间的相互制约关系的场景。基于此短板,因果图法和判定表法应运而生。 2 因果图法 2.1 概念及原理 2.1.1 定义 一种…

外包干了两个月,技术退步明显。。。。。

先说一下自己的情况,本科生,19年通过校招进入广州某软件公司,干了接近4年的功能测试,今年年初,感觉自己不能够在这样下去了,长时间呆在一个舒适的环境会让一个人堕落!而我已经在一个企业干了四年的功能测试…

2024/02/23

使用消息队列完成两个进程间相互通信 A.c #include<myhead.h> struct msgbuf {long mtype;char mtext[1024]; }; //定义表示正文内容大小的宏 #define MSGSIZE sizeof(struct msgbuf)-sizeof(long)int main(int argc, const char *argv[]) {//创建一个key值key_t key;ke…

快速学习安全框架 Springsecurity最新版(6.2)--用户授权模块

简介 上一节Springsecurity 用户认证 Springsecurity 拥有强大的认证和授权功能并且非常灵活&#xff0c;,一来说我们都i有以下需求 可以帮助应用程序实现以下两种常见的授权需求&#xff1a; 用户-权限-资源&#xff1a;例如张三的权限是添加用户、查看用户列表&#xff0c;李…

springboot214基于springboot的多媒体素材库的开发与应用

多媒体素材库的设计与实现 摘要 近年来&#xff0c;信息化管理行业的不断兴起&#xff0c;使得人们的日常生活越来越离不开计算机和互联网技术。首先&#xff0c;根据收集到的用户需求分析&#xff0c;对设计系统有一个初步的认识与了解&#xff0c;确定多媒体素材库的总体功…

CentOS使用Docker搭建Halo网站并实现无公网ip远程访问

&#x1f525;博客主页&#xff1a; 小羊失眠啦. &#x1f3a5;系列专栏&#xff1a;《C语言》 《数据结构》 《C》 《Linux》 《Cpolar》 ❤️感谢大家点赞&#x1f44d;收藏⭐评论✍️ 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&…

阿里云2024年优惠政策合集,你要的阿里云优惠政策都在这!

2024年阿里云优惠活动大全&#xff0c;包括阿里云服务器优惠活动清单、配置价格表、域名优惠活动、阿里云建站活动、阿里云优惠代金券免费领取、对象存储OSS活动、企业邮箱优惠、无影云电脑优惠、CDN特惠等等&#xff0c;阿里云百科aliyunbaike.com分享2024阿里云优惠活动大全_…

Linux RocketMQ 安装及卸载(附控制台搭建)

一、前言 在安装 RocketMQ 前需要确保 JDK 已安装并正确配置环境变量 二、下载安装 1.下载 下载 | RocketMQ 2.安装 # 打开存放目录 cd /usr/local # 创建目录 mkdir rocketMQ # 进入目录 cd rocketMQ # 把下载的压缩包上传到 rocketMQ 目录中 # 解压 $ unzip rocketmq-all-…

Elasticsearch:基于 Langchain 的 Elasticsearch Agent 对文档的搜索

在今天的文章中&#xff0c;我们将重点介绍如何使用 LangChain 提供的基础设施在 Python 中构建 Elasticsearch agent。 该 agent 应允许用户以自然语言询问有关 Elasticsearch 集群中数据的问题。 Elasticsearch 是一个强大的搜索引擎&#xff0c;支持词法和向量搜索。 Elast…

人工智能 — 数字图像

目录 一、图像1、像素2、图像分辨率3、RGB 模型4、灰度5、通道6、对比度7、RGB 转化为 Gray8、RGB 值转化为浮点数9、二值化10、常用视觉库11、频率12、幅值 二、图像的取样与量化1、数字图像2、取样3、量化 三、上采样与下采样1、上采样&#xff08;upsampling&#xff09;2、…

NOIP2018-J-4-对称二叉树的题解

原题描述&#xff1a; 题目描述 时间&#xff1a;1s 空间&#xff1a;256M 一棵有点权的有根树如果满足以下条件&#xff0c;则被轩轩称为对称二叉树&#xff1a; 1. 二叉树&#xff1b; 2. 将这棵树所有节点的左右子树交换&#xff0c;新树和原树对应位置的结构相同且…

【机器学习的基本术语和概念】

曾梦想执剑走天涯&#xff0c;我是程序猿【AK】 目录 简述概要知识图谱 简述概要 提示&#xff1a;简要描述文章内容&#xff0c;适合哪些人观看 知识图谱 样本&#xff08;Sample&#xff09;/实例&#xff08;Instance&#xff09;&#xff1a;在机器学习中&#xff0c;我…

vue-利用属性(v-if)控制表单(el-form-item)显示/隐藏

表单控制属性 v-if 示例&#xff1a; 通过switch组件作为开关&#xff0c;控制表单的显示与隐藏 <el-form-item label"创建数据集"><el-switch v-model"selectFormVisible"></el-switch></el-form-item><el-form-item label&…