远离复杂代码!Go语言里用AOP切面编程让系统维护更简单

文摘   2025-01-25 23:22   四川  

Go 实现 AOP 切面操作,说到这个,有人可能会想,AOP 不是 Java 的玩意儿吗,怎么也拿到 Go 里来了?其实,编程思想嘛,并没有语言的边界。AOP(Aspect Oriented Programming)其实就是切面编程,简单理解,就是在程序正常运行流程之外,你找到几个点,切进去,插一脚。这么做,通常能干点日志记录,性能监控什么的。

AOP 在 Go 里怎么玩?

Go 并不是天然像 Java 那样有完善的 AOP 机制。在 Go 里,最直接的办法就是利用函数回调,用一点小小的编程技巧,创造自己的切面逻辑。

举个例子,我们想在一个函数前后打印日志,比如说有这样的加法函数:

go


func add(a, b int) int {
    return a + b
}

直接调用,只得到个结果,干了什么不知道。现在想让每次运算前后都打一行日志,怎么做?直接改函数体当然可以,但这就违反了“分离关注点”的原则。理想的 AOP,就是让“加法”和“日志”井水不犯河水。

第一种方案:直接回调

在 Go 里,我们把原函数封装到一个回调里,再加个外围函数控制切面逻辑:

go


func logAdd(func(int,int)int)func(int,int)int{
    returnfunc(a, b int)int{
        deferfunc(){ fmt.Println("after add")}()
        fmt.Println("before add")
        returnf(a, b)
    }
}

这个logAdd相当于我们切面逻辑的载体。每次调加法,先打一行“before add”,再执行函数f,最后打“after add”。使用时变成:

go


func main() {
    addWithLog := logAdd(add)
    result := addWithLog(2, 3)
    fmt.Println("result is", result)
}

这样做就是很基础的 AOP,但是局限性也明显:只能用作特定函数签名(参数和返回值类型固定)。处理其他场景不灵活。再来看更普适的做法。

第二种方案:利用反射

写框架或是通用工具时,不同函数有不同参数和返回类型,思路是利用 Go 的反射机制进行更宽泛的包装:

go


func Aspect(fn interface{}, aspectFns ...interface{})[]reflect.Value {
    fnType := reflect.TypeOf(fn)
    fnValue := reflect.ValueOf(fn)

    aspects :=make([]reflect.Value,len(aspectFns))
    for i, aspectFn :=range aspectFns {
        aspects[i]= reflect.ValueOf(aspectFn)
    }

    returnfunc(in []reflect.Value)[]reflect.Value {
        for_, aspect :=range aspects {
            aspect.Call(in)
        }
        return fnValue.Call(in)
    }
}

这个 Aspect 函数通过反射把原始函数和切面函数包装在了一起。不管你函数参数、返回值怎样,我把“进”和“出”的位置让切面逻辑插进去执行。利用就像这样:

go


func before(in []reflect.Value){
    fmt.Println("aspect before")
}

funcafter(in []reflect.Value){
    fmt.Println("aspect after")
}

funcmain(){
    aspectAdd :=Aspect(add, before, after)
    result :=aspectAdd([]reflect.Value{reflect.ValueOf(2), reflect.ValueOf(3)})
    fmt.Println("result is", result[0].Int())
}

你可以把自己要插入的逻辑写成 before  after,包装不同函数都很方便。缺点嘛,反射引入了额外性能开销,函数调用链变得更加晦涩。这种方法相比第一种,对于多种类型的函数都适用,代价是牺牲了一点运行效率。

实际使用场景

以上方法好不好,最终还得结合实际场景看。框架里插日志、性能监控,类似手段很常见。做中间件的朋友可能就要琢磨,选哪种看场景需求:功能稳了再用上反射倒也不虚,普普通通的回调算是是强在简单直接。有些时候,真的不需要硬套切面,就像早市上挑鱼,怎得新鲜就怎么来。不论黑猫白猫,抓到耗子的都是好猫。

写这些例子就是简单开个头。在你今后程序里,如何更好地插入代码“侧面行动”,要多琢磨个性需求。不怕一步一步实验,错了也不要紧,总是得能琢磨出的。你会发现 AOP 在 Go 里就有它的适应方式,挑合适的用即可。


粒粒快点跑
我是粒姐,11年老猎头,职业咨询顾问,曾创立两家猎头公司。 分享求职技巧和职场经验,职业愿景是帮助1000人找到心仪工作。 猎聘签约求职教练,1V1咨询,求职辅导,职业规划咨询,职场辅导。视频号:#粒粒快点跑
 最新文章