1 - iocli 工具
iocli 源码位于 github.com/alibaba/IOC-golang/iocli ,是一款命令行工具,提供了以下能力:
代码调试
开发者可以使用 iocli 作为调试客户端,调试基于 ioc-golang 框架开发的 go 应用程序。
结构相关代码生成
开发者可以为需要依赖注入的结构体增加注解,iocli 会识别这些注解,并产生符合要求的结构相关代码。包括结构描述信息、结构代理层、结构专属接口、结构 Get 方法等。
调试能力
ioc-golang 框架拥有基于 AOP 的 Go 运行时程序调试能力,帮助故障排查,性能分析,提高应用可观测能力。在 快速开始 部分展示了接口信息的查看、参数监听能力。在 基于 IOC-golang 的电商系统demo 中,可以展示基于 ioc-golang 的,业务无侵入的,方法粒度全链路追踪能力。
注解与代码生成
注解 是以特定字符串开头的注释,标注在期望注入的结构前。注解 只具备静态意义,即在代码生成阶段,被 iocl i工具扫描识别到,从而获取结构相关信息。注解 本身不具备程序运行时的意义。
iocli 可以识别以下注解 key,其中 = 后面的 value 为示例。
// +ioc:autowire=true
// +ioc:autowire:type=normal
// +ioc:autowire:paramLoader=paramLoader
// +ioc:autowire:paramType=Config
// +ioc:autowire:constructFunc=New
// +ioc:autowire:baseType=true
// +ioc:autowire:alias=MyAppAlias
// +ioc:tx:func=MyTransactionFunction
ioc:autowire (必填)
bool 类型,为 true 则在代码生成阶段被识别到。
ioc:autowire:type (必填)
string类型,表示依赖注入模型,目前支持以下五种,结构提供者可以选择五种中的一种或多种进行标注,从而生成相关的结构信息与 API,供结构使用者选用。
// +ioc:autowire:type=singleton // +ioc:autowire:type=normal type MyStruct struct{ }
singleton
单例模型,使用该注入模型获取到的结构体,全局只存在一个对象。
normal
多例模型,使用该注入模型,每一个标签注入字段、每一次 API 获取,都会产生一个新的对象。
config:
配置模型是基于多例模型的封装扩展,基于配置模型定义的结构体方便从 yaml 配置文件中注入信息。参考例子 example/autowire/autowire_config
grpc:
grpc 模型是基于单例模型的封装扩展,基于 grpc 模型可以方便地从 yaml 配置文件中读取参数,生成 grpc 客户端。参考例子 example/third_party/autowire/grpc
rpc:
rpc 模型会在代码生成阶段产生 rpc 服务端注册代码,以及 rpc 客户端调用存根。参考例子 example/autowire/autowire_rpc
ioc:autowire:paramLoader(非必填)
string类型,表示需要定制的“参数加载器“类型名
参数加载器由结构定义者可选定制。可参考:extension/state/redis
参数加载器需要实现Load方法:
// ParamLoader is interface to load param type ParamLoader interface { Load(sd *StructDescriptor, fi *FieldInfo) (interface{}, error) }
定义结构的开发者可以通过实现参数加载器,来定义自己的结构初始化参数。例如,一个 redis 客户端结构 ‘Impl’,需要从Config 参数来加载,如下所示 New 方法。
type Config struct { Address string Password string DB string } func (c *Config) New(impl *Impl) (*Impl, error) { dbInt, err := strconv.Atoi(c.DB) if err != nil { return impl, err } client := redis.NewClient(&redis.Options{ Addr: c.Address, Password: c.Password, DB: dbInt, }) _, err = client.Ping().Result() if err != nil { return impl, err } impl.client = client return impl, nil }
Config 包含的三个字段:Address Password DB,需要由使用者传入。
从哪里传入?这就是参数加载器所做的事情。
结构定义者可以定义如下加载器,从而将字段通过注入该结构的 tag 标签获取,如果tag信息标注了配置位置,则通过配置文件获取。
type paramLoader struct { } func (p *paramLoader) Load(sd *autowire.StructDescriptor, fi *autowire.FieldInfo) (interface{}, error) { splitedTagValue := strings.Split(fi.TagValue, ",") param := &Config{} if len(splitedTagValue) == 1 { return nil, fmt.Errorf("file info %s doesn't contain param infomration, create param from sd paramLoader failed", fi) } if err := config.LoadConfigByPrefix("extension.normal.redis."+splitedTagValue[1], param); err != nil { return nil, err } return param, nil }
例如
type App struct { NormalDB3Redis normalRedis.Redis `normal:"github.com/alibaba/ioc-golang/extension/state/redis.Redis,address=127.0.0.1:6379&db=3"` }
当然也可以从配置文件读入,tag中指定了key为 db1-redis
type App struct { NormalDB3Redis normalRedis.Redis `normal:"github.com/alibaba/ioc-golang/extension/state/redis.Redis,db1-redis"` }
ioc-go.yaml: autowire.normal.Redis.Impl.db1-redis.param 读入参数
autowire: normal: github.com/alibaba/ioc-golang/extension/state/redis.Redis: db1-redis: param: address: localhost:6379 db: 1
我们提供了预置的参数加载器
除非用户有强烈需求,我们更推荐用户直接使用我们预置的参数加载器:http://github.com/alibaba/ioc-golang/tree/master/autowire/param_loader。
我们会先后尝试:标签重定向到配置、标签读入参数、配置文件的默认位置读入参数。每个注册到框架的结构都有唯一的ID,因此也会在配置文件中拥有配置参数的位置,这一默认位置在这里定义:http://github.com/alibaba/ioc-golang/blob/master/autowire/param_loader/default_config.go#L21,我们更希望和用户约定好这一点。
当所有加载器都加载参数失败后,将会抛出错误。使用者应当查阅自己引入的结构加载器实现,并按照要求配置好参数。
ioc:autowire:paramType(非必填)
string类型,表示依赖参数的类型名,在上述例子,该类型名为 Config
ioc:autowire:constructFunc(非必填)
string类型,表示结构的构造方法名
在给出 ioc:autowire:paramType 参数类型名的情况下,会使用参数的函数作为构造函数,例如在上述例子中,该构造方法为 Config 对象的 New 方法。
如果没有给出 ioc:autowire:paramType 参数类型名,则会直接使用这一方法作为构造函数。
我们要求该构造方法的函数签名是固定的,即:
func (*$(结构名)) (*$(结构名), error)
ioc:autowire:baseType=true (非必填)
该类型是否为基础类型
go 基础类型不可直接通过&在构造时取地址,因此我们针对基础类型单独设计了该注解。在 配置扩展 中被使用较多。
ioc:autowire:alias=MyAppAlias (非必填)
该类型的别名,可在标签、API获取、配置中,通过该别名替代掉较长的类型全名来指定结构。
ioc:tx:func=MyTransactionFunction(非必填)
指定事务函数和回滚函数,参考事务例子 example/aop/transaction
iocli 操作命令
iocli init
生成初始化工程
iocli gen
递归遍历当前目录下的所有 go pkg ,根据注解生成结构体相关代码。
iocli list
查看应用所有接口和方法信息,默认端口 :1999
iocli watch [structID] [methodName]
监听一个方法的实时调用信息
iocli monitor [structID] [methodName]
开启调用监控,structID 和 methodName 可不指定,则监控所有接口方法。
iocli trace [structID] [methodName]
以当前方法为入口,开启调用链路追踪
具体操作参数可通过 -h 查看。
2 - 注入标签格式规范
IOC-golang 依赖 Go 原生支持的字段标签来作为依赖注入标签,标签的格式定义如下
拥有注入标签的结构,必须要在 ioc 框架的管理下才能正确注入,因此需要在结构体上方增加注解 +ioc:autowire ...
=
// +ioc:autowire=true
// +ioc:autowire:type=singleton
type DemoStruct struct{
${大写字母开头的字段名} ${字段类型} `${自动装载模型}:"${结构ID},${结构key}或${参数字段1}=${参数值1}&..."`
}
${字段类型}
其中${字段类型}
可有如下选择
接口
结构专属接口已经包含了结构ID信息,其标签内
${结构ID}
为空即可。开发者提供的接口
由于结构专属接口与结构体实现处在同一 pkg 下,如果期望通过依赖注入进行接口-实现分离,则需要由开发者给出接口,同时必须给出期望注入的
${结构ID}
。并保证程序在任何位置引入了实现类pkg,保证在程序启动时,期望注入的结构可被注册,从而被框架所管理,完成注入逻辑。
结构体指针。
结构体指针已经包含了结构ID信息,其标签内
${结构ID}
为空即可。
${自动装载模型}
内置:autowire, normal, grpc, rpc-client, config
如开发者提供了 扩展的自动装载模型,也可以在这里指定。
${结构ID}
结构ID字符串,参考 结构ID。
如注入字段的 ${字段类型}
为结构体指针或者 结构专属接口,${结构ID}
为空即可。
${结构key}或${参数字段1}=${参数值1}&…
指定构造参数的值
- 如果为多例模型,在此位置指定结构key,参考 默认配置格式与参数加载位置 中的例子
- 如果在此位置给出
${参数字段1}=${参数值1}&${参数字段2}=${参数值2}
则会以此处的参数值构造依赖对象并注入。参数值可以从配置文件中读取,也可以从环境变量中读取,一个复杂的例子如下:
参考 example/third_party/state/redis
// +ioc:autowire=true
// +ioc:autowire:type=singleton
// +ioc:autowire:paramType=Param
// +ioc:autowire:constructFunc=Init
// +ioc:autowire:alias=AppAlias
type App struct {
NormalRedis normalRedis.RedisIOCInterface `normal:""` // 配置文件默认位置加载参数
NormalDB1Redis normalRedis.RedisIOCInterface `normal:",db1-redis"` // 配置文件当前结构的索引 db1-redis 位置加载参数
NormalDB2Redis normalRedis.RedisIOCInterface `normal:",db2-redis"` // 配置文件当前结构的索引 db2-redis 位置加载参数
NormalDB3Redis normalRedis.RedisIOCInterface `normal:",address=127.0.0.1:6379&db=3"` // 直接给出静态参数
NormalDB4Redis normalRedis.RedisIOCInterface `normal:",address=${REDIS_ADDRESS_EXPAND}&db=5"` // 从环境变量中读取参数
NormalDB5Redis normalRedis.RedisIOCInterface `normal:",address=${autowire.normal.<github.com/alibaba/ioc-golang/extension/state/redis.Redis>.nested.address}&db=15"` // 从配置文件的给定位置中读取参数
privateClient *redis.Client
}
其配置文件如下:
autowire:
normal:
github.com/alibaba/ioc-golang/extension/state/redis.Redis:
db1-redis:
param: # NormalDB1Redis 读取参数位置
address: localhost:6379
db: 1
db2-redis: # NormalDB2Redis 读取参数位置
param:
address: localhost:6379
db: 2
param: # NormalRedis 读取参数位置
address: localhost:6379
db: 0
expand:
address: ${REDIS_ADDRESS_EXPAND}
db: 15
nested: # NormalDB5Redis 读取参数位置
address: ${autowire.normal.<github.com/alibaba/ioc-golang/extension/state/redis.Redis>.expand.address}
db: ${autowire.normal.<github.com/alibaba/ioc-golang/extension/state/redis.Redis>.expand.db}
3 - 配置文件格式规范
默认配置格式与参数加载位置
配置文件 ioc_golang.yaml 的默认参数默认配置格式为:
autowire:
${自动装载模型名}:
${SDID}:
${多例模式下的索引key1}:
param:
field1: value1 # 多例模式创建对象的构造参数
${多例模式下的索引key2}:
param:
field1: value2 # 多例模式创建对象的构造参数
param:
field1: value3 # 单例模式或多例模式未指定索引key 创建对象的构造参数
例如,一个基于多例模型的结构 Impl,实现了 Redis 接口,其多个实例参数可以配置为:
autowire:
normal:
github.com/alibaba/ioc-golang/extension/normal/redis.Impl:
db1-redis:
param:
address: localhost:6379
db: 1
db2-redis:
param:
address: localhost:6379
db: 2
param:
address: localhost:6379
db: 0
拥有 注入标签 normal:",db2-redis"
的字段,会从配置中读入参数,位置为标签指向的对象名db2-redis,db 为2。
NormalDB2Redis normalRedis.RedisIOCInterface `normal:",db2-redis"`
address: localhost:6379
db: 2
拥有标签 normal:""
的字段,会从配置中读入参数,db 为 0。
NormalDBRedis normalRedis.RedisIOCInterface `normal:""`
address: localhost:6379
db: 0
定制化参数加载器
参数加载器(param_loader) 可以在自动装载模型或 SD (结构描述符) 中定制化,例如一个基于 grpc 自动装载模型注入的客户端,我们已经在 grpc 自动装载模型中 定制化 了参数加载位置,为 autowire.grpc.${tag-value}
,因此对于如下字段:
HelloServiceClient api.HelloServiceClient `grpc:"hello-service"`
会从如下位置读取参数:address 设置为 “localhost:8080”
autowire:
grpc:
hello-service:
address: localhost:8080
同理,也可以由服务提供者在 SD 中传入参数加载器,进行定制化参数加载源。
配置文件中的特殊引用
声明环境变量
在配置文件中,可以使用 ${ENV_KEY}
的格式,引入环境变量。当环境变量存在时,将会被替换为环境变量的值。
config:
app:
config-value: myValue
config-value-from-env: ${MY_CONFIG_ENV_KEY} # 如环境变量存在,读取环境变量替换该字段
声明配置引用(nested)
config:
app:
config-value: myValue
config-value-from-env: ${MY_CONFIG_ENV_KEY} # 如环境变量存在,读取环境变量替换该字段
nested-config-value: ${config.app.config-value} # 引用 config.app.config-value 的值,为 myValue
nested-config-value-from-env: ${config.app.config-value-from-env} # 引用 config.app.config-value-from-env 的值,为环境变量替换后的值
更多配置文件的加载与使用例子,参考 example/config_file
4 - 常见问题排查
默认配置层级
配置文件 ioc_golang.yaml 的默认参数默认配置层级依此为:
autowire:
$(自动装载模型名):
$(SDID):
$(多例模式下的对象名):
param:
param:
例如,一个基于多例模型的结构 Impl,实现了 Redis 接口,其多个实例参数可以配置为:
autowire:
normal:
github.com/alibaba/ioc-golang/extension/normal/redis.Impl:
db1-redis:
param:
address: localhost:6379
db: 1
db2-redis:
param:
address: localhost:6379
db: 2
param:
address: localhost:6379
db: 0
拥有标签 normal:"Impl,db2-redis"
的字段,会从配置中读入参数,位置为标签指向的对象名db2-redis
NormalDB2Redis normalRedis.Redis `normal:"Impl,db2-redis"`
address: localhost:6379
db: 2
拥有标签 normal:"Impl"
的字段,会从配置中读入参数。
NormalDBRedis normalRedis.Redis `normal:"Impl"`
address: localhost:6379
db: 0
定制化参数加载模型
参数加载模型可以由自动装载模型和 SD (结构描述符)定制化,例如一个基于 grpc 自动装载模型注入的客户端
HelloServiceClient api.HelloServiceClient `grpc:"hello-service"`
从如下位置读取参数:
autowire:
grpc:
hello-service:
address: localhost:8080