ok make it cleaner, it's working well and blazing fast but I have to do a lot cleaning and commenting and docs as well before push it to master --- hope at christmas day, also thinking some internal ideas - the whole code is not ready to be readen by a third person yet.

Former-commit-id: 0b3fb2841d5032ff47bdca42a6f4ccfeb789ce3c
This commit is contained in:
Gerasimos (Makis) Maropoulos 2017-12-19 23:40:42 +02:00
parent 4261b5784a
commit c15763c556
11 changed files with 343 additions and 313 deletions

View File

@ -13,7 +13,9 @@ import (
func main() { func main() {
app := iris.New() app := iris.New()
mvc.New(app.Party("/api/values/{id}")).Register(new(controllers.ValuesController)) mvc.New(app.Party("/api/values/{id}")).
Register(new(controllers.ValuesController))
app.Run(iris.Addr(":5000"), iris.WithoutVersionChecker) app.Run(iris.Addr(":5000"), iris.WithoutVersionChecker)
} }

View File

@ -35,7 +35,7 @@ type BeforeActivation interface {
type AfterActivation interface { type AfterActivation interface {
shared shared
DependenciesReadOnly() di.ValuesReadOnly DependenciesReadOnly() di.ValuesReadOnly
IsRequestScoped() bool Singleton() bool
} }
var ( var (
@ -85,14 +85,10 @@ func newControllerActivator(router router.Party, controller interface{}, depende
val = reflect.ValueOf(controller) val = reflect.ValueOf(controller)
typ = val.Type() typ = val.Type()
// the full name of the controller, it's its type including the package path. // the full name of the controller: its type including the package path.
fullName = getNameOf(typ) fullName = getNameOf(typ)
) )
// add the manual filled fields to the dependencies.
filledFieldValues := di.LookupNonZeroFieldsValues(val)
dependencies.AddValue(filledFieldValues...)
c := &ControllerActivator{ c := &ControllerActivator{
// give access to the Router to the end-devs if they need it for some reason, // give access to the Router to the end-devs if they need it for some reason,
// i.e register done handlers. // i.e register done handlers.
@ -141,26 +137,16 @@ func (c *ControllerActivator) Router() router.Party {
return c.router return c.router
} }
// IsRequestScoped returns new if each request has its own instance // Singleton returns new if all incoming clients' requests
// of the controller and it contains dependencies that are not manually // have the same controller instance.
// filled by the struct initialization from the caller. // This is done automatically by iris to reduce the creation
func (c *ControllerActivator) IsRequestScoped() bool { // of a new controller on each request, if the controller doesn't contain
// if the c.injector == nil means that is not seted to invalidate state, // any unexported fields and all fields are services-like, static.
// so it contains more fields that are filled by the end-dev. func (c *ControllerActivator) Singleton() bool {
// This "strange" check happens because the `IsRequestScoped` may if c.injector == nil {
// called before the controller activation complete its task (see Handle: if c.injector == nil). panic("MVC: IsRequestScoped used on an invalid state the API gives access to it only `AfterActivation`, report this as bug")
if c.injector == nil { // is nil so it contains more fields, maybe request-scoped or dependencies.
return true
} }
if c.injector.Valid { return c.injector.State == di.Singleton
// if injector is not nil:
// if it is !Valid then all fields are manually filled by the end-dev (see `newControllerActivator`).
// if it is Valid then it's filled on the first `Handle`
// and it has more than one valid dependency from the registered values.
return true
}
// it's not nil and it's !Valid.
return false
} }
// checks if a method is already registered. // checks if a method is already registered.
@ -191,21 +177,16 @@ func (c *ControllerActivator) parseMethod(m reflect.Method) {
// register all available, exported methods to handlers if possible. // register all available, exported methods to handlers if possible.
func (c *ControllerActivator) parseMethods() { func (c *ControllerActivator) parseMethods() {
n := c.Type.NumMethod() n := c.Type.NumMethod()
// wg := &sync.WaitGroup{}
// wg.Add(n)
for i := 0; i < n; i++ { for i := 0; i < n; i++ {
m := c.Type.Method(i) m := c.Type.Method(i)
c.parseMethod(m) c.parseMethod(m)
} }
// wg.Wait()
} }
func (c *ControllerActivator) activate() { func (c *ControllerActivator) activate() {
c.parseMethods() c.parseMethods()
} }
var emptyIn = []reflect.Value{}
// Handle registers a route based on a http method, the route's path // Handle registers a route based on a http method, the route's path
// and a function name that belongs to the controller, it accepts // and a function name that belongs to the controller, it accepts
// a forth, optionally, variadic parameter which is the before handlers. // a forth, optionally, variadic parameter which is the before handlers.
@ -221,6 +202,30 @@ func (c *ControllerActivator) Handle(method, path, funcName string, middleware .
return nil return nil
} }
// Remember:
// we cannot simply do that and expect to work:
// hasStructInjector = c.injector != nil && c.injector.Valid
// hasFuncInjector = funcInjector != nil && funcInjector.Valid
// because
// the `Handle` can be called from `BeforeActivation` callbacks
// and before activation, the c.injector is nil because
// we may not have the dependencies binded yet. But if `c.injector.Valid`
// inside the Handelr works because it's set on the `activate()` method.
// To solve this we can make check on the FIRST `Handle`,
// if c.injector is nil, then set it with the current bindings,
// so the user should bind the dependencies needed before the `Handle`
// this is a logical flow, so we will choose that one ->
if c.injector == nil {
// first, set these bindings to the passed controller, they will be useless
// if the struct contains any dynamic value because this controller will
// be never fired as it's but we make that in order to get the length of the static
// matched dependencies of the struct.
c.injector = di.MakeStructInjector(c.Value, hijacker, typeChecker, c.dependencies...)
if c.injector.HasFields {
golog.Debugf("MVC dependencies of '%s':\n%s", c.fullName, c.injector.String())
}
}
// get the method from the controller type. // get the method from the controller type.
m, ok := c.Type.MethodByName(funcName) m, ok := c.Type.MethodByName(funcName)
if !ok { if !ok {
@ -238,77 +243,17 @@ func (c *ControllerActivator) Handle(method, path, funcName string, middleware .
return nil return nil
} }
// add this as a reserved method name in order to
// be sure that the same func will not be registered again, even if a custom .Handle later on.
c.reservedMethods = append(c.reservedMethods, funcName)
// get the function's input. // get the function's input.
funcIn := getInputArgsFromFunc(m.Type) funcIn := getInputArgsFromFunc(m.Type)
// get the path parameters bindings from the template, // get the path parameters bindings from the template,
// use the function's input except the receiver which is the // use the function's input except the receiver which is the
// end-dev's controller pointer. // end-dev's controller pointer.
pathParams := getPathParamsForInput(tmpl.Params, funcIn[1:]...) pathParams := getPathParamsForInput(tmpl.Params, funcIn[1:]...)
// get the function's input arguments' bindings. // get the function's input arguments' bindings.
funcDependencies := c.dependencies.Clone() funcDependencies := c.dependencies.Clone()
funcDependencies.AddValue(pathParams...) funcDependencies.AddValues(pathParams...)
// fmt.Printf("for %s | values: %s\n", funcName, funcDependencies) handler := c.handlerOf(m, funcDependencies)
funcInjector := di.MakeFuncInjector(m.Func, hijacker, typeChecker, funcDependencies...)
// fmt.Printf("actual injector's inputs length: %d\n", funcInjector.Length)
// the element value, not the pointer, wil lbe used to create a
// new controller on each incoming request.
// Remember:
// we cannot simply do that and expect to work:
// hasStructInjector = c.injector != nil && c.injector.Valid
// hasFuncInjector = funcInjector != nil && funcInjector.Valid
// because
// the `Handle` can be called from `BeforeActivation` callbacks
// and before activation, the c.injector is nil because
// we may not have the dependencies binded yet. But if `c.injector.Valid`
// inside the Handelr works because it's set on the `activate()` method.
// To solve this we can make check on the FIRST `Handle`,
// if c.injector is nil, then set it with the current bindings,
// so the user should bind the dependencies needed before the `Handle`
// this is a logical flow, so we will choose that one ->
if c.injector == nil {
// check if manually filled + any dependencies are only static, if so
// and the total struct's fields are equal these static dependencies length
// then we don't need to create a new struct on each request.
//
// We use our custom NumFields here because the std "reflect" package
// checks only for the current struct and not for embedded's exported fields.
totalFieldsLength := di.NumFields(di.IndirectType(c.Type))
// first, set these bindings to the passed controller, they will be useless
// if the struct contains any dynamic value because this controller will
// be never fired as it's but we make that in order to get the length of the static
// matched dependencies of the struct.
c.injector = di.MakeStructInjector(c.Value, hijacker, typeChecker, c.dependencies...)
matchedStaticDependenciesLength := c.injector.InjectElemStaticOnly(di.IndirectValue(c.Value))
if c.injector.Valid {
golog.Debugf("MVC dependencies of '%s':\n%s", c.fullName, c.injector.String())
}
if matchedStaticDependenciesLength == totalFieldsLength {
// all fields are filled by the end-developer or via static dependencies (if context is there then it will be filled by the MakeStructInjector so we don't worry about it),
// the controller doesn't contain any other field neither any dynamic binding as well.
// Therefore we don't need to create a new controller each time.
// Set the c.injector now instead on the first `Handle` and set it to invalid state
// in order to `buildControllerHandler` ignore the
// creation of a new controller value on each incoming request.
c.injector = &di.StructInjector{Valid: false}
}
}
if funcInjector.Valid {
golog.Debugf("MVC dependencies of method '%s.%s':\n%s", c.fullName, funcName, funcInjector.String())
}
handler := buildControllerHandler(m, c.Type, c.Value, c.injector, funcInjector, funcIn)
// register the handler now. // register the handler now.
route := c.router.Handle(method, path, append(middleware, handler)...) route := c.router.Handle(method, path, append(middleware, handler)...)
@ -316,98 +261,55 @@ func (c *ControllerActivator) Handle(method, path, funcName string, middleware .
// change the main handler's name in order to respect the controller's and give // change the main handler's name in order to respect the controller's and give
// a proper debug message. // a proper debug message.
route.MainHandlerName = fmt.Sprintf("%s.%s", c.fullName, funcName) route.MainHandlerName = fmt.Sprintf("%s.%s", c.fullName, funcName)
// add this as a reserved method name in order to
// be sure that the same func will not be registered again,
// even if a custom .Handle later on.
c.reservedMethods = append(c.reservedMethods, funcName)
} }
return route return route
} }
// buildControllerHandler has many many dublications but we do that to achieve the best var emptyIn = []reflect.Value{}
// performance possible, to use the information we know
// and calculate what is needed and what not in serve-time. func (c *ControllerActivator) handlerOf(m reflect.Method, funcDependencies []reflect.Value) context.Handler {
func buildControllerHandler(m reflect.Method, typ reflect.Type, initRef reflect.Value, structInjector *di.StructInjector, funcInjector *di.FuncInjector, funcIn []reflect.Type) context.Handler { // fmt.Printf("for %s | values: %s\n", funcName, funcDependencies)
funcInjector := di.MakeFuncInjector(m.Func, hijacker, typeChecker, funcDependencies...)
// fmt.Printf("actual injector's inputs length: %d\n", funcInjector.Length)
if funcInjector.Valid {
golog.Debugf("MVC dependencies of method '%s.%s':\n%s", c.fullName, m.Name, funcInjector.String())
}
var ( var (
hasStructInjector = structInjector != nil && structInjector.Valid implementsBase = isBaseController(c.Type)
hasFuncInjector = funcInjector != nil && funcInjector.Valid hasBindableFields = c.injector.CanInject
hasBindableFuncInputs = funcInjector.Valid
implementsBase = isBaseController(typ) call = m.Func.Call
// we will make use of 'n' to make a slice of reflect.Value
// to pass into if the function has input arguments that
// are will being filled by the funcDependencies.
n = len(funcIn)
elemTyp = di.IndirectType(typ)
) )
// if it doesn't implement the base controller, if !implementsBase && !hasBindableFields && !hasBindableFuncInputs {
// it may have struct injector and/or func injector.
if !implementsBase {
if !hasStructInjector {
// if the controller doesn't have a struct injector
// and the controller's fields are empty or all set-ed by the end-dev
// then we don't need a new controller instance, we use the passed controller instance.
if !hasFuncInjector {
return func(ctx context.Context) { return func(ctx context.Context) {
DispatchFuncResult(ctx, initRef.Method(m.Index).Call(emptyIn)) DispatchFuncResult(ctx, call(c.injector.NewAsSlice()))
} }
} }
n := m.Type.NumIn()
return func(ctx context.Context) { return func(ctx context.Context) {
in := make([]reflect.Value, n, n) var (
in[0] = initRef ctrl = c.injector.New()
funcInjector.Inject(&in, reflect.ValueOf(ctx)) ctxValue reflect.Value
if ctx.IsStopped() { )
return
// inject struct fields first before the BeginRequest and EndRequest, if any,
// in order to be able to have access there.
if hasBindableFields {
ctxValue = reflect.ValueOf(ctx)
c.injector.InjectElem(ctrl.Elem(), ctxValue)
} }
DispatchFuncResult(ctx, m.Func.Call(in)) // check if has BeginRequest & EndRequest, before try to bind the method's inputs.
}
}
// it has struct injector for sure and maybe a func injector.
if !hasFuncInjector {
return func(ctx context.Context) {
ctrl := reflect.New(elemTyp)
ctxValue := reflect.ValueOf(ctx)
elem := ctrl.Elem()
structInjector.InjectElem(elem, ctxValue)
if ctx.IsStopped() {
return
}
DispatchFuncResult(ctx, ctrl.Method(m.Index).Call(emptyIn))
}
}
// has struct injector and func injector.
return func(ctx context.Context) {
ctrl := reflect.New(elemTyp)
ctxValue := reflect.ValueOf(ctx)
elem := ctrl.Elem()
structInjector.InjectElem(elem, ctxValue)
if ctx.IsStopped() {
return
}
in := make([]reflect.Value, n, n)
in[0] = ctrl
funcInjector.Inject(&in, ctxValue)
if ctx.IsStopped() {
return
}
DispatchFuncResult(ctx, m.Func.Call(in))
}
}
// if implements the base controller,
// it may have struct injector and func injector as well.
return func(ctx context.Context) {
ctrl := reflect.New(elemTyp)
if implementsBase { if implementsBase {
// the Interface(). is faster than MethodByName or pre-selected methods. // the Interface(). is faster than MethodByName or pre-selected methods.
b := ctrl.Interface().(BaseController) b := ctrl.Interface().(BaseController)
@ -422,36 +324,20 @@ func buildControllerHandler(m reflect.Method, typ reflect.Type, initRef reflect.
defer b.EndRequest(ctx) defer b.EndRequest(ctx)
} }
if !hasStructInjector && !hasFuncInjector { if hasBindableFuncInputs {
DispatchFuncResult(ctx, ctrl.Method(m.Index).Call(emptyIn)) // means that ctxValue is not initialized before by the controller's struct injector.
} else { if !hasBindableFields {
ctxValue := reflect.ValueOf(ctx) ctxValue = reflect.ValueOf(ctx)
if hasStructInjector {
elem := ctrl.Elem()
structInjector.InjectElem(elem, ctxValue)
if ctx.IsStopped() {
return
} }
// we do this in order to reduce in := make...
// if not func input binders, we execute the handler with empty input args.
if !hasFuncInjector {
DispatchFuncResult(ctx, ctrl.Method(m.Index).Call(emptyIn))
}
}
// otherwise, it has one or more valid input binders,
// make the input and call the func using those.
if hasFuncInjector {
in := make([]reflect.Value, n, n) in := make([]reflect.Value, n, n)
in[0] = ctrl in[0] = ctrl
funcInjector.Inject(&in, ctxValue) funcInjector.Inject(&in, ctxValue)
if ctx.IsStopped() { DispatchFuncResult(ctx, call(in))
return return
} }
DispatchFuncResult(ctx, m.Func.Call(in)) DispatchFuncResult(ctx, ctrl.Method(m.Index).Call(emptyIn))
} }
} }
}
}

View File

@ -469,8 +469,8 @@ func (c *testControllerNotCreateNewDueManuallySettingAllFields) AfterActivation(
-- got dependencies length: %d`, n) -- got dependencies length: %d`, n)
} }
if a.IsRequestScoped() { if !a.Singleton() {
c.T.Fatalf(`this controller shouldn't be tagged used as request scoped(create new instances on each request), c.T.Fatalf(`this controller should be tagged as Singleton. It shouldn't be tagged used as request scoped(create new instances on each request),
it doesn't contain any dynamic value or dependencies that should be binded via the iris mvc engine`) it doesn't contain any dynamic value or dependencies that should be binded via the iris mvc engine`)
} }
} }

View File

@ -85,22 +85,15 @@ func MakeFuncInjector(fn reflect.Value, hijack Hijacker, goodFunc TypeChecker, v
} }
} }
// s.Length = n
s.Length = len(s.inputs) s.Length = len(s.inputs)
s.Valid = s.Length > 0 s.Valid = s.Length > 0
for i, in := range s.inputs { for i, in := range s.inputs {
bindmethodTyp := "Static" bindmethodTyp := bindTypeString(in.Object.BindType)
if in.Object.BindType == Dynamic {
bindmethodTyp = "Dynamic"
}
typIn := typ.In(in.InputIndex) typIn := typ.In(in.InputIndex)
// remember: on methods that are part of a struct (i.e controller) // remember: on methods that are part of a struct (i.e controller)
// the input index = 1 is the begggining instead of the 0, // the input index = 1 is the begggining instead of the 0,
// because the 0 is the controller receiver pointer of the method. // because the 0 is the controller receiver pointer of the method.
s.trace += fmt.Sprintf("[%d] %s binding: '%s' for input position: %d and type: '%s'\n", i+1, bindmethodTyp, in.Object.Type.String(), in.InputIndex, typIn.String()) s.trace += fmt.Sprintf("[%d] %s binding: '%s' for input position: %d and type: '%s'\n", i+1, bindmethodTyp, in.Object.Type.String(), in.InputIndex, typIn.String())
} }

View File

@ -12,6 +12,15 @@ const (
Dynamic // dynamic value, depends on some input arguments from the caller. Dynamic // dynamic value, depends on some input arguments from the caller.
) )
func bindTypeString(typ BindType) string {
switch typ {
case Dynamic:
return "Dynamic"
default:
return "Static"
}
}
type BindObject struct { type BindObject struct {
Type reflect.Type // the Type of 'Value' or the type of the returned 'ReturnValue' . Type reflect.Type // the Type of 'Value' or the type of the returned 'ReturnValue' .
Value reflect.Value Value reflect.Value

View File

@ -66,6 +66,13 @@ func ValueOf(o interface{}) reflect.Value {
return reflect.ValueOf(o) return reflect.ValueOf(o)
} }
func ValuesOf(valuesAsInterface []interface{}) (values []reflect.Value) {
for _, v := range valuesAsInterface {
values = append(values, ValueOf(v))
}
return
}
func IndirectType(typ reflect.Type) reflect.Type { func IndirectType(typ reflect.Type) reflect.Type {
switch typ.Kind() { switch typ.Kind() {
case reflect.Ptr, reflect.Array, reflect.Chan, reflect.Map, reflect.Slice: case reflect.Ptr, reflect.Array, reflect.Chan, reflect.Map, reflect.Slice:
@ -116,8 +123,9 @@ func structFieldIgnored(f reflect.StructField) bool {
type field struct { type field struct {
Type reflect.Type Type reflect.Type
Name string // the actual name.
Index []int // the index of the field, slice if it's part of a embedded struct Index []int // the index of the field, slice if it's part of a embedded struct
Name string // the actual name CanSet bool // is true if it's exported.
// this could be empty, but in our cases it's not, // this could be empty, but in our cases it's not,
// it's filled with the bind object (as service which means as static value) // it's filled with the bind object (as service which means as static value)
@ -127,11 +135,11 @@ type field struct {
// NumFields returns the total number of fields, and the embedded, even if the embedded struct is not exported, // NumFields returns the total number of fields, and the embedded, even if the embedded struct is not exported,
// it will check for its exported fields. // it will check for its exported fields.
func NumFields(elemTyp reflect.Type) int { func NumFields(elemTyp reflect.Type, skipUnexported bool) int {
return len(lookupFields(elemTyp, nil)) return len(lookupFields(elemTyp, skipUnexported, nil))
} }
func lookupFields(elemTyp reflect.Type, parentIndex []int) (fields []field) { func lookupFields(elemTyp reflect.Type, skipUnexported bool, parentIndex []int) (fields []field) {
if elemTyp.Kind() != reflect.Struct { if elemTyp.Kind() != reflect.Struct {
return return
} }
@ -141,14 +149,15 @@ func lookupFields(elemTyp reflect.Type, parentIndex []int) (fields []field) {
if IndirectType(f.Type).Kind() == reflect.Struct && if IndirectType(f.Type).Kind() == reflect.Struct &&
!structFieldIgnored(f) { !structFieldIgnored(f) {
fields = append(fields, lookupFields(f.Type, append(parentIndex, i))...) fields = append(fields, lookupFields(f.Type, skipUnexported, append(parentIndex, i))...)
continue continue
} }
// skip unexported fields here, // skip unexported fields here,
// after the check for embedded structs, these can be binded if their // after the check for embedded structs, these can be binded if their
// fields are exported. // fields are exported.
if f.PkgPath != "" { isExported := f.PkgPath == ""
if skipUnexported && !isExported {
continue continue
} }
@ -161,6 +170,7 @@ func lookupFields(elemTyp reflect.Type, parentIndex []int) (fields []field) {
Type: f.Type, Type: f.Type,
Name: f.Name, Name: f.Name,
Index: index, Index: index,
CanSet: isExported,
} }
fields = append(fields, field) fields = append(fields, field)
@ -172,12 +182,13 @@ func lookupFields(elemTyp reflect.Type, parentIndex []int) (fields []field) {
// LookupNonZeroFieldsValues lookup for filled fields based on the "v" struct value instance. // LookupNonZeroFieldsValues lookup for filled fields based on the "v" struct value instance.
// It returns a slice of reflect.Value (same type as `Values`) that can be binded, // It returns a slice of reflect.Value (same type as `Values`) that can be binded,
// like the end-developer's custom values. // like the end-developer's custom values.
func LookupNonZeroFieldsValues(v reflect.Value) (bindValues []reflect.Value) { func LookupNonZeroFieldsValues(v reflect.Value, skipUnexported bool) (bindValues []reflect.Value) {
elem := IndirectValue(v) elem := IndirectValue(v)
fields := lookupFields(IndirectType(v.Type()), nil) fields := lookupFields(IndirectType(v.Type()), skipUnexported, nil)
for _, f := range fields {
if fieldVal := elem.FieldByIndex(f.Index); f.Type.Kind() == reflect.Ptr && !IsZero(fieldVal) { for _, f := range fields {
if fieldVal := elem.FieldByIndex(f.Index); /*f.Type.Kind() == reflect.Ptr &&*/
!IsZero(fieldVal) {
bindValues = append(bindValues, fieldVal) bindValues = append(bindValues, fieldVal)
} }
} }

View File

@ -5,6 +5,13 @@ import (
"reflect" "reflect"
) )
type State uint8
const (
Stateless State = iota
Singleton
)
type ( type (
targetStructField struct { targetStructField struct {
Object *BindObject Object *BindObject
@ -12,22 +19,38 @@ type (
} }
StructInjector struct { StructInjector struct {
initRef reflect.Value
initRefAsSlice []reflect.Value // useful when the struct is passed on a func as input args via reflection.
elemType reflect.Type elemType reflect.Type
// //
fields []*targetStructField fields []*targetStructField
Valid bool // is true when contains fields and it's a valid target struct. // is true when contains bindable fields and it's a valid target struct,
trace string // for debug info. // it maybe 0 but struct may contain unexported fields or exported but no bindable (Stateless)
// see `setState`.
HasFields bool
CanInject bool // if any bindable fields when the state is NOT singleton.
State State
} }
) )
func (s *StructInjector) countBindType(typ BindType) (n int) {
for _, f := range s.fields {
if f.Object.BindType == typ {
n++
}
}
return
}
func MakeStructInjector(v reflect.Value, hijack Hijacker, goodFunc TypeChecker, values ...reflect.Value) *StructInjector { func MakeStructInjector(v reflect.Value, hijack Hijacker, goodFunc TypeChecker, values ...reflect.Value) *StructInjector {
s := &StructInjector{ s := &StructInjector{
initRef: v,
initRefAsSlice: []reflect.Value{v},
elemType: IndirectType(v.Type()), elemType: IndirectType(v.Type()),
} }
fields := lookupFields(s.elemType, nil) fields := lookupFields(s.elemType, true, nil)
for _, f := range fields { for _, f := range fields {
if hijack != nil { if hijack != nil {
if b, ok := hijack(f.Type); ok && b != nil { if b, ok := hijack(f.Type); ok && b != nil {
s.fields = append(s.fields, &targetStructField{ s.fields = append(s.fields, &targetStructField{
@ -55,28 +78,75 @@ func MakeStructInjector(v reflect.Value, hijack Hijacker, goodFunc TypeChecker,
}) })
break break
} }
} }
} }
s.Valid = len(s.fields) > 0 s.HasFields = len(s.fields) > 0
// set the overall state of this injector.
if s.Valid { s.setState()
for i, f := range s.fields { s.fillStruct()
bindmethodTyp := "Static"
if f.Object.BindType == Dynamic {
bindmethodTyp = "Dynamic"
}
elemField := s.elemType.FieldByIndex(f.FieldIndex)
s.trace += fmt.Sprintf("[%d] %s binding: '%s' for field '%s %s'\n", i+1, bindmethodTyp, f.Object.Type.String(), elemField.Name, elemField.Type.String())
}
}
return s return s
} }
func (s *StructInjector) String() string { // set the state, once.
return s.trace func (s *StructInjector) setState() {
// note for zero length of struct's fields:
// if struct doesn't contain any field
// so both of the below variables will be 0,
// so it's a singleton.
// At the other hand the `s.HasFields` maybe false
// but the struct may contain UNEXPORTED fields or non-bindable fields (request-scoped on both cases)
// so a new controller/struct at the caller side should be initialized on each request,
// we should not depend on the `HasFields` for singleton or no, this is the reason I
// added the `.State` now.
staticBindingsFieldsLength := s.countBindType(Static)
structFieldsLength := NumFields(s.elemType, false)
// println("staticBindingsFieldsLength: ", staticBindingsFieldsLength)
// println("structFieldsLength: ", structFieldsLength)
// if the number of static values binded is equal to the
// total struct's fields(including unexported fields this time) then set as singleton.
if staticBindingsFieldsLength == structFieldsLength {
s.State = Singleton
return
}
s.CanInject = s.State == Stateless && s.HasFields
// the default is `Stateless`, which means that a new instance should be created
// on each inject action by the caller.
}
// fill the static bindings values once.
func (s *StructInjector) fillStruct() {
if !s.HasFields {
return
}
// if field is Static then set it to the value that passed by the caller,
// so will have the static bindings already and we can just use that value instead
// of creating new instance.
destElem := IndirectValue(s.initRef)
for _, f := range s.fields {
// if field is Static then set it to the value that passed by the caller,
// so will have the static bindings already and we can just use that value instead
// of creating new instance.
if s.State == Singleton && f.Object.BindType == Static {
destElem.FieldByIndex(f.FieldIndex).Set(f.Object.Value)
}
}
}
func (s *StructInjector) String() (trace string) {
for i, f := range s.fields {
elemField := s.elemType.FieldByIndex(f.FieldIndex)
trace += fmt.Sprintf("[%d] %s binding: '%s' for field '%s %s'\n",
i+1, bindTypeString(f.Object.BindType), f.Object.Type.String(),
elemField.Name, elemField.Type.String())
}
return
} }
func (s *StructInjector) Inject(dest interface{}, ctx ...reflect.Value) { func (s *StructInjector) Inject(dest interface{}, ctx ...reflect.Value) {
@ -91,25 +161,21 @@ func (s *StructInjector) Inject(dest interface{}, ctx ...reflect.Value) {
func (s *StructInjector) InjectElem(destElem reflect.Value, ctx ...reflect.Value) { func (s *StructInjector) InjectElem(destElem reflect.Value, ctx ...reflect.Value) {
for _, f := range s.fields { for _, f := range s.fields {
f.Object.Assign(ctx, func(v reflect.Value) { f.Object.Assign(ctx, func(v reflect.Value) {
// fmt.Printf("%s for %s at index: %d\n", destElem.Type().String(), f.Object.Type.String(), f.FieldIndex)
destElem.FieldByIndex(f.FieldIndex).Set(v) destElem.FieldByIndex(f.FieldIndex).Set(v)
}) })
} }
} }
func (s *StructInjector) InjectElemStaticOnly(destElem reflect.Value) (n int) { func (s *StructInjector) New() reflect.Value {
for _, f := range s.fields { if s.State == Singleton {
if f.Object.BindType != Static { return s.initRef
continue
} }
destElem.FieldByIndex(f.FieldIndex).Set(f.Object.Value) return reflect.New(s.elemType)
n++
}
return
} }
func (s *StructInjector) New(ctx ...reflect.Value) reflect.Value { func (s *StructInjector) NewAsSlice() []reflect.Value {
dest := reflect.New(s.elemType) if s.State == Singleton {
s.InjectElem(dest, ctx...) return s.initRefAsSlice
return dest }
return []reflect.Value{reflect.New(s.elemType)}
} }

View File

@ -8,6 +8,8 @@ type ValuesReadOnly interface {
Has(value interface{}) bool Has(value interface{}) bool
// Len returns the length of the values. // Len returns the length of the values.
Len() int Len() int
// Clone returns a copy of the current values.
Clone() Values
} }
type Values []reflect.Value type Values []reflect.Value
@ -27,21 +29,29 @@ func (bv Values) Clone() Values {
return NewValues() return NewValues()
} }
// CloneWithFieldsOf will return a copy of the current values
// plus the "v" struct's fields that are filled(non-zero) by the caller.
func (bv Values) CloneWithFieldsOf(s interface{}) Values {
values := bv.Clone()
// add the manual filled fields to the dependencies.
filledFieldValues := LookupNonZeroFieldsValues(ValueOf(s), true)
values = append(values, filledFieldValues...)
return values
}
func (bv Values) Len() int { func (bv Values) Len() int {
return len(bv) return len(bv)
} }
// Add binds values to this controller, if you want to share // Add adds values as dependencies, if the struct's fields
// binding values between controllers use the Engine's `Bind` function instead. // or the function's input arguments needs them, they will be defined as
// bindings (at build-time) and they will be used (at serve-time).
func (bv *Values) Add(values ...interface{}) { func (bv *Values) Add(values ...interface{}) {
for _, val := range values { bv.AddValues(ValuesOf(values)...)
bv.AddValue(reflect.ValueOf(val))
}
} }
// AddValue same as `Add` but accepts reflect.Value func (bv *Values) AddValues(values ...reflect.Value) {
// instead.
func (bv *Values) AddValue(values ...reflect.Value) {
for _, v := range values { for _, v := range values {
if !goodVal(v) { if !goodVal(v) {
continue continue
@ -115,6 +125,6 @@ func (bv *Values) addIfNotExists(v reflect.Value) bool {
return false return false
} }
bv.AddValue(v) bv.Add(v)
return true return true
} }

View File

@ -85,7 +85,9 @@ func (e *Engine) Handler(handler interface{}) context.Handler {
// //
// Examples at: https://github.com/kataras/iris/tree/master/_examples/mvc. // Examples at: https://github.com/kataras/iris/tree/master/_examples/mvc.
func (e *Engine) Controller(router router.Party, controller interface{}, beforeActivate ...func(BeforeActivation)) { func (e *Engine) Controller(router router.Party, controller interface{}, beforeActivate ...func(BeforeActivation)) {
ca := newControllerActivator(router, controller, e.Dependencies.Clone()) // add the manual filled fields to the dependencies.
dependencies := e.Dependencies.CloneWithFieldsOf(controller)
ca := newControllerActivator(router, controller, dependencies)
// give a priority to the "beforeActivate" // give a priority to the "beforeActivate"
// callbacks, if any. // callbacks, if any.

View File

@ -165,8 +165,7 @@ func DispatchCommon(ctx context.Context,
// //
// where Get is an HTTP METHOD. // where Get is an HTTP METHOD.
func DispatchFuncResult(ctx context.Context, values []reflect.Value) { func DispatchFuncResult(ctx context.Context, values []reflect.Value) {
numOut := len(values) if len(values) == 0 {
if numOut == 0 {
return return
} }
@ -195,15 +194,19 @@ func DispatchFuncResult(ctx context.Context, values []reflect.Value) {
) )
for _, v := range values { for _, v := range values {
// order of these checks matters // order of these checks matters
// for example, first we need to check for status code, // for example, first we need to check for status code,
// secondly the string (for content type and content)... // secondly the string (for content type and content)...
// if !v.IsValid() || !v.CanInterface() {
// continue
// }
if !v.IsValid() { if !v.IsValid() {
continue continue
} }
f := v.Interface() f := v.Interface()
/*
if b, ok := f.(bool); ok { if b, ok := f.(bool); ok {
found = b found = b
if !found { if !found {
@ -263,6 +266,53 @@ func DispatchFuncResult(ctx context.Context, values []reflect.Value) {
} }
*/
switch value := f.(type) {
case bool:
found = value
if !found {
// skip everything, skip other values, we don't care about other return values,
// this boolean is the higher in order.
break
}
case int:
statusCode = value
case string:
// a string is content type when it contains a slash and
// content or custom struct is being calculated already;
// (string -> content, string-> content type)
// (customStruct, string -> content type)
if (len(content) > 0 || custom != nil) && strings.IndexByte(value, slashB) > 0 {
contentType = value
} else {
// otherwise is content
content = []byte(value)
}
case []byte:
// it's raw content, get the latest
content = value
case compatibleErr:
if value != nil { // it's always not nil but keep it here.
err = value
if statusCode < 400 {
statusCode = DefaultErrStatusCode
}
break // break on first error, error should be in the end but we
// need to know break the dispatcher if any error.
// at the end; we don't want to write anything to the response if error is not nil.
}
default:
// else it's a custom struct or a dispatcher, we'll decide later
// because content type and status code matters
// do that check in order to be able to correctly dispatch:
// (customStruct, error) -> customStruct filled and error is nil
if custom == nil && f != nil {
custom = f
}
}
}
DispatchCommon(ctx, statusCode, contentType, content, custom, err, found) DispatchCommon(ctx, statusCode, contentType, content, custom, err, found)
} }

View File

@ -15,6 +15,7 @@ import (
// Therefore I should reduce some "freedom of change" for the shake of code maintanability in the core/router files: handler.go | router.go and single change on APIBuilder's field. // Therefore I should reduce some "freedom of change" for the shake of code maintanability in the core/router files: handler.go | router.go and single change on APIBuilder's field.
func main() { func main() {
app := iris.New() app := iris.New()
app.Logger().SetLevel("debug")
mvc.New(app.Party("/todo")).Configure(TodoApp) mvc.New(app.Party("/todo")).Configure(TodoApp)
// no let's have a clear "mvc" package without any conversions and type aliases, // no let's have a clear "mvc" package without any conversions and type aliases,
// it's one extra import path for a whole new world, it worths it. // it's one extra import path for a whole new world, it worths it.
@ -74,8 +75,8 @@ func (c *TodoController) BeforeActivation(b mvc.BeforeActivation) {
} }
func (c *TodoController) AfterActivation(a mvc.AfterActivation) { func (c *TodoController) AfterActivation(a mvc.AfterActivation) {
if !a.IsRequestScoped() { if a.Singleton() {
panic("TodoController should be request scoped, we have a 'Session' which depends on the context.") panic("TodoController should be stateless, a request-scoped, we have a 'Session' which depends on the context.")
} }
} }