Golang笔记
Golang相关配置
golang 配置goproxy可选的地址
IDEA/Goland使用WSL作为默认Terminal
GoLand 2022.1-X专业版激活
Win下用WSL作为Goland终端交叉编译
MacOS下在Goland的Terminal中使用‘ll’命令无效
GoLand 2024.1.X专业版激活
Golang LeeCode练习题
一 Golang数组问题
28. [简单] 寻找数组的中心下标
27. [简单] 数组的度
26. [简单] 最长连续递增序列
25. [简单] 非递减数列
24. [简单] 图片平滑器
23. [简单] 子数组最大平均数 I
22. [简单] 重塑矩阵
21. [简单] 数组拆分 I
20. [简单] 最大连续1的个数
19. [简单] 找到所有数组中消失的数字
18. [简单] 移动零
17. [简单] 丢失的数字
16. [简单] 汇总区间
15. [简单] 存在重复元素 II
14. [简单] 存在重复元素
13. [简单] 多数元素
12. [简单] 两数之和 II
11. [简单] 买卖股票的最佳时机 II
10. [简单] 买卖股票的最佳时机
09. [简单] 杨辉三角 II
08. [简单] 杨辉三角
07. [简单] 合并两个有序数组
06. [简单] 加一
05. [简单] 最大子序和
04. [简单] 搜索插入位置
03. [简单] 移除元素
02. [简单] 删除有序数组中的重复项
01. [简单] 两数之和
29. [简单] 至少是其他数字两倍的最大数
30. [简单] 托普利茨矩阵
31. [简单] 较大分组的位置
32. [简单] 转置矩阵
33. [简单] 公平的糖果棒交换
34. [简单] 单调数列
35. [简单] 按奇偶排序数组
36. [简单] 卡牌分组
37. [中等] 盛最多水的容器
38. [中等] 三数之和
39. [中等] 最接近的三数之和
40. [中等] 四数之和
41. [中等] 下一个排列
42. [中等] 搜索旋转排序数组
43. [中等] 在排序数组中查找元素的第一个和最后一个位置
44. [中等] 组合总和
45. [中等] 旋转图像
Golang完整学习记录
第一章 Go语言简介
20220519@基础环境
20220518@概述
第二章 Go语言基本语法
20220520@基础语法
20220521@正弦函数
20220523@数据类型转换
20220523@指针概念
20220524@堆栈和逃逸分析
20220526@(模拟)枚举
20220528@类型别名
20220528@注释的使用
20220528@关键字与标识符
20220528@运算符的优先级
20220528@数据类型的转换
第三章 Go语言容器
20220531@容器概念
20220531@数组详解
20220531@多维数组
20220605@切片详解
20220606@append的常见操作
20220606@切片元素修改
20220609@多维切片简述
20220609@map映射
20220612@并发(sync)Map
20220614@list(列表)
20220614@nil值/空值/零值
20220615@new和make
第四章 Go语言控制流程
20220615@if分支结构
20220615@for循环
20220615@range遍历
20220615@switch
20220616@goto标签
20220616@break和continue
20220616@聊天机器人
20220620@词频统计
20220622@缩进排序
20220622@二分查找算法
20220622@冒泡排序
20220623@分布式id生成器
第五章 Go语言函数
20220623@函数声明
20220623@函数参数传递效果
20220627@字符串的链式处理
20220630@匿名函数
20220704@函数类型接口
20220704@闭包(Closure)
20220706@可变参数
20220706@defer延迟语句
20220709@递归函数
20220713@处理运行错误
20220714@宕机(panic)
20220714@宕机恢复(recover)
20220715@计算函数耗时
20220718@内存缓存提升性能
20220718@哈希函数
20220720@Test功能测试
第六章 Go语言结构体
20220726@结构体定义
20220726@为结构体分配内存
20220730@实例化结构体
20220803@初始化结构体成员变量
20220810@构造函数
20220816@方法和接收器
20220816@为基本类型添加方法
20220816@使用事件系统实现事件响应和处理
20220817@类型内嵌和结构体内嵌
20220817@结构体内嵌模拟类的继承
20220817@初始化内嵌结构体
20220818@内嵌结构体成员名字冲突
20220823@使用匿名结构体解析JSON数据
20220827@垃圾回收和SetFinalizer
20220828@结构体数据保存为JSON格式
20220901@链表操作
20220908@数据I/O对象及操作
第七章 Go语言接口
20220911@接口定义
20220915@实现接口的条件
20220918@类型与接口的关系
20220918@接口的nil判断
20020918@类型断言简述
20220929@多输出实现日志系统
20221009@排序(by sort.Interface)
20221106@接口的嵌套组合
20221107@接口和类型之间的转换
20221109@空接口类型(interface{})
20221107@空接口实现任意值的字典保存
20221112@switch类型分支
20221201@Error接口返回错误信息
20221229@表达式求值器
20221229@实现Web服务器
20221229@部署Go程序到Linux
20221229@音乐播放器
20221230@有限状态机(FSM)
20221230@二叉树数据结构的应用
第八章 Go语言包概念
20230206@包的基本概念
20230212@封装简介及实现细节
20220212@GOPATH详解
20230212@常用内置包简介
20230212@自定义包
20230212@package(创建包)
20230212@import导入包
20230213@工厂模式自动注册
20230213@单例模式
20230214@sync包与锁
20230215@big包实现整数的高精度计算
20230215@使用图像包制作GIF动画
20230216@正则regexp包
20230218@time包:时间和日期
20230219@go mod包依赖管理工具
20230219@os包用法简述
20230219@flag包:命令行参数解析
20230219@生成二维码
20230219@Context(上下文)
20230220@示例:客户信息管理系统
20230221@发送电子邮件
20230222@Pingo插件化开发
20230221@定时器实现原理及作用
第九章 Go语言并发
20230224@并发简述(并发的优势)
20230224@goroutine(轻量级线程)
202300226@并发通信channe简介
20230226@竞争状态简述
20230227@GOMAXPROCS(并发运行性能)
20230227@并发和并行的区别
20230227@goroutine和coroutine的区别
20230227@通道(channel)—goroutine之间通信的管道
20230227@并发打印(借助通道实现)
20230227@单向通道——通道中的单行道
20230301@无缓冲的通道
20230301@带缓冲的通道
20230302@channel超时机制
20230302@通道的多路复用
20230302@RPC(模拟远程过程调用)
20230304@使用通道响应计时器的事件
20230306@关闭通道后继续使用通道
20230306@多核并行化
20230306@Telnet回音服务器-TCP服务器的基本结构
20230307@竞态检测——检测代码在并发环境下可能出现的问题
20230310@互斥锁(sync.Mutex)和读写互斥锁(sync.RWMutex)
20230310@等待组(sync.WaitGroup)
20230310@死锁、活锁和饥饿概述
20230311@封装qsort快速排序函数
20230311@CSP:并发通信顺序进程简述
20230312@聊天服务器
20230313@如何更加高效的使用并发
20230313@使用select切换协程
20230313@加密通信
第十章 Go语言反射
20230317@反射(reflection)简述
20230318@反射规则浅析
20230319@反射的性能和灵活性测试
20230322@通过反射获取类型信息(reflect.TypeOf()和reflect.Type)
20230325@通过反射获取指针指向的元素类型(reflect.Elem())
20230325@通过反射获取结构体的成员类型
20230325@结构体标签(Struct Tag)
20230325@通过反射获取值信息(reflect.ValueOf()和reflect.Value)
20230326@通过反射访问结构体成员的值
20230326@判断反射值的空和有效性(IsNil()和IsValid())
20230327@通过反射修改变量的值
20230327@通过类型信息创建实例
20230327@通过反射调用函数
20230327@依赖注入(inject库)
第十一章 文件处理
20230327@自定义数据文件
20230328@JSON文件的读写操作
20230402@XML文件的读写操作
20230402@使用Gob传输数据
20230404@纯文本文件的读写操作
20230405@二进制文件的读写操作
20230405@自定义二进制文件的读写操作
20230405@zip归档文件的读写操作
20230405@tar归档文件的读写操作
20230408@使用buffer读写文件
20230409@实现Unix中du命令统计文件
20230410@从INI文件中读取配置
20240411@文件的读写追加和复制
202304111@文件锁操作
第十二章 Go语言编译与工具
20230411@go build命令使用
20230413@clean命令-清除编译文件
20230413@run命令-编译并运行
20230413@fmt命令-格式化代码文件
20230413@install命令-编译并安装
20230414@go get命令-获取代码编译并安装
20230414@go generate命令-在编译前自动生成某类代码
20230415@go test命令-单元和性能测试
20230415@go pprof-性能分析命令
20230415@Go语言与C/C++进行交互
20230415@Go语言内存管理简述
20230415@Go语言垃圾回收
20230415@Go语言实现RSA和AES加解密
Golang简单实战
Golang根据书籍ISBN爬取豆瓣评分和评论数
Go编写使用指定的CPU百分比消耗CPU资源
Golang的日常应用
使用 FFmpeg 进行实时码率检测
WSL的远程开发应用
WSL2设置静态IP
在WSL2中启动SSH
使用CentOS7作为Goland终端的修改项
Golang学习路线
Go开发者成长路线图
本文档使用 MrDoc 发布
-
+
home page
20230327@依赖注入(inject库)
在介绍 inject 之前我们先来简单介绍一下“依赖注入”和“控制反转”这两个概念。 正常情况下,对函数或方法的调用是我们的主动直接行为,在调用某个函数之前我们需要清楚地知道被调函数的名称是什么,参数有哪些类型等等。 - **控制反转**:就是将这种主动行为变成间接的行为,我们不用直接调用函数或对象,而是借助框架代码进行间接的调用和初始化,这种行为称作“控制反转”,库和框架能很好的解释控制反转的概念。 - **依赖注入**: 是实现控制反转的一种方法,如果说控制反转是一种设计思想,那么依赖注入就是这种思想的一种实现,通过注入参数或实例的方式实现控制反转。如果没有特殊说明,我们可以认为依赖注入和控制反转是一个东西。 控制反转的价值在于解耦,有了控制反转就不需要将代码写死,可以让控制反转的的框架代码读取配置,动态的构建对象,这一点在 Java 的 Spring 框架中体现的尤为突出。 # inject 实践 inject 是依赖注入的Go语言实现,它能在运行时注入参数,调用方法,是 Martini 框架(Go语言中著名的 Web 框架)的基础核心。 **引言** 在介绍具体实现之前,先来想一个问题,如何通过一个字符串类型的函数名来调用函数?Go语言没有 Java 中的 Class.forName 方法可以通过类名直接构造对象,所以这种方法是行不通的! 能想到的方法就是使用 map 实现一个字符串到函数的映射,示例代码如下: ```go func fl() { println ("fl") } func f2 () { println ("f2") } funcs := make(map[string] func ()) funcs ["fl"] = fl funcs ["f2"] = fl funcs ["fl"]() funcs ["f2"]() ``` 但是这有个缺陷,就是 map 的 Value 类型被写成 func(),不同参数和返回值的类型的函数并不能通用。将 map 的 Value 定义为 interface{} 空接口类型即可以解决该问题,但需要借助类型断言或反射来实现,通过类型断言实现等于又绕回去了,反射是一种可行的办法。 inject 包借助反射实现函数的注入调用,下面通过一个示例来看一下。 ```go package main import ( "fmt" "github.com/codegangsta/inject" ) type S1 interface{} type S2 interface{} func Format(name string, company S1, level S2, age int) { fmt.Printf("name = %s, company=%s, level=%s, age = %d!\n", name, company, level, age) } func main() { //控制实例的创建 inj := inject.New() //实参注入 inj.Map("tom") inj.MapTo("tencent", (*S1)(nil)) inj.MapTo("T4", (*S2)(nil)) inj.Map(23) //函数反转调用 inj.Invoke(Format) } ``` 运行结果如下: ```go name = tom, company=tencent, level=T4, age = 23! ``` 可见 inject 提供了一种注入参数调用函数的通用功能,inject.New() 相当于创建了一个控制实例,由其来实现对函数的注入调用。inject 包不但提供了对函数的注入,还实现了对 struct 类型的注入,示例代码如下所示: ```go package main import ( "fmt" "github.com/codegangsta/inject" ) type S1 interface{} type S2 interface{} type Staff struct { Name string `inject` Company S1 `inject` Level S2 `inject` Age int `inject` } func main() { //创建被注入实例 s := Staff{} //控制实例的创建 inj := inject.New() //初始化注入值 inj.Map("tom") inj.MapTo("tencent", (*S1)(nil)) inj.MapTo("T4", (*S2)(nil)) inj.Map(23) //实现对 struct 注入 inj.Apply(&s) //打印结果 fmt.Printf("s = %v\n", s) } ``` 运行结果如下: ``` s = {tom tencent T4 23} ``` 可以看到 inject 提供了一种对结构类型的通用注入方法。至此,我们仅仅从宏观层面了解 iniect 能做什么,下面从源码实现角度来分析 inject。 # inject 原理分析 inject 包中只有 2 个文件,一个是 inject.go 文件和一个 inject_test.go 文件,这里我们只需要关注 inject.go 文件即可。 inject.go 短小精悍,包括注释和空行在内才 157 行代码,代码中定义了 4 个接口,包括一个父接口和三个子接口,如下所示: ```go type Injector interface { Applicator Invoker TypeMapper SetParent(Injector) } type Applicator interface { Apply(interface{}) error } type Invoker interface { Invoke(interface{}) ([]reflect.Value, error) } type TypeMapper interface { Map(interface{}) TypeMapper MapTo(interface{}, interface{}) TypeMapper Get(reflect.Type) reflect.Value } ``` Injector 接口是 Applicator、Invoker、TypeMapper 接口的父接口,所以实现了 Injector 接口的类型,也必然实现了 Applicator、Invoker 和 TypeMapper 接口: - Applicator 接口只规定了 Apply 成员,它用于注入 struct。 - Invoker 接口只规定了 Invoke 成员,它用于执行被调用者。 - TypeMapper 接口规定了三个成员,Map 和 MapTo 都用于注入参数,但它们有不同的用法,Get 用于调用时获取被注入的参数。 另外 Injector 还规定了 SetParent 行为,它用于设置父 Injector,其实它相当于查找继承。也即通过 Get 方法在获取被注入参数时会一直追溯到 parent,这是个递归过程,直到查找到参数或为 nil 终止。 ```go type injector struct { values map[reflect.Type]reflect.Value parent Injector } func InterfaceOf(value interface{}) reflect.Type { t := reflect.TypeOf(value) for t.Kind() == reflect.Ptr { t = t.Elem() } if t.Kind() != reflect.Interface { panic("Called inject.InterfaceOf with a value that is not a pointer to an interface. (*MyInterface)(nil)") } return t } func New() Injector { return &injector{ values: make(map[reflect.Type]reflect.Value), } } ``` injector 是 inject 包中唯一定义的 struct,所有的操作都是基于 injector struct 来进行的,它有两个成员 values 和 parent。values 用于保存注入的参数,是一个用 reflect.Type 当键、reflect.Value 为值的 map,理解这点将有助于理解 Map 和 MapTo。 New 方法用于初始化 injector struct,并返回一个指向 injector struct 的指针,但是这个返回值被 Injector 接口包装了。 InterfaceOf 方法虽然只有几句实现代码,但它是 Injector 的核心。InterfaceOf 方法的参数必须是一个接口类型的指针,如果不是则引发 panic。InterfaceOf 方法的返回类型是 reflect.Type,大家应该还记得 injector 的成员 values 就是一个 reflect.Type 类型当键的 map。这个方法的作用其实只是获取参数的类型,而不关心它的值。 示例代码如下所示: ```go package main import ( "fmt" "github.com/codegangsta/inject" ) type SpecialString interface{} func main() { fmt.Println(inject.InterfaceOf((*interface{})(nil))) fmt.Println(inject.InterfaceOf((*SpecialString)(nil))) } ``` 运行结果如下: ``` interface {} main.SpecialString ``` InterfaceOf 方法就是用来得到参数类型,而不关心它具体存储的是什么值。 ```go func (i *injector) Map(val interface{}) TypeMapper { i.values[reflect.TypeOf(val)] = reflect.ValueOf(val) return i } func (i *injector) MapTo(val interface{}, ifacePtr interface{}) TypeMapper { i.values[InterfaceOf(ifacePtr)] = reflect.ValueOf(val) return i } func (i *injector) Get(t reflect.Type) reflect.Value { val := i.values[t] if !val.IsValid() && i.parent != nil { val = i.parent.Get(t) } return val } func (i *injector) SetParent(parent Injector) { i.parent = parent } ``` Map 和 MapTo 方法都用于注入参数,保存于 injector 的成员 values 中。这两个方法的功能完全相同,唯一的区别就是 Map 方法用参数值本身的类型当键,而 MapTo 方法有一个额外的参数可以指定特定的类型当键。但是 MapTo 方法的第二个参数 ifacePtr 必须是接口指针类型,因为最终 ifacePtr 会作为 InterfaceOf 方法的参数。 为什么需要有 MapTo 方法?因为注入的参数是存储在一个以类型为键的 map 中,可想而知,当一个函数中有一个以上的参数的类型是一样时,后执行 Map 进行注入的参数将会覆盖前一个通过 Map 注入的参数。 SetParent 方法用于给某个 Injector 指定父 Injector。Get 方法通过 reflect.Type 从 injector 的 values 成员中取出对应的值,它可能会检查是否设置了 parent,直到找到或返回无效的值,最后 Get 方法的返回值会经过 IsValid 方法的校验。 示例代码如下所示: ```go package main import ( "fmt" "reflect" "github.com/codegangsta/inject" ) type SpecialString interface{} func main() { inj := inject.New() inj.Map("C语言中文网") inj.MapTo("Golang", (*SpecialString)(nil)) inj.Map(20) fmt.Println("字符串是否有效?", inj.Get(reflect.TypeOf("Go语言入门教程")).IsValid()) fmt.Println("特殊字符串是否有效?", inj.Get(inject.InterfaceOf((*SpecialString)(nil))).IsValid()) fmt.Println("int 是否有效?", inj.Get(reflect.TypeOf(18)).IsValid()) fmt.Println("[]byte 是否有效?", inj.Get(reflect.TypeOf([]byte("Golang"))).IsValid()) inj2 := inject.New() inj2.Map([]byte("test")) inj.SetParent(inj2) fmt.Println("[]byte 是否有效?", inj.Get(reflect.TypeOf([]byte("Golang"))).IsValid()) } ``` 运行结果如下所示: ``` 字符串是否有效? true 特殊字符串是否有效? true int 是否有效? true []byte 是否有效? false []byte 是否有效? true ``` 这段代码中,首先使用 inject.New() 创建了一个新的依赖注入器 inj。接着,创建了另一个依赖注入器 inj2,并调用 `inj2.Map([]byte("test")) 将 []byte("test") `映射到 inj2 中,这意味着在 inj2 中注入了 []byte 类型的值。 接下来,调用 `inj.SetParent(inj2)` 将 inj2 设置为 inj 的父注入器。这意味着,当 inj 执行依赖注入时,**如果在自己的依赖项中找不到所需类型的值,它将从其父注入器 inj2 中查找**。 通过以上例子应该知道 SetParent 是什么样的行为,是不是很像面向对象中的查找链? ```go func (inj *injector) Invoke(f interface{}) ([]reflect.Value, error) { t := reflect.TypeOf(f) var in = make([]reflect.Value, t.NumIn()) //Panic if t is not kind of Func for i := 0; i < t.NumIn(); i++ { argType := t.In(i) val := inj.Get(argType) if !val.IsValid() { return nil, fmt.Errorf("Value not found for type %v", argType) } in[i] = val } return reflect.ValueOf(f).Call(in), nil } ``` Invoke 方法用于动态执行函数,当然执行前可以通过 Map 或 MapTo 来注入参数,因为通过 Invoke 执行的函数会取出已注入的参数,然后通过 reflect 包中的 Call 方法来调用。Invoke 接收的参数 f 是一个接口类型,但是 f 的底层类型必须为 func,否则会 panic。 ```go package main import ( "fmt" "github.com/codegangsta/inject" ) type SpecialString interface{} func Say(name string, gender SpecialString, age int) { fmt.Printf("My name is %s, gender is %s, age is %d!\n", name, gender, age) } func main() { inj := inject.New() inj.Map("张三") inj.MapTo("男", (*SpecialString)(nil)) inj2 := inject.New() inj2.Map(25) inj.SetParent(inj2) inj.Invoke(Say) } ``` 运行结果如下: ``` My name is 张三, gender is 男, age is 25! ``` 上面的例子如果没有定义 SpecialString 接口作为 gender 参数的类型,而把 name 和 gender 都定义为 string 类型,那么 gender 会覆盖 name 的值。 ```go func (inj *injector) Apply(val interface{}) error { v := reflect.ValueOf(val) for v.Kind() == reflect.Ptr { v = v.Elem() } if v.Kind() != reflect.Struct { return nil } t := v.Type() for i := 0; i < v.NumField(); i++ { f := v.Field(i) structField := t.Field(i) if f.CanSet() && structField.Tag == "inject" { ft := f.Type() v := inj.Get(ft) if !v.IsValid() { return fmt.Errorf("Value not found for type %v", ft) } f.Set(v) } } return nil } ``` Apply 方法是用于对 struct 的字段进行注入,参数为指向底层类型为结构体的指针。可注入的前提是:字段必须是导出的(也即字段名以大写字母开头),并且此字段的 tag 设置为``` `inject` ```。 示例代码如下所示: ```go package main import ( "fmt" "github.com/codegangsta/inject" ) type SpecialString interface{} type TestStruct struct { Name string `inject` Nick []byte Gender SpecialString `inject` uid int `inject` Age int `inject` } func main() { s := TestStruct{} inj := inject.New() inj.Map("张三") inj.MapTo("男", (*SpecialString)(nil)) inj2 := inject.New() inj2.Map(26) inj.SetParent(inj2) inj.Apply(&s) fmt.Println("s.Name =", s.Name) fmt.Println("s.Gender =", s.Gender) fmt.Println("s.Age =", s.Age) } ``` 运行结果如下: ``` s.Name = 张三 s.Gender = 男 s.Age = 26 ``` 这段代码中,定义了一个TestStruct结构体类型,其中包含四个字段:Name、Nick、Gender、uid和Age。其中,Name、Gender、Age字段都有使用``` `inject` ``` tag进行注释,表明它们可以被注入值。 在main函数中: 1. 首先创建了一个inject.Injector类型的实例inj 2. 然后通过inj.Map方法为Name字段注入一个string类型的值"张三",通过inj.MapTo方法为Gender字段注入一个指向SpecialString类型的指针,该指针指向一个string类型的值"男"。 3. 接着,又创建了一个inject.Injector类型的实例inj2,并通过inj2.Map方法为Age字段注入一个int类型的值26。 3. 最后,通过inj.SetParent方法将inj2设置为inj的父级,也就是让inj可以从inj2中获取注入值。 通过inj.Apply方法将注入的值应用到TestStruct类型的实例s中,然后分别打印出Name、Gender、Age字段的值。可以看到,Name、Gender、Age字段都被成功注入了对应的值。另外,注意到TestStruct类型中还定义了一个uid字段,在代码中,uid 和 Age 都有 ``` `inject` ``` 标签,**但是 uid 变量不会被注入,因为它是私有变量(首字母小写)**。只有公开(首字母大写)的变量才会被注入。因此,uid 变量不会被注入,而 Age 变量会被注入。所以,int类型的uid没有被同类型的age覆盖。
Nathan
March 27, 2023, 4:07 p.m.
转发文档
Collection documents
Last
Next
手机扫码
Copy link
手机扫一扫转发分享
Copy link
Markdown文件
PDF文件
Docx文件
share
link
type
password
Update password