从别人的代码中学习golang系列--02
这篇博客还是整理从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 结构题我们需要MyFoo 和 MyBar ,而ProvideFoo 和 ProvideBar 就是用于生成MyFoo 和 MyBar,wire.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
}
延伸阅读
从别人的代码中学习golang系列--02的更多相关文章
- 从别人的代码中学习golang系列--01
		自己最近在思考一个问题,如何让自己的代码质量逐渐提高,于是想到整理这个系列,通过阅读别人的代码,从别人的代码中学习,来逐渐提高自己的代码质量.本篇是这个系列的第一篇,我也不知道自己会写多少篇,但是希望 ... 
- 从别人的代码中学习golang系列--03
		这篇博客还是整理从https://github.com/LyricTian/gin-admin 这个项目中学习的golang相关知识. 作者在项目中使用了 github.com/casbin/casb ... 
- 在C#代码中应用Log4Net系列教程
		在C#代码中应用Log4Net系列教程(附源代码) Log4Net应该可以说是DotNet中最流行的开源日志组件了.以前需要苦逼写的日志类,在Log4Net中简单地配置一下就搞定了.没用过Log4 ... 
- Python--网络编程学习笔记系列02 附:tcp服务端,tcp客户端
		Python--网络编程学习笔记系列02 TCP和UDP的概述: udp通信模型类似于写信,不需要建立相关链接,只需要发送数据即可(现在几乎不用:不稳定,不安全) tcp通信模型类似于打电话,一定要建 ... 
- 在C#代码中应用Log4Net系列教程(附源代码)
		Log4Net应该可以说是DotNet中最流行的开源日志组件了.以前需要苦逼写的日志类,在Log4Net中简单地配置一下就搞定了.没用过Log4Net,真心不知道原来日志组件也可以做得这么灵活,当然这 ... 
- [转]在C#代码中应用Log4Net系列教程(附源代码)
		Log4Net应该可以说是DotNet中最流行的开源日志组件了.以前需要苦逼写的日志类,在Log4Net中简单地配置一下就搞定了.没用过Log4Net,真心不知道原来日志组件也可以做得这么灵活,当然这 ... 
- NeteaseCloudWebApp模仿网易云音乐的vue自己从开源代码中学习到的
		github地址: https://github.com/javaSwing/NeteaseCloudWebApp 1.Vue.prototype.$http = Axios // 类似于vue-re ... 
- 在C#代码中应用Log4Net系列教程(附源代码)地址
		在博客园看到一篇关于Log4Net使用教程,比较详细,感谢这位热心的博主 博客园地址:http://www.cnblogs.com/kissazi2/archive/2013/10/29/339359 ... 
- WebService学习笔记系列(二)
		soap(简单对象访问协议),它是在http基础之上传递xml格式数据的协议.soap协议分为两个版本,soap1.1和soap1.2. 在学习webservice时我们有一个必备工具叫做tcpmon ... 
随机推荐
- CDN百科第三讲 | 如果用了云服务器,还需要做CDN加速吗?
			在全站上云的背景下,云计算已经不仅仅是大型互联网公司的独享概念,正在被更多的传统企业.中小企业甚至个人站长所采用.在众多云计算服务中,最常见两个产品就是云服务器和CDN,今天的CDN百科第三讲,就给大 ... 
- 三分钟搭建websocket实时在线聊天,项目经理也不敢这么写
			我们先看一下下面这张图: 可以看到这是一个简易的聊天室,两个窗口的消息是实时发送与接收的,这个主要就是用我们今天要讲的websocket实现的. websocket是什么? websocket是一种网 ... 
- TCP协议“三次握手”与“四次挥手”详解(下)
			前面进行“三次握手”建立连接后,当客户端的数据发送完毕,它就会要求与服务器端断开连接,那么就要进行“四次挥手”进行连接的释放. 注意,此处所谓的“客户端”与“服务器端”,只是为了方便标识连接的双方,即 ... 
- MySQL数据库离线包安装与注册
			本文主要介绍了MySQL数据库的离线安装和将MySQL服务注册为Windows应用服务的主要步骤. 1.下在安装程序包 MySQL Community Server 5.6.15 官方下载地址http ... 
- 05.Java面向对象
			一.面向对象基本概念 面向对象的特征 封装 封装是指利用抽象数据类型将数据(属性)和对数据的操作(方法)包装起来,把对象的属性和动作结合成一个独立的单位,并尽可能隐蔽对象的内部处理细节. 继承 一个类 ... 
- 其他函数-web_concurrent
			web_concurrent_start函数是并发组开始的标记.组中所有的函数是并发执行的,并发组的结束符为web_concurrent_end 函数. 在并发组中,可以包含的函数有: web_url ... 
- update语句基本用法
			UPDATE runoob_tbl SET runoob_title='学习 C++' WHERE runoob_id=; 
- 【JMeter_14】JMeter逻辑控制器__交替控制器<Interleave Controller>
			交替控制器<Interleave Controller> 业务逻辑: 根据被控制器触发执行次数,去依次执行控制器下的子节点<逻辑控制器.采样器>. 被触发执行可以由线程组的线程 ... 
- 在群晖NAS上运行URLOS之后竟然能安装Discuz! Q!!
			如果我们手头上有1台群晖NAS时,有没有考虑过把群晖NAS当成服务器来使用,这样会不会很有意思呢? 现在,我们终于可以尝试一番了,把群晖NAS变成一台实实在在的服务器,在上面跑各种运行环境!其实很简单 ... 
- express 框架的使用方法
			express 框架的使用方法: 第一步: 生成一个 (express)项目工程 命令提示框的指令是: express (文件名) express -e (文件名) 两段指令的 ... 
