從別人的代碼中學習golang系列--02

来源:https://www.cnblogs.com/zhaof/archive/2020/07/03/13232768.html
-Advertisement-
Play Games

這篇博客還是整理從https://github.com/LyricTian/gin-admin 這個項目中學習的golang相關知識 作者在項目中使用了https://github.com/google/wire 做依賴註入,這個庫我之前沒有使用過,看了作者代碼中的使用,至少剛開始是看著優點懵,不知 ...


這篇博客還是整理從https://github.com/LyricTian/gin-admin 這個項目中學習的golang相關知識

作者在項目中使用了https://github.com/google/wire 做依賴註入,這個庫我之前沒有使用過,看了作者代碼中的使用,至少剛開始是看著優點懵,不知道是做什麼,所以這篇博客主要就是整理這個包的使用

依賴註入是什麼?

如果你搜索依賴註入,百度百科里可能先看到的是控制反轉,下麵是百度百科的解釋

控制反轉(Inversion of Control,縮寫為IoC),是面向對象編程中的一種設計原則,可以用來減低電腦代碼之間的耦合度。其中最常見的方式叫做依賴註入(Dependency Injection,簡稱DI),還有一種方式叫“依賴查找”(Dependency Lookup)。通過控制反轉,對象在被創建的時候,由一個調控系統內所有對象的外界實體將其所依賴的對象的引用傳遞給它。也可以說,依賴被註入到對象中。

這樣的解釋可能還是不好理解,所以我們通過一個簡單的代碼來理解應該就清楚很多。

我們用程式實現:小明對世界說:"hello golang"

這裡將小明抽象為People 說的內容抽象為: Message 小明說 hello golang 抽象為:Event, 代碼如下:

package main

import "fmt"

var msg = "Hello World!"

func NewMessage() Message {
   return Message(msg)
}

// 要說的內容的抽象
type Message string

func NewPeople(m Message) People {
   return People{name: "小明", message: m}
}

// 小明這個人的抽象
type People struct {
   name    string
   message Message
}

// 小明這個人會說話
func (p People) SayHello() string {
   msg := fmt.Sprintf("%s 對世界說:%s\n", p.name, p.message)
   return msg
}

func NewEvent(p People) Event {
   return Event{people: p}
}

// 小明去說話這個行為抽象為一個事件
type Event struct {
   people People
}

func (e Event) start() {
   msg := e.people.SayHello()
   fmt.Println(msg)
}

func main() {
   message := NewMessage()
   people := NewPeople(message)
   event := NewEvent(people)
   event.start()
}

從上面這個代碼我們可以看出,我們必須先初始化一個NewMessage, 因為NewPeople 依賴它,NewEvent 依賴NewPeople. 這還是一種比較簡單的依賴關係,實際生產的依賴關係可能會更複雜,那麼什麼好的辦法來處理這種依賴,https://github.com/google/wire 就是來乾這件事情的。

wire依賴註入例子

慄子1

安裝: go get github.com/google/wire/cmd/wire

上面的代碼,我們用wire的方式實現,代碼如下:

package main

import (
   "fmt"

   "github.com/google/wire"
)

var msg = "Hello World!"

func NewMessage() Message {
   return Message(msg)
}

// 要說的內容的抽象
type Message string

func NewPeople(m Message) People {
   return People{name: "小明", message: m}
}

// 小明這個人的抽象
type People struct {
   name    string
   message Message
}

// 小明這個人會說話
func (p People) SayHello() string {
   msg := fmt.Sprintf("%s 對世界說:%s\n", p.name, p.message)
   return msg
}

func NewEvent(p People) Event {
   return Event{people: p}
}

// 小明去說話這個行為抽象為一個事件
type Event struct {
   people People
}

func (e Event) start() {
   msg := e.people.SayHello()
   fmt.Println(msg)
}

func InitializeEvent() Event {
   wire.Build(NewEvent, NewPeople, NewMessage)
   return Event{}
}

func main() {
   e := InitializeEvent()
   e.start()
}

這裡我們不用再手動初始化NewEvent, NewPeople, NewMessage,而是通過需要初始化的函數傳遞給wire.Build , 這三者的依賴關係,wire 會幫我們處理,我們通過wire . 的方式生成代碼:

➜  useWireBaseExample2 wire .
wire: awesomeProject/202006/useWireBaseExample2: wrote /home/fan/codes/go_project/awesomeProject/202006/useWireBaseExample2/wire_gen.go
➜  useWireBaseExample2 

會在當前目錄下生成wire_gen.go的代碼,內容如下:

// Code generated by Wire. DO NOT EDIT.

//go:generate wire
//+build !wireinject

package main

import (
   "fmt"
)

// Injectors from main.go:

func InitializeEvent() Event {
   message := NewMessage()
   people := NewPeople(message)
   event := NewEvent(people)
   return event
}

// main.go:

var msg = "Hello World!"

func NewMessage() Message {
   return Message(msg)
}

// 要說的內容的抽象
type Message string

func NewPeople(m Message) People {
   return People{name: "小明", message: m}
}

// 小明這個人的抽象
type People struct {
   name    string
   message Message
}

// 小明這個人會說話
func (p People) SayHello() string {
   msg2 := fmt.Sprintf("%s 對世界說:%s\n", p.name, p.message)
   return msg2
}

func NewEvent(p People) Event {
   return Event{people: p}
}

// 小明去說話這個行為抽象為一個事件
type Event struct {
   people People
}

func (e Event) start() {
   msg2 := e.people.SayHello()
   fmt.Println(msg2)
}

func main() {
   e := InitializeEvent()
   e.start()
}

代碼中wire為我們生成瞭如下代碼:

// Injectors from main.go:

func InitializeEvent() Event {
   message := NewMessage()
   people := NewPeople(message)
   event := NewEvent(people)
   return event
}

在看看我們剛開始寫的代碼,發現其實是一樣的,是不是感覺方便了很多。

註意:當使用 Wire 時,我們將同時提交 Wire.go 和 Wire _ gen 到代碼倉庫

wire 能做的事情很多,如果我們相互依賴的初始化其中有初始化失敗的,wire也能幫我們很好的處理。

慄子2

package main

import (
   "errors"
   "fmt"
   "os"
   "time"

   "github.com/google/wire"
)

var msg = "Hello World!"

func NewMessage() Message {
   return Message(msg)
}

// 要說的內容的抽象
type Message string

func NewPeople(m Message) People {
   var grumpy bool
   if time.Now().Unix()%2 == 0 {
      grumpy = true
   }
   return People{name: "小明", message: m, grumpy: grumpy}
}

// 小明這個人的抽象
type People struct {
   name    string
   message Message
   grumpy  bool // 脾氣是否暴躁
}

// 小明這個人會說話
func (p People) SayHello() string {
   if p.grumpy {
      // 脾氣暴躁,心情不好
      msg := "Go away !"
      return msg
   }
   msg := fmt.Sprintf("%s 對世界說:%s\n", p.name, p.message)
   return msg

}

func NewEvent(p People) (Event, error) {
   if p.grumpy {
      return Event{}, errors.New("could not create event: event greeter is grumpy")
   }
   return Event{people: p}, nil
}
https://github.com/LyricTian/gin-admin
// 小明去說話這個行為抽象為一個事件
type Event struct {
   people People
}

func (e Event) start() {
   msg := e.people.SayHello()
   fmt.Println(msg)
}

func InitializeEvent() (Event, error) {
   wire.Build(NewEvent, NewPeople, NewMessage)
   return Event{}, nil
}

func main() {
   e, err := InitializeEvent()
   if err != nil {
      fmt.Printf("failed to create event: %s\n", err)
      os.Exit(2)
   }
   e.start()
}

更改之後的代碼初始化NewEvent 可能就會因為People.grumpy 的值而失敗,通過wire生成之後的代碼

// Injectors from main.go:

func InitializeEvent() (Event, error) {
   message := NewMessage()
   people := NewPeople(message)
   event, err := NewEvent(people)
   if err != nil {
      return Event{}, err
   }
   return event, nil
}

慄子3

我們再將上面的代碼進行更改:

package main

import (
   "errors"
   "fmt"
   "os"
   "time"

   "github.com/google/wire"
)

func NewMessage(msg string) Message {
   return Message(msg)
}

// 要說的內容的抽象
type Message string

func NewPeople(m Message) People {
   var grumpy bool
   if time.Now().Unix()%2 == 0 {
      grumpy = true
   }
   return People{name: "小明", message: m, grumpy: grumpy}
}

// 小明這個人的抽象
type People struct {
   name    string
   message Message
   grumpy  bool // 脾氣是否暴躁
}

// 小明這個人會說話
func (p People) SayHello() string {
   if p.grumpy {
      // 脾氣暴躁,心情不好
      msg := "Go away !"
      return msg
   }
   msg := fmt.Sprintf("%s 對世界說:%s\n", p.name, p.message)
   return msg

}

func NewEvent(p People) (Event, error) {
   if p.grumpy {
      return Event{}, errors.New("could not create event: event greeter is grumpy")
   }
   return Event{people: p}, nil
}

// 小明去說話這個行為抽象為一個事件
type Event struct {
   people People
}

func (e Event) start() {
   msg := e.people.SayHello()
   fmt.Println(msg)
}

func InitializeEvent(msg string) (Event, error) {
   wire.Build(NewEvent, NewPeople, NewMessage)
   return Event{}, nil
}

func main() {
   msg := "Hello Golang"https://github.com/LyricTian/gin-admin
   e, err := InitializeEvent(msg)
   if err != nil {
      fmt.Printf("failed to create event: %s\n", err)
      os.Exit(2)
   }
   e.start()
}

上面的更改主要是NewPeople 函數增加了msg參數,同時InitializeEvent增加了msg參數,這個時候我們通過wire生成代碼則可以看到如下:

// Injectors from main.go:

func InitializeEvent(msg string) (Event, error) {
	message := NewMessage(msg)
	people := NewPeople(message)
	event, err := NewEvent(people)
	if err != nil {
		return Event{}, err
	}
	return event, nil
}

wire 會檢查註入器的參數,並檢查到NewMessage 需要msg的參數,所以它將msg傳遞給了NewMessage

慄子4

如果我們傳給wire.Build 的依賴關係存在問題,wire會怎麼處理呢? 我們調整InitializeEvent 的代碼:

func InitializeEvent(msg string) (Event, error) {
   wire.Build(NewEvent, NewMessage)
   return Event{}, nil
}

然後執行wire 進行代碼的生成:

➜  useWireBaseExample4 wire .
wire: /home/fan/codes/go_project/awesomeProject/202006/useWireBaseExample4/main.go:63:1: inject InitializeEvent: no provider found for awesomeProject/202006/useWireBaseExample4.People
        needed by awesomeProject/202006/useWireBaseExample4.Event in provider "NewEvent" (/home/fan/codes/go_project/awesomeProject/202006/useWireBaseExample4/main.go:46:6)
wire: awesomeProject/202006/useWireBaseExample4: generate failed
wire: at least one generate failure
➜  useWireBaseExample4 

錯誤提示中非常清楚的告訴我它找不到no provider found ,如果我們傳給wire.Build 沒有用的依賴,它依然會給我們提示告訴我們 unused provider "main.NewEventNumber"

➜  useWireBaseExample4 wire .
wire: /home/fan/codes/go_project/awesomeProject/202006/useWireBaseExample4/main.go:67:1: inject InitializeEvent: unused provider "main.NewEventNumber"
wire: awesomeProject/202006/useWireBaseExample4: generate failed
wire: at least one generate failure

wire的高級用法

Binding Interfaces

依賴註入通常用於綁定介面的具體實現。通過下麵的例子理解:

// Run 運行服務
func Run(ctx context.Context, opts ...Option) error {
	var state int32 = 1
	sc := make(chan os.Signal, 1)
	signal.Notify(sc, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT)
	cleanFunc, err := Init(ctx, opts...)
	if err != nil {
		return err
	}

EXIT:
	for {
		sig := <-sc
		logger.Printf(ctx, "接收到信號[%s]", sig.String())
		switch sig {
		case syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT:
			atomic.CompareAndSwapInt32(&state, 1, 0)
			break EXIT
		case syscall.SIGHUP:
		default:
			break EXIT
		}
	}

	cleanFunc()
	logger.Printf(ctx, "服務退出")
	time.Sleep(time.Second)
	os.Exit(int(atomic.LoadInt32(&state)))
	return nil
}package main

import (
	"fmt"

	"github.com/google/wire"
)

type Fooer interface {
	Foo() string
}

type MyFooer string

func (b *MyFooer) Foo() string {
	return string(*b)
}

func provideMyFooer() *MyFooer {
	b := new(MyFooer)
	*b = "Hello, World!"
	return b
}

type Bar string

func provideBar(f Fooer) string {
	// f will be a *MyFooer.
	return f.Foo()
}


func InitializeEvent() string {
	wire.Build(provideMyFooer, provideBar, wire.Bind(new(Fooer), new(*MyFooer)))
	return ""
}
func main() {
	ret := InitializeEvent()
	fmt.Println(ret)
}

我們可以看到Fooer 是一個interface, MyFooer 實現了Fooer 這個介面,同時provideBar 的參數是Fooer 介面類型。可以看到// Run 運行服務

func Run(ctx context.Context, opts ...Option) error {
	var state int32 = 1
	sc := make(chan os.Signal, 1)
	signal.Notify(sc, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT)
	cleanFunc, err := Init(ctx, opts...)
	if err != nil {
		return err
	}

EXIT:
	for {
		sig := <-sc
		logger.Printf(ctx, "接收到信號[%s]", sig.String())
		switch sig {
		case syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT:
			atomic.CompareAndSwapInt32(&state, 1, 0)
			break EXIT
		case syscall.SIGHUP:
		default:
			break EXIT
		}
	}
	logger.Printf(ctx, "服務退出")
	time.Sleep(time.Second)
	os.Exit(int(atomic.LoadInt32(&state)))
	return nil
}

代碼中我們用了wire.Bind方法,為什麼這麼用呢?如果我們wire.Build的那段代碼寫成如下:

wire.Build(provideMyFooer, provideBar),再次用wire生成代碼則會提示如下錯誤:https://github.com/LyricTian/gin-admin

➜  useWireBaseExample5 wire .
wire: /home/fan/codes/go_project/awesomeProject/202006/useWireBaseExample5/main.go:36:1: inject InitializeEvent: no provider found for awesomeProject/202006/useWireBaseExample5.Fooer
        needed by string in provider "provideBar" (/home/fan/codes/go_project/awesomeProject/202006/useWireBaseExample5/main.go:27:6)
wire: awesomeProject/202006/useWireBaseExample5: generate failed
wire: at least one generate failure

這是因為我們傳遞給provideBar 需要的是 Fooer 介面類型,我們傳給wire.Build 的是provideMyFooer, provideBar 這個時候預設從依賴關係里,provideBar 沒有找能夠提供Fooer的provider, 雖然我們我們都知道MyFooer 實現了Fooer 這個介面。所以我們需要在wire.Build 里告訴它,我們傳遞provideMyFooer 就是provideBar的provider。wire.Bind 就是來做這件事情的。

wire.Bind 的第一個參數是介面類型的值的指針,第二個參數是實現第一個參數介面的類型的值的指針。

這樣當我們在用wire生成代碼的時候就正常了。

Struct Providers

wire還可以用於結構體的構造。先直接看使用的例子:

package main

import (
   "fmt"

   "github.com/google/wire"
)

type Foo int
type Bar int

func ProvideFoo() Foo {
   return Foo(1)
}

func ProvideBar() Bar {
   return Bar(2)// Run 運行服務
func Run(ctx context.Context, opts ...Option) error {
	var state int32 = 1
	sc := make(chan os.Signal, 1)
	signal.Notify(sc, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT)
	cleanFunc, err := Init(ctx, opts...)
	if err != nil {
		return err
	}

EXIT:
	for {
		sig := <-sc
		logger.Printf(ctx, "接收到信號[%s]", sig.String())
		switch sig {
		case syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT:
			atomic.CompareAndSwapInt32(&state, 1, 0)
			break EXIT
		case syscall.SIGHUP:
		default:
			break EXIT
		}
	}

	cleanFunc()
	logger.Printf(ctx, "服務退出")
	time.Sleep(time.Second)
	os.Exit(int(atomic.LoadInt32(&state)))
	return nil
}
}

type FooBar struct {
   MyFoo Foo
   MyBar Bar
}

var Set = wire.NewSet(
   ProvideFoo,
   ProvideBar,
   wire.Struct(new(FooBar), "MyFoo", "MyBar"),
)

func injectFooBar() FooBar {
   wire.Build(Set)
   return FooBar{}
}

func main() {
   fooBar := injectFooBar()
   fmt.Println(fooBar)
}

上面的例子其實很簡單,我們構造FooBar 結構題我們需要MyFooMyBar ,而ProvideFooProvideBar 就是用於生成MyFooMyBarwire.Struct 也可以幫我們做這件事情。我們通過wire生成的代碼如下:

// Injectors from main.go:

func injectFooBar() FooBar {
   foo := ProvideFoo()
   bar := ProvideBar()
   fooBar := FooBar{
      MyFoo: foo,
      MyBar: bar,
   }
   return fooBar
}

wire.Struct 的第一個參數是所需結構類型的指針,後面的參數是要註入的欄位的名稱。可以使用一個特殊的字元串“ * ”作為告訴註入器註入所有欄位的快捷方式。 所以我們上面的代碼也可以寫成:wire.Struct(new(FooBar), "×") ,而當我們使用* 這種方式的時候可能會把一些不需要註入的欄位註入了,如鎖,那麼類似這種情況,如果我們註入,卡一通過wire:"-" 的方式告訴wire 該欄位不進行註入。

type Foo struct {
    mu sync.Mutex `wire:"-"`
    Bar Bar
}

Binding Values

這個功能主要就是給數據類型綁定一個預設值,代碼例子如下:

https://github.com/LyricTian/gin-adminpackage main

import (
   "fmt"

   "github.com/google/wire"
)

type Foo struct {
   X int
}

func injectFoo() Foo {
   wire.Build(wire.Value(Foo{X: 11}))
   return Foo{}
}

func main() {
   foo := injectFoo()
   fmt.Println(foo)
}

我通過wire生成的代碼如下:

// Code generated by Wire. DO NOT EDIT.

//go:generate wire
//+build !wireinject

package main

import (
   "fmt"
)

// Injectors from main.go:

func injectFoo() Foo {
   foo := _wireFooValue
   return foo
}

var (
   _wireFooValue = Foo{X: 11}
)

// main.go:

type Foo struct {
   X int
}

func main() {
   foo := injectFoo()
   fmt.Println(foo)
}

Use Fields of a Struct as Providers

有時,我們需要獲取結構體的某些欄位,按照我們已經使用的wire的用法,你可能會這樣寫代碼:

package main

import (
   "fmt"

   "github.com/google/wire"
)

type Foo struct {
   S string
   N int
   F float64
}

func getS(foo Foo) string {
   return foo.S
}

func provideFoo() Foo {
   return Foo{S: "Hello, World!", N: 1, F: 3.14}
}

func injectedMessage() string {
   wire.Build(
      provideFoo,
      getS,
   )
   return ""
}

func main() {
   ret := injectedMessage()
   fmt.Println(ret)
}

這種用法當然也可以實現,但是wire其實提供了更好的辦法來實現wire.FieldsOf, 我們將上面的代碼進行更改如下,通過wire生成的代碼其實和上面的是一樣的:

package main

import (
   "fmt"

   "github.com/google/wire"
)

type Foo struct {
   S string
   N int
   F float64
}

func provideFoo() Foo {
   return Foo{S: "Hello, World!", N: 1, F: 3.14}
}

func injectedMessage() string {
   wire.Build(
      provideFoo,
      wire.FieldsOf(new(Foo), "S"),
   )
   return ""
}

func main() {
   ret := injectedMessage()
   fmt.Println(ret)
}

Cleanup functions

如果我們的Provider創建了一個需要做clean 的值,例如關閉文件,關閉數據連接..., 這裡也是可以返回一個閉包來清理資源,註入器將使用它向調用者返回一個聚合的清理函數,或者如果稍後在註入器實現中調用的提供程式返回一個錯誤,則使用它來清理資源。

關於這個功能的使用,通過https://github.com/LyricTian/gin-admin 的代碼中的使用,可以更加清楚。

作者在gin-admin/internal/app/app.go 中進行了初始化依賴註入器

// 初始化依賴註入器
injector, injectorCleanFunc, err := injector.BuildInjector()
if err != nil {
   return nil, err
}

我們在看看下wire生成的wire_gen.go代碼:

// Injectors from wire.go:

func BuildInjector() (*Injector, func(), error) {
   auther, cleanup, err := InitAuth()
   if err != nil {
      return nil, nil, err
   }
   db, cleanup2, err := InitGormDB()
   if err != nil {
      cleanup()
      return nil, nil, err
   }
   role := &model.Role{
      DB: db,
   }
   roleMenu := &model.RoleMenu{
      DB: db,
   }
   menuActionResource := &model.MenuActionResource{
      DB: db,
   }
   user := &model.User{
      DB: db,
   }
   userRole := &model.UserRole{
      DB: db,
   }
   casbinAdapter := &adapter.CasbinAdapter{
      RoleModel:         role,
      RoleMenuModel:     roleMenu,
      MenuResourceModel: menuActionResource,
      UserModel:         user,
      UserRoleModel:     userRole,
   }
   syncedEnforcer, cleanup3, err := InitCasbin(casbinAdapter)
   if err != nil {
      cleanup2()
      cleanup()
      return nil, nil, err
   }
   demo := &model.Demo{
      DB: db,
   }
   bllDemo := &bll.Demo{
      DemoModel: demo,
   }
   apiDemo := &api.Demo{
      DemoBll: bllDemo,
   }
   menu := &model.Menu{
      DB: db,
   }
   menuAction := &model.MenuAction{
      DB: db,
   }
   login := &bll.Login{
      Auth:            auther,
      UserModel:       user,
      UserRoleModel:   userRole,
      RoleModel:       role,
      RoleMenuModel:   roleMenu,
      MenuModel:       menu,
      MenuActionModel: menuAction,
   }
   apiLogin := &api.Login{
      LoginBll: login,
   }
   trans := &model.Trans{
      DB: db,
   }
   bllMenu := &bll.Menu{
      TransModel:              trans,
      MenuModel:               menu,
      MenuActionModel:         menuAction,
      MenuActionResourceModel: menuActionResource,
   }
   apiMenu := &api.Menu{
      MenuBll: bllMenu,
   }
   bllRole := &bll.Role{
      Enforcer:      syncedEnforcer,
      TransModel:    trans,
      RoleModel:     role,
      RoleMenuModel: roleMenu,
      UserModel:     user,
   }
   apiRole := &api.Role{
      RoleBll: bllRole,
   }
   bllUser := &bll.User{
      Enforcer:      syncedEnforcer,
      TransModel:    trans,
      UserModel:     user,
      UserRoleModel: userRole,
      RoleModel:     role,
   }
   apiUser := &api.User{
      UserBll: bllUser,
   }
   routerRouter := &router.Router{
      Auth:           auther,
      CasbinEnforcer: syncedEnforcer,
      DemoAPI:        apiDemo,
      LoginAPI:       apiLogin,
      MenuAPI:        apiMenu,
      RoleAPI:        apiRole,
      UserAPI:        apiUser,
   }
   engine := InitGinEngine(routerRouter)
   injector := &Injector{
      Engine:         engine,
      Auth:           auther,
      CasbinEnforcer: syncedEnforcer,
      MenuBll:        bllMenu,
   }
   return injector, func() {
      cleanup3()
      cleanup2()
      cleanup()
   }, nil
}

而當程式退出的時候這上面代碼返回的那些清理操作都會被執行:

// Run 運行服務
func Run(ctx context.Context, opts ...Option) error {
   var state int32 = 1
   sc := make(chan os.Signal, 1)
   signal.Notify(sc, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT)
   cleanFunc, err := Init(ctx, opts...)
   if err != nil {
      return err
   }

EXIT:
   for {
      sig := <-sc
      logger.Printf(ctx, "接收到信號[%s]", sig.String())
      switch sig {
      case syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT:
         atomic.CompareAndSwapInt32(&state, 1, 0)
         break EXIT
      case syscall.SIGHUP:
      default:
         break EXIT
      }
   }
   // 在這裡執行了清理工作
   cleanFunc()
   logger.Printf(ctx, "服務退出")
   time.Sleep(time.Second)
   os.Exit(int(atomic.LoadInt32(&state)))
   return nil
}

延伸閱讀


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 一、列印float類型 %f來表示float和double類型的數字 %e來列印指數記數法的浮點數 如果系統支持十六進位的浮點數,可以使用a和A分別來代替e和E 列印long double類型要使用%Lf或%Le或%La來替代 #include<stdio.h> int D12_1_showf_pt ...
  • 異常處理 把可能會發生的錯誤,提前在代碼里進行捕捉(監測) try : code except Exception: 出錯後要執行的代碼 下麵是常見的異常: attributeError 試圖訪問一個對象沒有的屬性 Nameerror 訪問一個沒有變數 Valueerror 值類型不匹配 impor ...
  • 學習時常常忘記某個容器怎麼用。別怕,只要用時會查。太多也記不過來。 微軟的c++文檔:https://docs.microsoft.com/zh-cn/cpp/standard-library/vector-class?view=vs-2019 又發現一個網上查文檔的網站了,感覺還不錯。鏈接:htt ...
  • volatile 這個關鍵字可能很多朋友都聽說過,或許也都用過。在 Java 5 之前,它是一個備受爭議的關鍵字,因為在程式中使用它往往會導致出人意料的結果。在 Java 5之後,volatile 關鍵字才得以重獲生機。 ...
  • <form action="/search" id="search_form"> <input type="text" name="keywords" value="" placeholder="Furniture Handles" class="jhser" /> <span class="ser ...
  • Java生鮮電商平臺-微服務電商優惠券的架構設計(小程式/APP) 說明:Java生鮮電商平臺的優惠券屬於電子優惠券,不過我們要先看看線下紙質優惠券: 商家決定做促銷,印製了10000張50元代金券; 其中1000張代金券分別發給1000個用戶; 到某一個時刻,這1000個用戶有300個適用了代金券 ...
  • 一、Java基礎知識 二、軟體 1. 軟體是什麼? 軟體=數據+指令[命令]+文檔 2. 軟體開髮指什麼? 軟體開發是根據用戶的需求創建出相應的軟體系統. 軟體開發是一個過程,包含需求的提取,需求分析,軟體的編寫,軟體測試. 三、人與電腦做交互 圖形化界面 vs 命令行方式 dir md rd c ...
  • 前言 剛回到家,又被公司群里的消息轟炸了。讓統計每個人最近是否去過石景山萬達廣場。 這基本上已經是每日必備了,只要有任何風吹草動,就需要我們填各種信息。 我們都知道,北京最近的疫情很不樂觀,從每天的數據就能看出來了。 也許很多小伙伴不在北京,是切實感受不到的。 就拿我自己舉例吧,在去公司的每一天,我 ...
一周排行
    -Advertisement-
    Play Games
  • 前言 插件化的需求主要源於對軟體架構靈活性的追求,特別是在開發大型、複雜或需要不斷更新的軟體系統時,插件化可以提高軟體系統的可擴展性、可定製性、隔離性、安全性、可維護性、模塊化、易於升級和更新以及支持第三方開發等方面的能力,從而滿足不斷變化的業務需求和技術挑戰。 一、插件化探索 在WPF中我們想要開 ...
  • 歡迎ReaLTaiizor是一個用戶友好的、以設計為中心的.NET WinForms項目控制項庫,包含廣泛的組件。您可以使用不同的主題選項對項目進行個性化設置,並自定義用戶控制項,以使您的應用程式更加專業。 項目地址:https://github.com/Taiizor/ReaLTaiizor 步驟1: ...
  • EDP是一套集組織架構,許可權框架【功能許可權,操作許可權,數據訪問許可權,WebApi許可權】,自動化日誌,動態Interface,WebApi管理等基礎功能於一體的,基於.net的企業應用開發框架。通過友好的編碼方式實現數據行、列許可權的管控。 ...
  • Channel 是乾什麼的 The System.Threading.Channels namespace provides a set of synchronization data structures for passing data between producers and consume ...
  • efcore如何優雅的實現按年分庫按月分表 介紹 本文ShardinfCore版本 本期主角: ShardingCore 一款ef-core下高性能、輕量級針對分表分庫讀寫分離的解決方案,具有零依賴、零學習成本、零業務代碼入侵適配 距離上次發文.net相關的已經有很久了,期間一直在從事java相關的 ...
  • 前言 Spacesniffer 是一個免費的文件掃描工具,通過使用樹狀圖可視化佈局,可以立即瞭解大文件夾的位置,幫助用戶處理找到這些文件夾 當前系統C盤空間 清理後系統C盤空間 下載 Spacesniffer 下載地址:https://spacesniffer.en.softonic.com/dow ...
  • EDP是一套集組織架構,許可權框架【功能許可權,操作許可權,數據訪問許可權,WebApi許可權】,自動化日誌,動態Interface,WebApi管理等基礎功能於一體的,基於.net的企業應用開發框架。通過友好的編碼方式實現數據行、列許可權的管控。 ...
  • 一、ReZero簡介 ReZero是一款.NET中間件 : 全網唯一開源界面操作就能生成API , 可以集成到任何.NET6+ API項目,無破壞性,也可讓非.NET用戶使用exe文件 免費開源:MIT最寬鬆協議 , 一直從事開源事業十年,一直堅持開源 1.1 純ReZero開發 適合.Net Co ...
  • 一:背景 1. 講故事 停了一個月沒有更新文章了,主要是忙於寫 C#內功修煉系列的PPT,現在基本上接近尾聲,可以回頭繼續更新這段時間分析dump的一些事故報告,有朋友微信上找到我,說他們的系統出現了大量的http超時,程式不響應處理了,讓我幫忙看下怎麼回事,dump也抓到了。 二:WinDbg分析 ...
  • 開始做項目管理了(本人3年java,來到這邊之後真沒想到...),天天開會溝通整理需求,他們講話的時候忙裡偷閑整理一下常用的方法,其實語言還是有共通性的,基本上看到方法名就大概能猜出來用法。出去打水的時候看到外面太陽好好,真想在外面坐著曬太陽,回來的時候好兄弟三年前送給我的鍵盤D鍵不靈了,在打"等待 ...