社区微信群开通啦,扫一扫抢先加入社区官方微信群
社区微信群
从java转go以来一直在寻找动态代理的实现方案,几经波折,终于找到了位牛人写的框架monkey,简单喵了下其源码,貌似是通过替换底层函数字节码来达到动态替换效果,后续再仔细拜读。这里我利用monkey框架实现了一个简单的AOP框架
monkey项目地址:https://github.com/bouk/monkey
aop码云地址:https://gitee.com/sqxwww/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.每次执行都要遍历所有代理列表,可以考虑在注册代理或注册方法时将方法与代理关联上,去掉不必要的遍历
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!