首页 Golang教程AOP 切面操作 , Go语言是如何实现的

AOP 切面操作 , Go语言是如何实现的

运维派隶属马哥教育旗下专业运维社区,是国内成立最早的IT运维技术社区,欢迎关注公众号:yunweipai
领取学习更多免费Linux云计算、Python、Docker、K8s教程关注公众号:马哥linux运维

本文介绍了在 go 语言中实现 AOP 操作的实践。

  • golang如何进行AOP操作
  • 怎样的AOP风格最好用
  • gin的中间件是如何实现的

前言

我们将一个事件处理标记为handler, 那么AOP指代的,就是围绕这个handler的【执行前】【执行后】的切面操作,他可以形象地描述为:

图片

这种执行流程,容易开发成以下样式:

beforeHandler()
handler()
AfterHandler()

一旦做成这样子,那么在同一个handler接入不同的切面操作时,便会需要无限侵入代码,变成:

countExecTime()
startRateLimit()
handler
endRateLimit()
endExecTime()

这样子的操作,不只是丑陋,而且随着aop操作增多,维护难度会剧增。

所以到底要如何设计一套可以复用的机制,来优雅使用AOP操作。熟悉golang Web开发的人都知道,github.com/gin-gonic/gin里,可以通过c.Next 和 c.Abort()的机制,来优雅设计aop切面(中间件)。

那么,我们能否依照同类api,来达到类似的效果呢?

答案是,可以的。

合理的AOP设计,应该是这样的

图片
它的执行流为:


限流start –> 熔断start —> handler —>熔断end –> 限流end

设计

设计前,考虑到大部分群体,都对http gin的中间件流,十分熟悉。所以我们也基于同样的方式,来实现aop操作。实现以前呢,我们同步一下设计的要求:

  • 需要支持,对任意方法执行切面操作。
  • 需要和gin共享相似度极高的api。
  • 支持所有框架场景。
  • 实现包体简单。
  • 支持对【全局】【单个路由】两个维度支持中间件

中间件样例:

func AlertTimeout(c *irr.Context) {
   //  c.Abort() 阻断
   // c.Next() 放行
}

使用时

wrapF := irr.WrapFunc(func(){
     // 原本的handler逻辑
})
// 流程超时报警
wrapF.Use(AlertTimeout)

// 流程熔断报警
wrapF.Use(AlertFuse)

wrapF.Handle()

实现

目录层级

irr
 | - context.go
 | - core.go

context.go

package irr

import (
 "math"
)

const ABORT = math.MaxInt32 - 10000

type Context struct {
 offset   int
 handlers []func(*Context)
}

func newContext() *Context {
 return &Context{
  offset:   -1,
  handlers: make([]func(*Context), 0, 10),
 }
}
func (ctx *Context) Next() {
 ctx.offset ++
 s := len(ctx.handlers)
 for ; ctx.offset < s; ctx.offset++ {
  if !ctx.isAbort() {
   ctx.handlers[ctx.offset](ctx)
  } else {
   return
  }
 }
}
func (ctx *Context) Reset() {
 //ctx.PerRequestContext = &sync.Map{}
 ctx.offset = -1
 ctx.handlers = ctx.handlers[:0]
}

// stop middleware chain
func (ctx *Context) Abort() {
 ctx.offset = math.MaxInt32 - 10000
}

func (ctx *Context) isAbort() bool {
 if ctx.offset >= ABORT {
  return true
 }
 return false
}

func (ctx *Context) addHandler(f func(ctx *Context)) {
 ctx.handlers = append(ctx.handlers, f)
}

core.go

package irr

type WrapF struct {
 f func()

 ctx *Context
}

func WrapFunc(f func()) *WrapF {
 return &WrapF{
  f:   f,
  ctx: newContext(),
 }
}

func (wf *WrapF) Use(f func(c *Context)) {
 wf.ctx.addHandler(f)
}

func (wf *WrapF) Handle() {
 wf.ctx.handlers = append(wf.ctx.handlers, func(c *Context) {
  wf.f()
 })

 if len(wf.ctx.handlers) > 0 {
  wf.ctx.Next()
 }
 wf.ctx.Reset()
}

使用

1. 对不支持中间件的ws/tcp框架接入中间件

假设你的tcp/websocket框架长这样

for {
    conn := l.Accept()
    go func(conn net.Conn){
        for {
               packet, _ = readAPackFrom(conn)
               go handle(packet, conn)
        }
   }(conn)
}

接入aop后, 全局中间件:

for {
    conn := l.Accept()
    go func(conn net.Conn){
        for {
               packet, _ = readAPackFrom(conn)
               go func(){
                  wrapF := irr.WrapFunc(func(){
                      handle(packet, conn)
                  })
                  wrapF.Use(AlertTimeout)
                  wrapF.Use(Sentinel)
                  wrapF.Handle()
               }()
        }
   }(conn)
}

路由级:

func handlePacket(packet []byte, conn) {
     wrapF := irr.WrapFunc(func(){
         // 路由逻辑
         handle(packet, conn)
     })
     // 熔断
     wrapF.Use(Fuse("app:get_user_info", 20,10))
     wrapF.Use(LimitRate(50,1))
     wrapF.Handle()
}

2.接入gin

  • 复制一份func (c *irr.Context) ,将irr改成gin,即可直接投入gin使用。

结语

  • 通过wrap任意函数 func(){} 达到了对任意方法切面封装。
  • 使用方法和gin完全一致。
  • 对不支持中间件的框架,可以快速接入。
  • 实现包体简单,累计不到40行。
  • 支持对【全局】【单个路由】两个维度支持中间件

本次实现的包体很轻便,所以直接将实现源码贴出来了。c.Next c.Abort Use(middleware)的设计原理,和以下仓库同源:

github.com/gin-gonic/gin
github.com/fwhezfwhez/tcpx
github.com/fwhezfwhez/wsx

最后,如果不想以包体嵌入的形式,来使用irr,可以直接使用仓库:

github.com/fwhezfwhez/irr

转自:Go生态圈

参考链接:https://blog.csdn.net/fwhezfwhez/article/details/115471158

本文链接:https://www.yunweipai.com/41580.html

网友评论comments

发表回复

您的电子邮箱地址不会被公开。

暂无评论

Copyright © 2012-2022 YUNWEIPAI.COM - 运维派 京ICP备16064699号-6
扫二维码
扫二维码
返回顶部