diff --git a/aop.go b/aop.go index 31437f0..fe28bf6 100644 --- a/aop.go +++ b/aop.go @@ -78,17 +78,10 @@ func (p *AOP) funcWrapper(bean *Bean, methodName string, methodType reflect.Type advicesGroup = append(advicesGroup, advices) } - callAdvicesFunc := func(order AdviceOrdering, retValues ...reflect.Value) (e error) { - - var result Result - if order == AfterReturning { - for _, v := range retValues { - result = append(result, v.Interface()) - } - } + callAdvicesFunc := func(order AdviceOrdering, resultValues ...reflect.Value) (e error) { for _, advices := range advicesGroup { - if e = invokeAdvices(&joinPoint, advices[order], methodName, result); e != nil { + if e = invokeAdvices(&joinPoint, advices[order], methodName, resultValues); e != nil { if errOutIndex >= 0 { ret[errOutIndex] = reflect.ValueOf(&e).Elem() } @@ -104,9 +97,41 @@ func (p *AOP) funcWrapper(bean *Bean, methodName string, methodType reflect.Type return } - //@Normal func + //@Real func + var retValues []reflect.Value + funcInSturctName := getFuncNameByStructFuncName(methodName) - retValues := beanValue.MethodByName(funcInSturctName).Call(inputs) + + realFunc := func(args ...interface{}) Result { + values := []reflect.Value{} + for _, arg := range args { + values = append(values, reflect.ValueOf(arg)) + } + + return beanValue.MethodByName(funcInSturctName).Call(values) + } + + //@Around + var aroundAdvice *Advice + for _, advices := range advicesGroup { + if aroundAdvices, exist := advices[Around]; exist && len(aroundAdvices) > 0 { + aroundAdvice = aroundAdvices[0] + break + } + } + + if aroundAdvice != nil { + pjp := ProceedingJoinPoint{JoinPointer: &joinPoint, method: realFunc} + + if err = invokeAdvices(&pjp, []*Advice{aroundAdvice}, methodName, nil); err != nil { + if errOutIndex >= 0 { + ret[errOutIndex] = reflect.ValueOf(&err).Elem() + } + return + } + } else { + retValues = realFunc(inputs) + } if IsTracing() { var metadata MethodMetadata @@ -157,14 +182,14 @@ func (p *AOP) GetProxy(beanID string) (proxy *Proxy, err error) { mType := methodV.Type() var metadata MethodMetadata - if metadata, err = getMethodMetadata(methodT.Func.Interface()); err != nil { + if metadata, err = getMethodMetadata(methodT); err != nil { return } - newFunc := p.funcWrapper(bean, metadata.MethodName, mType) + newFunc := p.funcWrapper(bean, metadata.Method.Name, mType) funcV := reflect.MakeFunc(mType, newFunc) - metadata.method = funcV.Interface() // rewrite to new proxy func + metadata.Method.Func = funcV // rewrite to new proxy func tmpProxy.registryFunc(metadata) } diff --git a/bean.go b/bean.go index 2f4d8e8..bdc4291 100644 --- a/bean.go +++ b/bean.go @@ -61,7 +61,7 @@ func (p *Bean) methodMetadata(methodName string) (metadata MethodMetadata, err e return } - metadata, err = getMethodMetadata(method.Func.Interface()) + metadata, err = getMethodMetadata(method) return } diff --git a/example/main.go b/example/main.go index f1bb095..812b37d 100644 --- a/example/main.go +++ b/example/main.go @@ -1,11 +1,15 @@ -package main +AOP +=== +Aspect Oriented Programming For Golang -import ( - "fmt" +> current version is in alpha, welcome to submit your ideas (api is not stable current version) - "github.com/gogap/aop" -) +### Basic Usage + +#### define struct + +```go type Auth struct { } @@ -31,6 +35,157 @@ func (p *Auth) After(username, password string) { fmt.Printf("After Login: %s %s\n", username, password) } +// use join point to around the real func of login +func (p *Auth) Around(pjp aop.ProceedingJoinPointer) { + fmt.Println("@Begin Around") + + ret := pjp.Proceed("fakeName", "fakePassword") + ret.MapTo(func(loginResult bool) { + fmt.Println("@Proceed Result is", loginResult) + }) + + fmt.Println("@End Around") +} +``` + +In this case, we want call `Before()` func before `Login()`, and `After()` func after `Login()` + +In general, we will do it like as following + +```go +func (p *Auth) Login(userName string, password string) bool { + p.Before(userName, password) + defer p.After(userName, password) + + if userName == "zeal" && password == "gogap" { + return true + } + return false +} +``` + +So, if we have more funcs to call before and after, it will pollution the real logic func `Login()`, we want a proxy help us to invoke `Before()` and `After()` automatic. + +That was what AOP does. + +#### Step 1: Define Beans factory + +```go +beanFactory := aop.NewClassicBeanFactory() +beanFactory.RegisterBean("auth", new(Auth)) +``` + +#### Step 2: Define Aspect + +```go +aspect := aop.NewAspect("aspect_1", "auth") +aspect.SetBeanFactory(beanFactory) +``` + +#### Step 3: Define Pointcut + +```go +pointcut := aop.NewPointcut("pointcut_1").Execution(`Login()`) +aspect.AddPointcut(pointcut) +``` + +#### Step 4: Add Advice + +```go +aspect.AddAdvice(&aop.Advice{Ordering: aop.Before, Method: "Before", PointcutRefID: "pointcut_1"}) +aspect.AddAdvice(&aop.Advice{Ordering: aop.After, Method: "After", PointcutRefID: "pointcut_1"}) +aspect.AddAdvice(&aop.Advice{Ordering: aop.Around, Method: "Around", PointcutRefID: "pointcut_1"}) +``` + +#### Step 5: Create AOP + +```go +gogapAop := aop.NewAOP() +gogapAop.SetBeanFactory(beanFactory) +gogapAop.AddAspect(aspect) +``` + +#### Setp 6: Get Proxy + +```go +proxy, err := gogapAop.GetProxy("auth") +``` + +#### Last Step: Enjoy + +```go +login := proxy.Method(new(Auth).Login).(func(string, string) bool)("zeal", "gogap") + +fmt.Println("login result:", login) +``` +> output + +```bash +$> go run main.go +Before Login: zeal +After Login: zeal gogap +Login result: true +``` + +### Advance + +#### Pointcut expression + +> every condition expression is regex expression + +```go +pointcut := aop.NewPointcut("pointcut_1") + +// will trigger the advice while call login +pointcut.Execution(`Login()`) + +// will trigger the advice will call any func +pointcut.Execution(`.*?`) + +// will not trigger the advice will call any func +pointcut.NotExecution(`Login()`) +``` + +##### other conditions: +- WithIn +- NotWithIn +- Bean +- NotBean + +```go +// will trigger the advie while we call Login +// and in bean named auth +pointcut.Execution(`Login()`).Bean(`auth`) + +// will trigger the advie while we call Login +// and in bean named auth and sysAuth +pointcut.Execution(`Login()`).Bean(`auth`).Bean(`sysAuth`) + + +// will trigger the advie while we call Login +// and in bean named auth not sysAuth +pointcut.Execution(`Login()`).Bean(`auth`).NotBean(`sysAuth`) + +// will trigger the advie while we call Login +// and the call stacktrace should contain example/aop/main +pointcut.Execution(`Login()`).WithIn(`example/aop/main`) + +``` + +#### Do not want to assertion func type + +```go +proxy.Invoke(new(Auth).Login, "zeal", "errorpassword").End( + func(result bool) { + login = result + }) +``` + +#### Weaving other beans into aspect + +##### define a bean + +```go type Foo struct { } @@ -41,66 +196,85 @@ func (p *Foo) Bar(result aop.Result) { fmt.Println("Bar Bar Bar .... Result is:", v) }) } +``` -func main() { - beanFactory := aop.NewClassicBeanFactory() - beanFactory.RegisterBean("auth", new(Auth)) - beanFactory.RegisterBean("foo", new(Foo)) - - aspect := aop.NewAspect("aspect_1", "auth") - aspect.SetBeanFactory(beanFactory) +##### register bean - aspectFoo := aop.NewAspect("aspect_2", "foo") - aspectFoo.SetBeanFactory(beanFactory) +```go +beanFactory.RegisterBean("foo", new(Foo)) +``` - pointcut := aop.NewPointcut("pointcut_1").Execution(`Login()`) - pointcut.Execution(`Login()`) +##### create aspect +```go +aspectFoo := aop.NewAspect("aspect_2", "foo") +aspectFoo.SetBeanFactory(beanFactory) +``` - aspect.AddPointcut(pointcut) - aspectFoo.AddPointcut(pointcut) - aspect.AddAdvice(&aop.Advice{Ordering: aop.Before, Method: "Before", PointcutRefID: "pointcut_1"}) - aspect.AddAdvice(&aop.Advice{Ordering: aop.After, Method: "After", PointcutRefID: "pointcut_1"}) - aspectFoo.AddAdvice(&aop.Advice{Ordering: aop.AfterReturning, Method: "Bar", PointcutRefID: "pointcut_1"}) +##### add advice - gogapAop := aop.NewAOP() - gogapAop.SetBeanFactory(beanFactory) - gogapAop.AddAspect(aspect) - gogapAop.AddAspect(aspectFoo) +```go +aspectFoo.AddAdvice(&aop.Advice{Ordering: aop.AfterReturning, Method: "Bar", PointcutRefID: "pointcut_1"}) +``` - var err error - var proxy *aop.Proxy +##### add aspect into aop - // Get proxy - if proxy, err = gogapAop.GetProxy("auth"); err != nil { - fmt.Println("get proxy failed", err) - return - } +```go +gogapAop.AddAspect(aspectFoo) +``` - // start trace for debug - aop.StartTrace() +result - fmt.Println("==========Func Type Assertion==========") +```bash +Before Login: zeal +Bar Bar Bar .... Result is: true +After Login: zeal gogap +Login result: true +``` - login := proxy.Method(new(Auth).Login).(func(string, string) bool)("zeal", "gogap") +#### Turn on trace for debug - fmt.Println("Login result:", login) +```go +err := aop.StartTrace() - fmt.Println("================Invoke=================") +.... +// use proxy to call your funcs - if err = proxy.Invoke(new(Auth).Login, "zeal", "errorpassword").End( - func(result bool) { - login = result - }); err != nil { - fmt.Println("invoke proxy func error", err) - } else { - fmt.Println("Login result:", login) - } +t, err := aop.StopTrace() - t, _ := aop.StopTrace() - - // print trace result - for _, item := range t.Items() { +for _, item := range t.Items() { fmt.Println(item.ID, item.InvokeID, item.BeanRefID, item.Pointcut, item.Method) - } } +``` + +```bash +$> go run main.go +go run main.go +==========Func Type Assertion========== +Before Login: zeal +@Begin Around +@Login fakeName fakePassword +@Proceed Result is false +@End Around +After Login: zeal gogap +Login result: false +================Invoke================= +Before Login: zeal +@Begin Around +@Login fakeName fakePassword +@Proceed Result is false +@End Around +After Login: zeal errorpassword +Login result: false +1 aqpk3jjhssa5ul6pt0h0 auth main.(Auth).Login Before +2 aqpk3jjhssa5ul6pt0h0 auth main.(Auth).Login Around +3 aqpk3jjhssa5ul6pt0h0 auth main.(Auth).Login *Login +4 aqpk3jjhssa5ul6pt0h0 foo main.(Auth).Login Bar +5 aqpk3jjhssa5ul6pt0h0 auth main.(Auth).Login After +6 aqpk3jjhssa5ul6pt0hg auth main.(Auth).Login Before +7 aqpk3jjhssa5ul6pt0hg auth main.(Auth).Login Around +8 aqpk3jjhssa5ul6pt0hg auth main.(Auth).Login *Login +9 aqpk3jjhssa5ul6pt0hg foo main.(Auth).Login Bar +10 aqpk3jjhssa5ul6pt0hg auth main.(Auth).Login After +``` +> the `*` means the real func in this call \ No newline at end of file diff --git a/invoke_result.go b/invoke_result.go index 8e3e385..e86d526 100644 --- a/invoke_result.go +++ b/invoke_result.go @@ -7,7 +7,7 @@ import ( "github.com/gogap/errors" ) -type Result []interface{} +type Result []reflect.Value type Args []interface{} func (p Result) MapTo(fn interface{}) { @@ -15,22 +15,22 @@ func (p Result) MapTo(fn interface{}) { } func (p Args) MapTo(fn interface{}) { - mapTo(fn, p) + values := []reflect.Value{} + + for _, arg := range p { + values = append(values, reflect.ValueOf(arg)) + } + + mapTo(fn, values) } -func mapTo(fn interface{}, v []interface{}) { +func mapTo(fn interface{}, values []reflect.Value) { fnType := reflect.TypeOf(fn) if fnType.Kind() != reflect.Func { panic(ErrMapperArgShouldBeFunc.New()) } - values := make([]reflect.Value, len(v)) - - for i := 0; i < len(v); i++ { - values[i] = reflect.ValueOf(v[i]) - } - - if fnType.NumIn() != len(v) { + if fnType.NumIn() != len(values) { panic(ErrWrongMapFuncArgsNum.New()) } diff --git a/join_point.go b/join_point.go index b903768..2df3f27 100644 --- a/join_point.go +++ b/join_point.go @@ -4,6 +4,21 @@ import ( "reflect" ) +type joinPointFunc func(JoinPointer) error +type joinPointWithResultFunc func(JoinPointer, Result) error +type proceedingJoinPoint func(ProceedingJoinPointer) error + +var ( + joinPointFuncType = reflect.TypeOf((*joinPointFunc)(nil)).Elem() + joinPointWithResultFuncType = reflect.TypeOf((*joinPointWithResultFunc)(nil)).Elem() + proceedingJoinPointType = reflect.TypeOf((*proceedingJoinPoint)(nil)).Elem() +) + +var ( + _ JoinPointer = (*JoinPoint)(nil) + _ ProceedingJoinPointer = (*ProceedingJoinPoint)(nil) +) + type JoinPointer interface { Args() Args Target() *Bean @@ -13,7 +28,7 @@ type JoinPointer interface { type ProceedingJoinPointer interface { JoinPointer - Proceed(args ...interface{}) InvokeResult + Proceed(args ...interface{}) Result } type JoinPoint struct { @@ -35,28 +50,38 @@ func (p *JoinPoint) Target() *Bean { } type ProceedingJoinPoint struct { - JoinPoint + JoinPointer method interface{} } func (p *ProceedingJoinPoint) Args() Args { - return p.args + return p.JoinPointer.Args() } func (p *ProceedingJoinPoint) Target() *Bean { - return p.target + return p.JoinPointer.Target() } -func (p *ProceedingJoinPoint) Proceed(args ...interface{}) (ir InvokeResult) { +func (p *ProceedingJoinPoint) Proceed(args ...interface{}) (result Result) { v := reflect.ValueOf(p.method) + if v.Type().NumIn() > 0 { + if len(args) == 0 { + args = p.JoinPointer.Args() + } + } + var vArgs []reflect.Value for _, arg := range args { vArgs = append(vArgs, reflect.ValueOf(arg)) } - ir.values = v.Call(vArgs) + rets := v.Call(vArgs) + + Result(rets).MapTo(func(r Result) { + result = r + }) return } diff --git a/method_metadata.go b/method_metadata.go index b2187d7..b7bd9c0 100644 --- a/method_metadata.go +++ b/method_metadata.go @@ -1,9 +1,40 @@ package aop +import ( + "reflect" +) + type MethodMetadata struct { - method interface{} - MethodName string - IsStatic bool - File string - Line int + Method reflect.Method + File string + Line int +} + +func (p *MethodMetadata) IsEqual(t reflect.Type) bool { + if t.ConvertibleTo(p.Method.Type) { + return false + } + + baseIndex := 0 + if p.Method.Index >= 0 { + baseIndex = 1 + } + + if t.NumIn()+baseIndex != p.Method.Type.NumIn() { + return false + } + + for i := 0; i < p.Method.Type.NumIn()-baseIndex; i++ { + if p.Method.Type.In(baseIndex+i) != t.In(i) { + return false + } + } + + for i := 0; i < p.Method.Type.NumOut(); i++ { + if p.Method.Type.Out(baseIndex+i) != t.Out(i) { + return false + } + } + + return true } diff --git a/proxy.go b/proxy.go index 6e4cf2e..09f84d5 100644 --- a/proxy.go +++ b/proxy.go @@ -25,14 +25,14 @@ func (p *Proxy) BeanID() string { func (p *Proxy) Method(fn interface{}) (method interface{}) { methodName := "" - if methodMetadata, err := getMethodMetadata(fn); err != nil { + if methodMetadata, err := getFuncMetadata(fn); err != nil { panic(err) } else { - methodName = methodMetadata.MethodName + methodName = methodMetadata.Method.Name } if metadata, exist := p.funcs[methodName]; exist { - method = metadata.method + method = metadata.Method.Func.Interface() return } return @@ -41,14 +41,14 @@ func (p *Proxy) Method(fn interface{}) (method interface{}) { func (p *Proxy) Invoke(method interface{}, args ...interface{}) (result *InvokeResult) { methodName := "" - if methodMetadata, err := getMethodMetadata(method); err != nil { + if methodMetadata, err := getFuncMetadata(method); err != nil { result = &InvokeResult{ beanID: p.beanID, methodName: methodName, err: err, } } else { - methodName = methodMetadata.MethodName + methodName = methodMetadata.Method.Name } fnMetadata, exist := p.funcs[methodName] @@ -61,7 +61,7 @@ func (p *Proxy) Invoke(method interface{}, args ...interface{}) (result *InvokeR return } - fn := fnMetadata.method + fn := fnMetadata.Method.Func.Interface() fnType := reflect.TypeOf(fn) if fnType.Kind() != reflect.Func { @@ -100,5 +100,5 @@ func (p *Proxy) Invoke(method interface{}, args ...interface{}) (result *InvokeR } func (p *Proxy) registryFunc(metadata MethodMetadata) { - p.funcs[metadata.MethodName] = metadata + p.funcs[metadata.Method.Name] = metadata } diff --git a/utils.go b/utils.go index 6b35c7a..362f8bf 100644 --- a/utils.go +++ b/utils.go @@ -1,31 +1,66 @@ package aop import ( - "github.com/gogap/errors" "reflect" "runtime" "strings" ) -func getMethodMetadata(method interface{}) (metadata MethodMetadata, err error) { - if method == nil { +func getMethodMetadata(method reflect.Method) (metadata MethodMetadata, err error) { + if method.Func.IsNil() { err = ErrMethodIsNil.New() return } - if reflect.TypeOf(method).Kind() != reflect.Func { + iMethod := method.Func.Interface() + + if reflect.TypeOf(iMethod).Kind() != reflect.Func { + err = ErrBadMethodType.New() + return + } + + v := reflect.ValueOf(iMethod) + + pc := runtime.FuncForPC(v.Pointer()) + + name := strings.TrimRight(pc.Name(), "-fm") + name = strings.Replace(name, "*", "", 1) + + metadata.Method = method + metadata.Method.Name = name + metadata.File, metadata.Line = pc.FileLine(v.Pointer()) + + return +} + +func getFuncMetadata(fn interface{}) (metadata MethodMetadata, err error) { + if fn == nil { + err = ErrMethodIsNil.New() + return + } + + if reflect.TypeOf(fn).Kind() != reflect.Func { err = ErrBadMethodType.New() return } - v := reflect.ValueOf(method) + v := reflect.ValueOf(fn) pc := runtime.FuncForPC(v.Pointer()) - metadata.MethodName = strings.TrimRight(pc.Name(), "-fm") - metadata.MethodName = strings.Replace(metadata.MethodName, "*", "", 1) + name := strings.TrimRight(pc.Name(), "-fm") + name = strings.Replace(name, "*", "", 1) + + m := reflect.Method{ + Name: name, + PkgPath: "", + Type: reflect.TypeOf(fn), + Func: v, + Index: -1, + } + metadata.File, metadata.Line = pc.FileLine(v.Pointer()) - metadata.method = method + metadata.Method = m return } @@ -42,44 +77,17 @@ func invokeAdvices(joinPoint JoinPointer, advices []*Advice, methodName string, appendTraceItem(joinPoint.CallID(), metadata.File, metadata.Line, advice.Method, methodName, advice.beanRef.ID()) } - useJPArgs := false - var jpArgs Args - - adviceArgsType := getFuncArgsType(advice.beanRef, advice.Method) - lenAdviceArgs := len(adviceArgsType) - - if lenAdviceArgs != len(joinPoint.Args()) { - useJPArgs = true - jpArgs = make(Args, lenAdviceArgs) - } else { - for i, adviceArgType := range adviceArgsType { - if adviceArgType != reflect.TypeOf(joinPoint.Args()[i]) { - useJPArgs = true - jpArgs = make([]interface{}, lenAdviceArgs) - } - } + var adviceMethodMeta MethodMetadata + if adviceMethodMeta, err = advice.beanRef.methodMetadata(advice.Method); err != nil { + return } - // inject jp to advice args var invokeArgs Args - if useJPArgs { - jpType := reflect.TypeOf(joinPoint) - retType := reflect.TypeOf(result) - - for i, argType := range adviceArgsType { - if jpType.ConvertibleTo(argType) { - jpArgs[i] = joinPoint - } else if argType == retType { - if advice.Ordering == AfterReturning { - jpArgs[i] = result - } else { - panic(ErrJoinPointArgsUsage.New()) - } - } else { - panic(ErrUnknownJoinPointArgType.New(errors.Params{"id": advice.beanRef.id, "method": advice.Method, "refID": advice.PointcutRefID})) - } - } - invokeArgs = jpArgs + if adviceMethodMeta.IsEqual(joinPointFuncType) || + adviceMethodMeta.IsEqual(proceedingJoinPointType) { + invokeArgs = Args{joinPoint} + } else if adviceMethodMeta.IsEqual(joinPointWithResultFuncType) { + invokeArgs = Args{joinPoint, result} } else { invokeArgs = joinPoint.Args() }