欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

詳解Go語言中init的使用與常見應用場景

 更新時間:2024年02月18日 15:44:51   作者:波羅學  
Go?中有一個特別的?init()?函數,它主要用于包的初始化,這篇文章將以此為主題介紹?Go?中?init()?函數的使用和常見使用場景,希望對大家有所幫助

Go 中有一個特別的 init() 函數,它主要用于包的初始化。init() 函數在包被引入后會被自動執(zhí)行。如果在 main 包中,它也會在 main() 函數之前執(zhí)行。

本文將以此為主題,介紹 Go 中 init() 函數的使用和常見使用場景。還有,我在工作中更多看到的是 init() 函數的濫用。

init() 函數的執(zhí)行時機

首先,init() 的執(zhí)行時機處于包級別變量聲明和 main() 函數執(zhí)行之間。

這意味著在包中聲明的全局變量,如果附帶初始化表達式,這些表達式將在任何 init() 函數執(zhí)行之前進行初始化。

我們通過一個示例演示,代碼如下:

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() 函數前執(zhí)行,賦值 Age 為 3。而 init() 函數于其后執(zhí)行,賦值 Age 為 3main() 函數則在最后執(zhí)行,輸出最終的 Age 值。

這個順序是符合我們預期的。

與被引入包的 init() 函數

如果一個包導入了其他包,被導入包的初始化 init() 則會先于導入它的包的變量初始化和 init 函數前執(zhí)行。

舉例來說明吧!

假設,我們有一個 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() 區(qū)別于其他函數,不需要我們顯式調用,它會自動被 Go runtime 調用。而且,每個包中的 init() 只會被執(zhí)行一次。

一個包其實可有多個 init(),無論是在分部在包中的同一個文件中還是多個文件中。如果分布在多個文件中,執(zhí)行順序通常是按照文件名的字典順序。

為說明這個問題,我們首先修改 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() 函數通常用于進行一些必要的設置或初始化操作,例如初始化包級別的變量與命令行參數、配置加載、環(huán)境檢查、甚至注冊插件等。

項目開發(fā)中,組件依賴管理通常比較令人頭疼。但一些簡單的依賴關系,即使沒有如 wire 這樣依賴注入工具的加持,通過 init 也可管理。

命令行參數

對于開發(fā)一個簡單的命令行應用,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() 函數解析了命令行參數并初始化變量 name 和 help 變量。

配置加載

init 函數的領哇一個常見場景是配置加載。配置通常是程序啟動時要盡早執(zhí)行的操作。

例如,你有一個 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) (更優(yōu)雅)或 panic(err) 退出服務。

環(huán)境檢查

init() 還可以用于檢查和驗證程序運行所需的環(huán)境。如,我們要確保必要的環(huán)境變量已設置,或者必要的外部服務可用。

如我們的必須依賴一個需要認證的外部服務,示例代碼:

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
    // ...
}

通過,如果要實例化的組件不需要賴加載,創(chuàng)建和配置驗證同時 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 驅動,而不需關心它的實現細節(jié)。

import (
    "database/sql"
    _ "github.com/go-sql-driver/mysql" // 導入 MySQL 驅動
)

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

通過這種方式,Go 的數據庫驅動代碼更加模塊化和靈活性。使用方只需關心與 database/sql 交互即可,而不必關心驅動的實現細節(jié)。

實際的場景案例,我覺得肯定不止這么多。對于任何需要提前初始化和驗證的場景,可適當考慮是否可通過使用 init() 來簡化代碼。

注意點

講了那么多 init() 的使用,但我在平時發(fā)現,更多的時候 init() 函數是在被濫用。

我這里不得不提一些注意點。

啟動耗時

首先,由于 init() 函數在程序啟動時自動執(zhí)行,這就導致它會增加程序啟動時間,特別是一些組件初始化耗時較長。

非必要場景,懶加載依然是不錯的選擇。

什么是必要場景呢?簡單來說,如果這個操作失敗了,這個程序就沒有繼續(xù)啟動的必要了。

依賴關系

還有,過多或過于復雜的 init() 函數可能會導致程序難以理解維護,依賴關系混亂。

這點在單體項目中體現的特別明顯,所有人維護一個項目,所以依賴都加載到 init() 中。

如何解決呢?

如前面所有,一方面要僅在必要場景時使用 init() 函數初始化一些操作。

另外,有條件的話,建議盡量保持服務簡單,如果依賴過多,如出現要一個服務連接多個相同組件(數據庫、Redis),就是時候考慮優(yōu)化系統(tǒng)設計了,可考慮將部分業(yè)務抽離為獨立服務。

總結

本文介紹了到 init() 函數在 Go 中的特殊之處和使用方式。它提供了一種不同于其他語言的機制來初始化包,但也需謹慎使用以避免不必要的復雜性。

到此這篇關于詳解Go語言中init的使用與常見應用場景的文章就介紹到這了,更多相關Go init使用內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

  • Go語言中for循環(huán)的經典案例分析

    Go語言中for循環(huán)的經典案例分析

    for循環(huán)問題,在面試中經常都會被問到,并且在實際業(yè)務項目中也經常用到for循環(huán),要是沒用好,一不下心就掉坑。本文為大家挑選了幾個經典的案例,一塊來探討下,看看如何避免掉坑,多積累積累采坑經驗
    2023-02-02
  • Go?modules?replace解決Go依賴引用問題

    Go?modules?replace解決Go依賴引用問題

    這篇文章主要為大家介紹了Go?modules?replace解決Go依賴引用問題,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2022-06-06
  • go的defer和閉包示例說明(非內部實現)

    go的defer和閉包示例說明(非內部實現)

    這篇文章主要為大家介紹了go的defer和閉包示例說明(非內部實現),有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2023-08-08
  • GoLang bytes.Buffer基礎使用方法詳解

    GoLang bytes.Buffer基礎使用方法詳解

    Go標準庫中的bytes.Buffer(下文用Buffer表示)類似于一個FIFO的隊列,它是一個流式字節(jié)緩沖區(qū),我們可以持續(xù)向Buffer尾部寫入數據,從Buffer頭部讀取數據。當Buffer內部空間不足以滿足寫入數據的大小時,會自動擴容
    2023-03-03
  • 淺談Golang是如何讀取文件內容的(7種)

    淺談Golang是如何讀取文件內容的(7種)

    這篇文章主要介紹了淺談Golang是如何讀取文件內容的,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2020-05-05
  • golang強制類型轉換和類型斷言

    golang強制類型轉換和類型斷言

    這篇文章主要介紹了詳情介紹golang類型轉換問題,分別由介紹類型斷言和類型轉換,這兩者都是不同的概念,下面文章圍繞類型斷言和類型轉換的相關資料展開文章的詳細內容,需要的朋友可以參考以下
    2021-12-12
  • Golang使用JWT進行認證和加密的示例詳解

    Golang使用JWT進行認證和加密的示例詳解

    JWT是一個簽名的JSON對象,通常用作Oauth2的Bearer?token,JWT包括三個用.分割的部分。本文將利用JWT進行認證和加密,感興趣的可以了解一下
    2023-02-02
  • Go語言Elasticsearch數據清理工具思路詳解

    Go語言Elasticsearch數據清理工具思路詳解

    這篇文章主要介紹了Go語言Elasticsearch數據清理工具思路詳解,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2021-10-10
  • Golang中Options模式的使用

    Golang中Options模式的使用

    選項模式是一種設計模式,允許通過提供選項自定義行為,Golang中的應用廣泛,尤其是庫和框架設計中,本文深入探討Golang中選項模式的實現,包括函數選項和結構體選項兩種方式,文中通過示例代碼介紹的非常詳細,需要的朋友們下面隨著小編來一起學習學習吧
    2024-11-11
  • golang?cache帶索引超時緩存庫實戰(zhàn)示例

    golang?cache帶索引超時緩存庫實戰(zhàn)示例

    這篇文章主要為大家介紹了golang?cache帶索引超時緩存庫實戰(zhàn)示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2022-09-09

最新評論