go利用monkey框架的动态代理实现AOP - Go语言中文社区

go利用monkey框架的动态代理实现AOP


前言

从java转go以来一直在寻找动态代理的实现方案,几经波折,终于找到了位牛人写的框架monkey,简单喵了下其源码,貌似是通过替换底层函数字节码来达到动态替换效果,后续再仔细拜读。这里我利用monkey框架实现了一个简单的AOP框架

monkey项目地址:https://github.com/bouk/monkey

aop码云地址:https://gitee.com/sqxwww/aop

AOP框架

package aop

import (
	"bou.ke/monkey"
	"fmt"
	"reflect"
	"regexp"
)

//连接点
type JoinPoint struct {
	Receiver 			interface{}
	Method   			reflect.Method
	Params   			[]reflect.Value
	Result   			[]reflect.Value
}

func NewJoinPoint(receiver interface{}, params []reflect.Value, method reflect.Method) *JoinPoint {
	point := &JoinPoint{
		Receiver: receiver,
		Params: params,
		Method: method,
	}

	fn := method.Func
	fnType := fn.Type()
	nout := fnType.NumOut()
	point.Result = make([]reflect.Value, nout)
	for i := 0; i < nout; i++ {
		//默认返回空值
		point.Result[i] = reflect.Zero(fnType.Out(i))
	}

	return point
}

//切面接口
type AspectInterface interface {
	Before(point *JoinPoint) bool
	After(point *JoinPoint)
	Finally(point *JoinPoint)
	GetAspectExpress() string
}

//切面列表
var aspectList = make([]AspectInterface, 0)

//注册切点
func RegisterPoint(pointType reflect.Type) {
	pkgPth := pointType.PkgPath()
	receiverName := pointType.Name()
	if pointType.Kind() == reflect.Ptr {
		pkgPth = pointType.Elem().PkgPath()
		receiverName = pointType.Elem().Name()
	}
	for i := 0; i < pointType.NumMethod(); i++ {
		method := pointType.Method(i)
		//方法位置字符串"包名.接收者.方法名",用于匹配代理
		methodLocation := fmt.Sprintf("%s.%s.%s", pkgPth, receiverName, method.Name)
		var guard *monkey.PatchGuard
		var proxy = func(in []reflect.Value) []reflect.Value {
			guard.Unpatch()
			defer guard.Restore()
			receiver := in[0]
			point := NewJoinPoint(receiver, in[1:], method)
			defer finallyProcessed(point, methodLocation)
			if !beforeProcessed(point, methodLocation) {
				return point.Result
			}
			point.Result = receiver.MethodByName(method.Name).Call(in[1:])
			afterProcessed(point, methodLocation)
			return point.Result
		}
		//动态创建代理函数
		proxyFn := reflect.MakeFunc(method.Func.Type(), proxy)
		//利用monkey框架替换代理函数
		guard = monkey.PatchInstanceMethod(pointType, method.Name, proxyFn.Interface())
	}
}

//注册切面
func RegisterAspect(aspect AspectInterface) {
	aspectList = append(aspectList, aspect)
}

//前置处理
func beforeProcessed(point *JoinPoint, methodLocation string) bool {
	for _, aspect := range aspectList {
		if !isAspectMatch(aspect.GetAspectExpress(), methodLocation) {
			continue
		}
		if !aspect.Before(point) {
			return false
		}
	}
	return true
}

//后置处理
func afterProcessed(point *JoinPoint, methodLocation string) {
	for i := len(aspectList) - 1; i >= 0; i-- {
		aspect := aspectList[i]
		if !isAspectMatch(aspect.GetAspectExpress(), methodLocation) {
			continue
		}
		aspect.After(point)
	}
}

//最终处理
func finallyProcessed(point *JoinPoint, methodLocation string) {
	for i := len(aspectList) - 1; i >= 0; i-- {
		aspect := aspectList[i]
		if !isAspectMatch(aspect.GetAspectExpress(), methodLocation) {
			continue
		}
		aspect.Finally(point)
	}
}

func isAspectMatch(aspectExpress, methodLocation string) bool {
	//aspectExpress采用正则表达式
	pattern, err := regexp.Compile(aspectExpress)
	if err != nil {
		return false
	}
	return pattern.MatchString(methodLocation)
}

测试 

package main

import (
	"fmt"
	"reflect"
	"aop/aop"
)

func init()  {
	aop.RegisterPoint(reflect.TypeOf((*HelloAop)(nil)))
	aop.RegisterAspect(&Aspect{})
}

type Aspect struct {}

func (a *Aspect) Before(point *aop.JoinPoint) bool {
	fmt.Println("before")
	return true
}

func (a *Aspect) After(point *aop.JoinPoint) {
	fmt.Println("after")
}

func (a *Aspect) Finally(point *aop.JoinPoint) {
	fmt.Println("finally")
}

func (a *Aspect) GetAspectExpress() string {
	return ".*\.HelloAop"
}

type HelloAop struct {

}

func (h *HelloAop) HelloAop() {
	fmt.Println("helloAop")
}

func main()  {
	h := &HelloAop{}
	h.HelloAop()
}

测试结果

注意

monkey官网原话:Monkey sometimes fails to patch a function if inlining is enabled. Try running your tests with inlining disabled, for example: go test -gcflags=-l. The same command line argument can also be used for build.

即monkey有些环境无法替换内联函数,在编译时禁掉内联函数即可。go build/run/install -gcflags=-l main.go

缺陷

1.替换为全局替换,若需只调用原生方法则需先解除替换,比较麻烦

2.每次执行都要遍历所有代理列表,可以考虑在注册代理或注册方法时将方法与代理关联上,去掉不必要的遍历

版权声明:本文来源CSDN,感谢博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/sqxww123/article/details/101777075
站方申明:本站部分内容来自社区用户分享,若涉及侵权,请联系站方删除。
  • 发表于 2021-05-16 03:07:00
  • 阅读 ( 1340 )
  • 分类:Go

0 条评论

请先 登录 后评论

官方社群

GO教程

猜你喜欢