技巧一:提前初始化
很多时候,我们的代码逻辑上,并不真的需要在每次运行的时候,才获取或设置某些值。而是可以在初始化阶段就把需要的值给确定出来。
比如在webapi组件中,我们要做一个header绑定的动作:在每次api请求时,可以将httpHeader的信息绑定到struct中。
省去我们做r.HttpContext.Header.GetValue("xxx"),非常省事。
优化前:
type HandleMiddleware struct { }
func (receiver HandleMiddleware) Invoke(httpContext *context.HttpContext) {
// doing...
receiver.bindHeader(httpContext, controllerVal) // 自动绑定头部
// doing...
}
// 绑定header
func (receiver HandleMiddleware) bindHeader(httpContext *context.HttpContext, controllerVal reflect.Value) {
controllerElem := controllerVal.Elem()
controllerType := controllerElem.Type()
for i := 0; i < controllerElem.NumField(); i++ {
// 找到需要绑定头部的标记
if controllerType.Field(i).Tag.Get("webapi") == "header" {
controllerHeaderVal := controllerElem.Field(i)
controllerHeaderType := controllerHeaderVal.Type()
// 遍历需要将header绑定的结构体
for headerIndex := 0; headerIndex < controllerHeaderVal.NumField(); headerIndex++ {
headerFieldVal := controllerHeaderVal.Field(headerIndex)
headerFieldType := headerFieldVal.Type()
headerName := controllerHeaderType.Field(headerIndex).Tag.Get("webapi")
if headerName == "" {
headerName = controllerHeaderType.Field(headerIndex).Name
}
headerVal := httpContext.Header.GetValue(headerName)
if headerVal == "" {
continue
}
headerValue := parse.ConvertValue(headerVal, headerFieldType)
headerFieldVal.Set(headerValue)
}
}
}
}
可以看出这是一个中间件,意味着每次Api请求时都会被执行。而bindHeader方法是用来查找在控制器中包含tag = webapi:"header"的字段,如:
type TaskController struct {
controller.BaseController
Client clientApp.DTO `webapi:"header"`
}
找到后,将对这个字段(Client)做自动绑定。
bindHeader方法会每次都遍历这个TaskController这个控制器下的字段,然后找到符合Tag规则的字段,然后再遍历这个结构的字段,然后做绑定操作。
仔细观察会发现,每次API执行都要for一遍这个控制器来找到Client标签。其实这种查找方式是完全可以在初始阶段就确定的,而没必要在每次执行api时都来查找一遍。
优化后:
// Register 自动注册控制器下的所有Action方法
func Register(area string, c IController) {
cVal := reflect.ValueOf(c)
cRealType := reflect.Indirect(cVal).Type()
// 查找是否需要自动绑定header
var autoBindHeaderName string
for i := 0; i < cRealType.NumField(); i++ {
// 找到需要绑定头部的标记
controllerFieldType := cRealType.Field(i)
if controllerFieldType.Tag.Get("webapi") == "header" {
autoBindHeaderName = controllerFieldType.Name
}
}
}
先在注册控制器前,找到这个字段,这个时间损耗,只会在运行前执行一次。然后在自动绑定header时,先判断这个字段存不存在即可:
// 绑定header
func (receiver HandleMiddleware) bindHeader(httpContext *context.HttpContext, controllerVal reflect.Value) {
// 没有设置绑定字段,则不需要绑定
if httpContext.Route.AutoBindHeaderName == "" {
return
}
controllerHeaderVal := controllerVal.Elem().FieldByName(httpContext.Route.AutoBindHeaderName)
controllerHeaderType := controllerHeaderVal.Type()
// 遍历需要将header绑定的结构体
for headerIndex := 0; headerIndex < controllerHeaderVal.NumField(); headerIndex++ {
headerFieldVal := controllerHeaderVal.Field(headerIndex)
headerFieldType := headerFieldVal.Type()
headerName := controllerHeaderType.Field(headerIndex).Tag.Get("webapi")
if headerName == "" {
headerName = controllerHeaderType.Field(headerIndex).Name
}
headerVal := httpContext.Header.GetValue(headerName)
if headerVal == "" {
continue
}
headerValue := parse.ConvertValue(headerVal, headerFieldType)
headerFieldVal.Set(headerValue)
}
}
如果存在,则直接通过FieldByName(httpContext.Route.AutoBindHeaderName)快速定位到这个field。
这样我们就少了一个for遍历。
最后
事实上,这里示例的优化,并不能真正提升多少性能。但做为编写框架提供给go使用者而言,哪怕是快0.01ms,也是很有必要的。使用者关心的是:
- 功能足够的强大
- 使用起来足够优雅
- 没有BUG
- 持续的维护
- 性能要足够的快
这里主要是提供一种思路:我们在执行某些逻辑时,确实需要在每次执行时才调用吗?
常见的新手错误:在for循环里,读写数据库、redis,事实上我们可以一次性读出来,在操作。
最后,希望能帮助到大家。