go中装饰器初探

1 篇文章 0 订阅
1 篇文章 0 订阅

虽然对于动态类型的语言,比如python或者js里,装饰器模式已经很常用

go中的函数是一等公民,可以直接作为参数传入传出,装饰器是可行的
但go的问题是静态类型,func addAllInt(a …int) 和 func addAllStr(a …string)是不同的类型。本身作为装饰器函数,只能传入出入参数一致的同类型的函数,这样限制了装饰器的应用场景。为了兼容通用的类型,需要使用反射来做一些事,但真正实现起来,却踩了不少坑,蛮有难度

需求:
go的协程一旦发生panic,就只能在自己的协程里捕获,一旦某个协程忘记了捕获,就会导致整个服务挂掉。因而就想实现一个通用的装饰器来捕获,给所有被装饰的函数添加,就无需在函数体内处理recover了。
效果类似于如下的python代码

import traceback

def trier(func):
    def inner(*args, **kwargs):
        try:
            return func(*args, **kwargs)
        except:
            print(traceback.format_exc())
    return inner

对于go来讲,困难点在于参数的静态类型,不支持*args这样的通用参数
因为这样的函数不会修改被装饰参数的出入参,考虑传入函数指针,反射利用指针替换具体的函数代码来实现功能

核心要点:
使用reflect.Makefunc来创建函数,注意使用[]reflect.Value来保存出入参数列表
使用reflect.Indirect(reflect.ValueOf(decoPtr)).Interface()拿到被装饰函数指针的接口对象oldFuncInterface,再通过out = reflect.ValueOf(oldFuncInterface).Call(in)来调用原函数,拿到响应结果
在调用原函数前,使用defer recover捕获异常,使用debug.Stack()打出堆栈

另外,因为使用了反射,堆栈会有反射库调用相关的很多信息,而这些信息是使用者不关心的,因而我以最内层的一次reflect.Makefunc调用为分界,处理掉了不必要的堆栈

下面是实现代码。可以说由于是静态类型语言,用这种通用装饰函数的代价不轻,所以不建议对非必要的函数使用通用类型的装饰器

func Catcher(decoPtr interface{}) {
	var decoratedFunc, targetFunc reflect.Value

	decoratedFunc = reflect.ValueOf(decoPtr).Elem()
	targetFuncAll := reflect.Indirect(reflect.ValueOf(decoPtr)).Interface()
	targetFuncType := reflect.TypeOf(targetFuncAll)
	targetFunc = reflect.ValueOf(targetFuncAll) // valueOf and interface make a copy?

	v := reflect.MakeFunc(targetFuncType,
		func(in []reflect.Value) (out []reflect.Value) {
			defer func() {
				if err := recover(); err != nil {
					fmt.Println(string(time.Now().AppendFormat(nil, "2006-01-02 15:04:05")),
						"PANIC caught panic:", err)
					trace := string(debug.Stack())
					trace = "reflect.makeFunc" + strings.Join(strings.Split(trace, "reflect.makeFunc")[1:], "reflect.makeFunc")
					fmt.Println("StackTrace:", trace)
					out = make([]reflect.Value, targetFuncType.NumOut()) // todo make default value
					for i, _ := range out {
						out[i] = reflect.Zero(targetFuncType.Out(i))
					}
				}
			}()

			out = targetFunc.Call(in)
			return
		})
	decoratedFunc.Set(v)
}

使用方式如下

func schedulerCenter(ctx context.Context) {
	for {
		b := <-taskChannel
		job := b.asyncBuildHandler
		Catcher(&job)
		go job(ctx)
	}
}

拿到函数变量(如果是静态定义的函数,需要先赋值给一个变量),然后用Catcher传入函数变量的指针,然后再去异步调用即可。同样适用于结构体和接口的方法(如上例子)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值