more checks about creating new instance of controller on each request - this time if all bindings are static then set them to the initial-devpassed controller and if the total number of lengths are equal with these static dependencies then we ignore the injector and use the initial controller on each request - maximize the performance when simple controller is used - need more cleanup before new release but I hope until Christmas iris developers will be amazed

Former-commit-id: 32ed69368d1df2c25cdb712bb7f0cf47b2e36c05
This commit is contained in:
Gerasimos (Makis) Maropoulos 2017-12-18 00:16:10 +02:00
parent 40b40fa7d3
commit d5a38a0cd6
19 changed files with 232 additions and 117 deletions

View File

@ -80,22 +80,22 @@ func (c *ExampleController) GetHello() interface{} {
return map[string]string{"message": "Hello Iris!"} return map[string]string{"message": "Hello Iris!"}
} }
// BeforeActivate called once, before the controller adapted to the main application // BeforeActivation called once, before the controller adapted to the main application
// and of course before the server ran. // and of course before the server ran.
// After version 9 you can also add custom routes for a specific controller's methods. // After version 9 you can also add custom routes for a specific controller's methods.
// Here you can register custom method's handlers // Here you can register custom method's handlers
// use the standard router with `ca.Router` to do something that you can do without mvc as well, // use the standard router with `ca.Router` to do something that you can do without mvc as well,
// and add dependencies that will be binded to a controller's fields or method function's input arguments. // and add dependencies that will be binded to a controller's fields or method function's input arguments.
func (c *ExampleController) BeforeActivate(ca *mvc.ControllerActivator) { func (c *ExampleController) BeforeActivation(b mvc.BeforeActivation) {
anyMiddlewareHere := func(ctx iris.Context) { anyMiddlewareHere := func(ctx iris.Context) {
ctx.Application().Logger().Warnf("Inside /custom_path") ctx.Application().Logger().Warnf("Inside /custom_path")
ctx.Next() ctx.Next()
} }
ca.Handle("GET", "/custom_path", "CustomHandlerWithoutFollowingTheNamingGuide", anyMiddlewareHere) b.Handle("GET", "/custom_path", "CustomHandlerWithoutFollowingTheNamingGuide", anyMiddlewareHere)
// or even add a global middleware based on this controller's router, // or even add a global middleware based on this controller's router,
// which in this example is the root "/": // which in this example is the root "/":
// ca.Router.Use(myMiddleware) // b.Router().Use(myMiddleware)
} }
// CustomHandlerWithoutFollowingTheNamingGuide serves // CustomHandlerWithoutFollowingTheNamingGuide serves
@ -140,11 +140,13 @@ func (c *ExampleController) Any() {}
func (c *ExampleController) BeforeActivate(ca *mvc.ControllerActivator) { func (c *ExampleController) BeforeActivation(b mvc.BeforeActivation) {
// 1 -> the HTTP Method // 1 -> the HTTP Method
// 2 -> the route's path // 2 -> the route's path
// 3 -> this controller's method name that should be handler for that route. // 3 -> this controller's method name that should be handler for that route.
ca.Handle("GET", "/mypath/{param}", "DoIt", optionalMiddlewareHere...) b.Handle("GET", "/mypath/{param}", "DoIt", optionalMiddlewareHere...)
} }
func (c *ExampleController) AfterActivation(a mvc.AfterActivation)
*/ */

View File

@ -16,11 +16,11 @@ type TodoController struct {
Session *sessions.Session Session *sessions.Session
} }
// BeforeActivate called once before the server ran, and before // BeforeActivation called once before the server ran, and before
// the routes and dependency binder builded. // the routes and dependency binder builded.
// You can bind custom things to the controller, add new methods, add middleware, // You can bind custom things to the controller, add new methods, add middleware,
// add dependencies to the struct or the method(s) and more. // add dependencies to the struct or the method(s) and more.
func (c *TodoController) BeforeActivate(ca *mvc.ControllerActivator) { func (c *TodoController) BeforeActivation(ca *mvc.ControllerActivator) {
// this could be binded to a controller's function input argument // this could be binded to a controller's function input argument
// if any, or struct field if any: // if any, or struct field if any:
ca.Dependencies.Add(func(ctx iris.Context) todo.Item { ca.Dependencies.Add(func(ctx iris.Context) todo.Item {

View File

@ -821,6 +821,8 @@ type Context interface {
String() string String() string
} }
var _ Context = (*context)(nil)
// Next calls all the next handler from the handlers chain, // Next calls all the next handler from the handlers chain,
// it should be used inside a middleware. // it should be used inside a middleware.
func Next(ctx Context) { func Next(ctx Context) {

View File

@ -83,7 +83,7 @@ type GzipResponseWriter struct {
disabled bool disabled bool
} }
var _ ResponseWriter = &GzipResponseWriter{} var _ ResponseWriter = (*GzipResponseWriter)(nil)
// BeginGzipResponse accepts a ResponseWriter // BeginGzipResponse accepts a ResponseWriter
// and prepares the new gzip response writer. // and prepares the new gzip response writer.

View File

@ -39,7 +39,7 @@ type ResponseRecorder struct {
headers http.Header headers http.Header
} }
var _ ResponseWriter = &ResponseRecorder{} var _ ResponseWriter = (*ResponseRecorder)(nil)
// Naive returns the simple, underline and original http.ResponseWriter // Naive returns the simple, underline and original http.ResponseWriter
// that backends this response writer. // that backends this response writer.

View File

@ -115,7 +115,7 @@ type responseWriter struct {
beforeFlush func() beforeFlush func()
} }
var _ ResponseWriter = &responseWriter{} var _ ResponseWriter = (*responseWriter)(nil)
const ( const (
defaultStatusCode = http.StatusOK defaultStatusCode = http.StatusOK

View File

@ -114,7 +114,7 @@ m.Controller(sub, &myController{service: myService})
``` ```
```go ```go
NewEngine().Controller(sub.Party("/subsub"), new(myController), func(ca *ControllerActivator) { NewEngine().Controller(sub.Party("/subsub"), new(myController), func(b mvc.BeforeActivation) {
ca.Dependencies.Add(myService) b.Dependencies().Add(myService)
}) })
``` ```

View File

@ -21,29 +21,50 @@ type BaseController interface {
EndRequest(context.Context) EndRequest(context.Context)
} }
type shared interface {
Name() string
Router() router.Party
Handle(method, path, funcName string, middleware ...context.Handler) *router.Route
}
type BeforeActivation interface {
shared
Dependencies() *di.Values
}
type AfterActivation interface {
shared
DependenciesReadOnly() di.ValuesReadOnly
IsRequestScoped() bool
}
var (
_ BeforeActivation = (*ControllerActivator)(nil)
_ AfterActivation = (*ControllerActivator)(nil)
)
// ControllerActivator returns a new controller type info description. // ControllerActivator returns a new controller type info description.
// Its functionality can be overriden by the end-dev. // Its functionality can be overriden by the end-dev.
type ControllerActivator struct { type ControllerActivator struct {
// the router is used on the `Activate` and can be used by end-dev on the `BeforeActivate` // the router is used on the `Activate` and can be used by end-dev on the `BeforeActivation`
// to register any custom controller's functions as handlers but we will need it here // to register any custom controller's methods as handlers.
// in order to not create a new type like `ActivationPayload` for the `BeforeActivate`. router router.Party
Router router.Party
// initRef BaseController // the BaseController as it's passed from the end-dev. // initRef BaseController // the BaseController as it's passed from the end-dev.
Value reflect.Value // the BaseController's Value. Value reflect.Value // the BaseController's Value.
Type reflect.Type // raw type of the BaseController (initRef). Type reflect.Type // raw type of the BaseController (initRef).
// FullName it's the last package path segment + "." + the Name. // FullName it's the last package path segment + "." + the Name.
// i.e: if login-example/user/controller.go, the FullName is "user.Controller". // i.e: if login-example/user/controller.go, the FullName is "user.Controller".
FullName string fullName string
// the methods names that is already binded to a handler, // the methods names that is already binded to a handler,
// the BeginRequest, EndRequest and BeforeActivate are reserved by the internal implementation. // the BeginRequest, EndRequest and BeforeActivation are reserved by the internal implementation.
reservedMethods []string reservedMethods []string
// the bindings that comes from the Engine and the controller's filled fields if any. // the bindings that comes from the Engine and the controller's filled fields if any.
// Can be binded to the the new controller's fields and method that is fired // Can be binded to the the new controller's fields and method that is fired
// on incoming requests. // on incoming requests.
Dependencies *di.D dependencies di.Values
// on activate. // on activate.
injector *di.StructInjector injector *di.StructInjector
@ -59,7 +80,7 @@ func getNameOf(typ reflect.Type) string {
return fullname return fullname
} }
func newControllerActivator(router router.Party, controller interface{}, d *di.D) *ControllerActivator { func newControllerActivator(router router.Party, controller interface{}, dependencies di.Values) *ControllerActivator {
var ( var (
val = reflect.ValueOf(controller) val = reflect.ValueOf(controller)
typ = val.Type() typ = val.Type()
@ -68,13 +89,17 @@ func newControllerActivator(router router.Party, controller interface{}, d *di.D
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.
Router: router, router: router,
Value: val, Value: val,
Type: typ, Type: typ,
FullName: fullName, fullName: fullName,
// set some methods that end-dev cann't use accidentally // set some methods that end-dev cann't use accidentally
// to register a route via the `Handle`, // to register a route via the `Handle`,
// all available exported and compatible methods // all available exported and compatible methods
@ -85,27 +110,14 @@ func newControllerActivator(router router.Party, controller interface{}, d *di.D
// TODO: now that BaseController is totally optionally // TODO: now that BaseController is totally optionally
// we have to check if BeginRequest and EndRequest should be here. // we have to check if BeginRequest and EndRequest should be here.
reservedMethods: whatReservedMethods(typ), reservedMethods: whatReservedMethods(typ),
Dependencies: d, dependencies: dependencies,
}
filledFieldValues := di.LookupNonZeroFieldsValues(val)
c.Dependencies.AddValue(filledFieldValues...)
if len(filledFieldValues) == di.IndirectType(typ).NumField() {
// all fields are filled by the end-developer,
// the controller doesn't contain any other field, not 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
// creating new controller value on each incoming request.
c.injector = &di.StructInjector{Valid: false}
} }
return c return c
} }
func whatReservedMethods(typ reflect.Type) []string { func whatReservedMethods(typ reflect.Type) []string {
methods := []string{"BeforeActivate"} methods := []string{"BeforeActivation", "AfterActivation"}
if isBaseController(typ) { if isBaseController(typ) {
methods = append(methods, "BeginRequest", "EndRequest") methods = append(methods, "BeginRequest", "EndRequest")
} }
@ -113,6 +125,22 @@ func whatReservedMethods(typ reflect.Type) []string {
return methods return methods
} }
func (c *ControllerActivator) Dependencies() *di.Values {
return &c.dependencies
}
func (c *ControllerActivator) DependenciesReadOnly() di.ValuesReadOnly {
return c.dependencies
}
func (c *ControllerActivator) Name() string {
return c.fullName
}
func (c *ControllerActivator) Router() router.Party {
return c.router
}
// IsRequestScoped returns new if each request has its own instance // IsRequestScoped returns new if each request has its own instance
// of the controller and it contains dependencies that are not manually // of the controller and it contains dependencies that are not manually
// filled by the struct initialization from the caller. // filled by the struct initialization from the caller.
@ -150,8 +178,8 @@ func (c *ControllerActivator) parseMethod(m reflect.Method) {
httpMethod, httpPath, err := parseMethod(m, c.isReservedMethod) httpMethod, httpPath, err := parseMethod(m, c.isReservedMethod)
if err != nil { if err != nil {
if err != errSkip { if err != errSkip {
err = fmt.Errorf("MVC: fail to parse the route path and HTTP method for '%s.%s': %v", c.FullName, m.Name, err) err = fmt.Errorf("MVC: fail to parse the route path and HTTP method for '%s.%s': %v", c.fullName, m.Name, err)
c.Router.GetReporter().AddErr(err) c.router.GetReporter().AddErr(err)
} }
return return
@ -197,16 +225,16 @@ func (c *ControllerActivator) Handle(method, path, funcName string, middleware .
m, ok := c.Type.MethodByName(funcName) m, ok := c.Type.MethodByName(funcName)
if !ok { if !ok {
err := fmt.Errorf("MVC: function '%s' doesn't exist inside the '%s' controller", err := fmt.Errorf("MVC: function '%s' doesn't exist inside the '%s' controller",
funcName, c.FullName) funcName, c.fullName)
c.Router.GetReporter().AddErr(err) c.router.GetReporter().AddErr(err)
return nil return nil
} }
// parse a route template which contains the parameters organised. // parse a route template which contains the parameters organised.
tmpl, err := macro.Parse(path, c.Router.Macros()) tmpl, err := macro.Parse(path, c.router.Macros())
if err != nil { if err != nil {
err = fmt.Errorf("MVC: fail to parse the path for '%s.%s': %v", c.FullName, funcName, err) err = fmt.Errorf("MVC: fail to parse the path for '%s.%s': %v", c.fullName, funcName, err)
c.Router.GetReporter().AddErr(err) c.router.GetReporter().AddErr(err)
return nil return nil
} }
@ -222,11 +250,11 @@ func (c *ControllerActivator) Handle(method, path, funcName string, middleware .
// 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.AddValue(pathParams...)
// fmt.Printf("for %s | values: %s\n", funcName, funcDependencies.Values) // fmt.Printf("for %s | values: %s\n", funcName, funcDependencies)
funcInjector := funcDependencies.Func(m.Func) funcInjector := di.MakeFuncInjector(m.Func, hijacker, typeChecker, funcDependencies...)
// fmt.Printf("actual injector's inputs length: %d\n", funcInjector.Length) // fmt.Printf("actual injector's inputs length: %d\n", funcInjector.Length)
// the element value, not the pointer, wil lbe used to create a // the element value, not the pointer, wil lbe used to create a
@ -237,7 +265,7 @@ func (c *ControllerActivator) Handle(method, path, funcName string, middleware .
// hasStructInjector = c.injector != nil && c.injector.Valid // hasStructInjector = c.injector != nil && c.injector.Valid
// hasFuncInjector = funcInjector != nil && funcInjector.Valid // hasFuncInjector = funcInjector != nil && funcInjector.Valid
// because // because
// the `Handle` can be called from `BeforeActivate` callbacks // the `Handle` can be called from `BeforeActivation` callbacks
// and before activation, the c.injector is nil because // and before activation, the c.injector is nil because
// we may not have the dependencies binded yet. But if `c.injector.Valid` // 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. // inside the Handelr works because it's set on the `activate()` method.
@ -246,24 +274,48 @@ func (c *ControllerActivator) Handle(method, path, funcName string, middleware .
// so the user should bind the dependencies needed before the `Handle` // so the user should bind the dependencies needed before the `Handle`
// this is a logical flow, so we will choose that one -> // this is a logical flow, so we will choose that one ->
if c.injector == nil { if c.injector == nil {
c.injector = c.Dependencies.Struct(c.Value) // 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 { if c.injector.Valid {
golog.Debugf("MVC dependencies of '%s':\n%s", c.FullName, c.injector.String()) 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 { if funcInjector.Valid {
golog.Debugf("MVC dependencies of method '%s.%s':\n%s", c.FullName, funcName, funcInjector.String()) 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) 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)...)
if route != nil { if route != nil {
// 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)
} }
return route return route

View File

@ -17,11 +17,11 @@ type testControllerHandle struct {
reqField string reqField string
} }
func (c *testControllerHandle) BeforeActivate(ca *ControllerActivator) { // BeforeActivate(t *mvc.TController) { func (c *testControllerHandle) BeforeActivation(b BeforeActivation) { // BeforeActivation(t *mvc.TController) {
ca.Handle("GET", "/histatic", "HiStatic") b.Handle("GET", "/histatic", "HiStatic")
ca.Handle("GET", "/hiservice", "HiService") b.Handle("GET", "/hiservice", "HiService")
ca.Handle("GET", "/hiparam/{ps:string}", "HiParamBy") b.Handle("GET", "/hiparam/{ps:string}", "HiParamBy")
ca.Handle("GET", "/hiparamempyinput/{ps:string}", "HiParamEmptyInputBy") b.Handle("GET", "/hiparamempyinput/{ps:string}", "HiParamEmptyInputBy")
} }
func (c *testControllerHandle) BeginRequest(ctx iris.Context) { func (c *testControllerHandle) BeginRequest(ctx iris.Context) {

View File

@ -275,7 +275,7 @@ func (t *testControllerBindDeep) Get() {
t.Ctx.Writef(t.TitlePointer.title + t.TitleValue.title + t.Other) t.Ctx.Writef(t.TitlePointer.title + t.TitleValue.title + t.Other)
} }
func TestControllerBind(t *testing.T) { func TestControllerDependencies(t *testing.T) {
app := iris.New() app := iris.New()
// app.Logger().SetLevel("debug") // app.Logger().SetLevel("debug")
@ -421,8 +421,8 @@ type testControllerActivateListener struct {
TitlePointer *testBindType TitlePointer *testBindType
} }
func (c *testControllerActivateListener) BeforeActivate(ca *ControllerActivator) { func (c *testControllerActivateListener) BeforeActivation(b BeforeActivation) {
ca.Dependencies.AddOnce(&testBindType{title: "default title"}) b.Dependencies().AddOnce(&testBindType{title: "default title"})
} }
func (c *testControllerActivateListener) Get() string { func (c *testControllerActivateListener) Get() string {
@ -454,9 +454,27 @@ func TestControllerActivateListener(t *testing.T) {
} }
type testControllerNotCreateNewDueManuallySettingAllFields struct { type testControllerNotCreateNewDueManuallySettingAllFields struct {
T *testing.T
TitlePointer *testBindType TitlePointer *testBindType
} }
func (c *testControllerNotCreateNewDueManuallySettingAllFields) AfterActivation(a AfterActivation) {
if n := a.DependenciesReadOnly().Len(); n != 2 {
c.T.Fatalf(`expecting 2 dependency, the 'T' and the 'TitlePointer' that we manually insert
and the fields total length is 2 so it will not create a new controller on each request
however the dependencies are available here
although the struct injector is being ignored when
creating the controller's handlers because we set it to invalidate state at "newControllerActivator"
-- got dependencies length: %d`, n)
}
if a.IsRequestScoped() {
c.T.Fatalf(`this controller 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`)
}
}
func (c *testControllerNotCreateNewDueManuallySettingAllFields) Get() string { func (c *testControllerNotCreateNewDueManuallySettingAllFields) Get() string {
return c.TitlePointer.title return c.TitlePointer.title
} }
@ -464,23 +482,12 @@ func (c *testControllerNotCreateNewDueManuallySettingAllFields) Get() string {
func TestControllerNotCreateNewDueManuallySettingAllFields(t *testing.T) { func TestControllerNotCreateNewDueManuallySettingAllFields(t *testing.T) {
app := iris.New() app := iris.New()
NewEngine().Controller(app, &testControllerNotCreateNewDueManuallySettingAllFields{ NewEngine().Controller(app, &testControllerNotCreateNewDueManuallySettingAllFields{
T: t,
TitlePointer: &testBindType{ TitlePointer: &testBindType{
title: "my title", title: "my title",
}, },
}, func(ca *ControllerActivator) { }, func(b BeforeActivation) {
if n := len(ca.Dependencies.Values); n != 1 {
t.Fatalf(`expecting 1 dependency, the 'TitlePointer' which we manually insert
and the fields length is 1 so it will not create a new controller on each request
however the dependencies are available here
although the struct injector is being ignored when
creating the controller's handlers because we set it to invalidate state at "newControllerActivator"
-- got dependencies length: %d`, n)
}
if ca.IsRequestScoped() {
t.Fatalf(`this controller 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`)
}
}) })
e := httptest.New(t, app) e := httptest.New(t, app)

View File

@ -44,19 +44,11 @@ func (d *D) GoodFunc(fn TypeChecker) *D {
// Clone returns a new Dependency Injection container, it adopts the // Clone returns a new Dependency Injection container, it adopts the
// parent's (current "D") hijacker, good func type checker and all dependencies values. // parent's (current "D") hijacker, good func type checker and all dependencies values.
func (d *D) Clone() *D { func (d *D) Clone() *D {
clone := New() return &D{
clone.hijacker = d.hijacker Values: d.Values.Clone(),
clone.goodFunc = d.goodFunc hijacker: d.hijacker,
goodFunc: d.goodFunc,
// copy the current dynamic bindings (func binders)
// and static struct bindings (services) to this new child.
if n := len(d.Values); n > 0 {
values := make(Values, n, n)
copy(values, d.Values)
clone.Values = values
} }
return clone
} }
// Struct is being used to return a new injector based on // Struct is being used to return a new injector based on

View File

@ -125,6 +125,12 @@ type field struct {
AnyValue reflect.Value AnyValue reflect.Value
} }
// 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.
func NumFields(elemTyp reflect.Type) int {
return len(lookupFields(elemTyp, nil))
}
func lookupFields(elemTyp reflect.Type, parentIndex []int) (fields []field) { func lookupFields(elemTyp reflect.Type, parentIndex []int) (fields []field) {
if elemTyp.Kind() != reflect.Struct { if elemTyp.Kind() != reflect.Struct {
return return

View File

@ -97,6 +97,17 @@ func (s *StructInjector) InjectElem(destElem reflect.Value, ctx ...reflect.Value
} }
} }
func (s *StructInjector) InjectElemStaticOnly(destElem reflect.Value) (n int) {
for _, f := range s.fields {
if f.Object.BindType != Static {
continue
}
destElem.FieldByIndex(f.FieldIndex).Set(f.Object.Value)
n++
}
return
}
func (s *StructInjector) New(ctx ...reflect.Value) reflect.Value { func (s *StructInjector) New(ctx ...reflect.Value) reflect.Value {
dest := reflect.New(s.elemType) dest := reflect.New(s.elemType)
s.InjectElem(dest, ctx...) s.InjectElem(dest, ctx...)

View File

@ -2,12 +2,35 @@ package di
import "reflect" import "reflect"
type ValuesReadOnly interface {
// Has returns true if a binder responsible to
// bind and return a type of "typ" is already registered to this controller.
Has(value interface{}) bool
// Len returns the length of the values.
Len() int
}
type Values []reflect.Value type Values []reflect.Value
func NewValues() Values { func NewValues() Values {
return Values{} return Values{}
} }
// Clone returns a copy of the current values.
func (bv Values) Clone() Values {
if n := len(bv); n > 0 {
values := make(Values, n, n)
copy(values, bv)
return values
}
return NewValues()
}
func (bv Values) Len() int {
return len(bv)
}
// Add binds values to this controller, if you want to share // Add binds values to this controller, if you want to share
// binding values between controllers use the Engine's `Bind` function instead. // binding values between controllers use the Engine's `Bind` function instead.
func (bv *Values) Add(values ...interface{}) { func (bv *Values) Add(values ...interface{}) {
@ -57,13 +80,12 @@ func (bv *Values) remove(typ reflect.Type, n int) (ok bool) {
// Has returns true if a binder responsible to // Has returns true if a binder responsible to
// bind and return a type of "typ" is already registered to this controller. // bind and return a type of "typ" is already registered to this controller.
func (bv *Values) Has(value interface{}) bool { func (bv Values) Has(value interface{}) bool {
return bv.valueTypeExists(reflect.TypeOf(value)) return bv.valueTypeExists(reflect.TypeOf(value))
} }
func (bv *Values) valueTypeExists(typ reflect.Type) bool { func (bv Values) valueTypeExists(typ reflect.Type) bool {
input := *bv for _, in := range bv {
for _, in := range input {
if equalTypes(in.Type(), typ) { if equalTypes(in.Type(), typ) {
return true return true
} }

View File

@ -20,7 +20,7 @@ import (
// //
// For a more high-level structure please take a look at the "mvc.go#Application". // For a more high-level structure please take a look at the "mvc.go#Application".
type Engine struct { type Engine struct {
Dependencies *di.D Dependencies di.Values
} }
// NewEngine returns a new engine, a container for dependencies and a factory // NewEngine returns a new engine, a container for dependencies and a factory
@ -28,7 +28,7 @@ type Engine struct {
// Please take a look at the structure's documentation for more information. // Please take a look at the structure's documentation for more information.
func NewEngine() *Engine { func NewEngine() *Engine {
return &Engine{ return &Engine{
Dependencies: di.New().Hijack(hijacker).GoodFunc(typeChecker), Dependencies: di.NewValues(),
} }
} }
@ -46,7 +46,7 @@ func (e *Engine) Clone() *Engine {
// It returns a standard `iris/context.Handler` which can be used anywhere in an Iris Application, // It returns a standard `iris/context.Handler` which can be used anywhere in an Iris Application,
// as middleware or as simple route handler or subdomain's handler. // as middleware or as simple route handler or subdomain's handler.
func (e *Engine) Handler(handler interface{}) context.Handler { func (e *Engine) Handler(handler interface{}) context.Handler {
h, err := MakeHandler(handler, e.Dependencies.Values...) h, err := MakeHandler(handler, e.Dependencies.Clone()...)
if err != nil { if err != nil {
golog.Errorf("mvc handler: %v", err) golog.Errorf("mvc handler: %v", err)
} }
@ -84,8 +84,8 @@ func (e *Engine) Handler(handler interface{}) context.Handler {
// where Get is an HTTP Method func. // where Get is an HTTP Method func.
// //
// 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(*ControllerActivator)) { func (e *Engine) Controller(router router.Party, controller interface{}, beforeActivate ...func(BeforeActivation)) {
ca := newControllerActivator(router, controller, e.Dependencies) ca := newControllerActivator(router, controller, e.Dependencies.Clone())
// give a priority to the "beforeActivate" // give a priority to the "beforeActivate"
// callbacks, if any. // callbacks, if any.
@ -93,13 +93,19 @@ func (e *Engine) Controller(router router.Party, controller interface{}, beforeA
cb(ca) cb(ca)
} }
// check if controller has an "BeforeActivate" function // check if controller has an "BeforeActivation" function
// which accepts the controller activator and call it. // which accepts the controller activator and call it.
if activateListener, ok := controller.(interface { if activateListener, ok := controller.(interface {
BeforeActivate(*ControllerActivator) BeforeActivation(BeforeActivation)
}); ok { }); ok {
activateListener.BeforeActivate(ca) activateListener.BeforeActivation(ca)
} }
ca.activate() ca.activate()
if afterActivateListener, ok := controller.(interface {
AfterActivation(AfterActivation)
}); ok {
afterActivateListener.AfterActivation(ca)
}
} }

View File

@ -264,8 +264,8 @@ func (t *testControllerViewResultRespectCtxViewData) Get() Result {
func TestControllerViewResultRespectCtxViewData(t *testing.T) { func TestControllerViewResultRespectCtxViewData(t *testing.T) {
app := iris.New() app := iris.New()
NewEngine().Controller(app, new(testControllerViewResultRespectCtxViewData), func(ca *ControllerActivator) { NewEngine().Controller(app, new(testControllerViewResultRespectCtxViewData), func(b BeforeActivation) {
ca.Dependencies.Add(t) b.Dependencies().Add(t)
}) })
e := httptest.New(t, app) e := httptest.New(t, app)

View File

@ -69,6 +69,16 @@ type TodoController struct {
Session *sessions.Session Session *sessions.Session
} }
func (c *TodoController) BeforeActivation(b mvc.BeforeActivation) {
b.Handle("GET", "/custom", "Custom")
}
func (c *TodoController) AfterActivation(b mvc.BeforeActivation) {
if !b.IsRequestScoped() {
panic("TodoController should be request scoped, we have a 'Session' which depends on the context.")
}
}
func (c *TodoController) Get() string { func (c *TodoController) Get() string {
count := c.Session.Increment("count", 1) count := c.Session.Increment("count", 1)
@ -77,6 +87,10 @@ func (c *TodoController) Get() string {
return body return body
} }
func (c *TodoController) Custom() string {
return "custom"
}
type TodoSubController struct { type TodoSubController struct {
Session *sessions.Session Session *sessions.Session
} }

View File

@ -25,13 +25,13 @@ func newApp(engine *Engine, subRouter router.Party) *Application {
} }
} }
// New returns a new mvc Application based on a "subRouter". // New returns a new mvc Application based on a "party".
// Application creates a new engine which is responsible for binding the dependencies // Application creates a new engine which is responsible for binding the dependencies
// and creating and activating the app's controller(s). // and creating and activating the app's controller(s).
// //
// Example: `New(app.Party("/todo"))`. // Example: `New(app.Party("/todo"))` or `New(app)` as it's the same as `New(app.Party("/"))`.
func New(subRouter router.Party) *Application { func New(party router.Party) *Application {
return newApp(NewEngine(), subRouter) return newApp(NewEngine(), party)
} }
// Configure can be used to pass one or more functions that accept this // Configure can be used to pass one or more functions that accept this
@ -53,7 +53,7 @@ func (app *Application) Configure(configurators ...func(*Application)) *Applicat
// controller's methods, if matching. // controller's methods, if matching.
// //
// The dependencies can be changed per-controller as well via a `beforeActivate` // The dependencies can be changed per-controller as well via a `beforeActivate`
// on the `Register` method or when the controller has the `BeforeActivate(c *ControllerActivator)` // on the `Register` method or when the controller has the `BeforeActivation(c *ControllerActivator)`
// method defined. // method defined.
// //
// It returns this Application. // It returns this Application.
@ -75,16 +75,17 @@ func (app *Application) AddDependencies(values ...interface{}) *Application {
// It returns this Application. // It returns this Application.
// //
// Example: `.Register(new(TodoController))`. // Example: `.Register(new(TodoController))`.
func (app *Application) Register(controller interface{}, beforeActivate ...func(*ControllerActivator)) *Application { func (app *Application) Register(controller interface{}, beforeActivate ...func(BeforeActivation)) *Application {
app.Engine.Controller(app.Router, controller, beforeActivate...) app.Engine.Controller(app.Router, controller, beforeActivate...)
return app return app
} }
// NewChild creates and returns a new Application which will be adapted // NewChild creates and returns a new MVC Application which will be adapted
// to the "subRouter", it adopts // to the "party", it adopts
// the dependencies bindings from the parent(current) one. // the parent's (current) dependencies, the "party" may be
// a totally new router or a child path one via the parent's `.Router.Party`.
// //
// Example: `.NewChild(irisApp.Party("/sub")).Register(new(TodoSubController))`. // Example: `.NewChild(irisApp.Party("/path")).Register(new(TodoSubController))`.
func (app *Application) NewChild(subRouter router.Party) *Application { func (app *Application) NewChild(party router.Party) *Application {
return newApp(app.Engine.Clone(), subRouter) return newApp(app.Engine.Clone(), party)
} }

View File

@ -15,13 +15,13 @@ type SessionController struct {
Session *sessions.Session Session *sessions.Session
} }
// BeforeActivate called, once per application lifecycle NOT request, // BeforeActivation called, once per application lifecycle NOT request,
// every single time the dev registers a specific SessionController-based controller. // every single time the dev registers a specific SessionController-based controller.
// It makes sure that its "Manager" field is filled // It makes sure that its "Manager" field is filled
// even if the caller didn't provide any sessions manager via the `app.Controller` function. // even if the caller didn't provide any sessions manager via the `app.Controller` function.
func (s *SessionController) BeforeActivate(ca *ControllerActivator) { func (s *SessionController) BeforeActivation(b BeforeActivation) {
if didntBindManually := ca.Dependencies.AddOnce(defaultSessionManager); didntBindManually { if didntBindManually := b.Dependencies().AddOnce(defaultSessionManager); didntBindManually {
ca.Router.GetReporter().Add( b.Router().GetReporter().Add(
`MVC SessionController: couldn't find any "*sessions.Sessions" bindable value to fill the "Manager" field, `MVC SessionController: couldn't find any "*sessions.Sessions" bindable value to fill the "Manager" field,
therefore this controller is using the default sessions manager instead. therefore this controller is using the default sessions manager instead.
Please refer to the documentation to learn how you can provide the session manager`) Please refer to the documentation to learn how you can provide the session manager`)