Golang:martini's Inject source analysis

Source: Internet
Author: User
Tags uppercase letter
This is a creation in Article, where the information may have evolved or changed.

Dependency Injection (Dependency injection) and control inversion (inversion of controls) are the same concept. In the traditional process of program design, the caller is the one who decides which callee to use to implement. In the dependency injection pattern, however, the work of creating the callee is no longer done by the caller, so called control inversion, and the work of creating the callee instance is usually done by the injector, which is then injected into the caller, and therefore also referred to as dependency injection.

Inject is a dependency injection of the Golang implementation, the author is Codegangsta. It can inject parameters at run time and invoke methods. is the foundation core of the Martini framework.

I extracted the following 2-point properties for Dependency injection:

    1. Injected by the Injector property.

    2. The callee instance is created by the injector.

In inject, the callee is the Func, so the injected attribute also injects the arguments to the Func (of course inject can also inject the struct, so that the injected attribute is the exported field in the struct that has the tag ' inject ' added). Let's look at the normal function call:

Package Mainimport ("FMT") func Say (name, gender string, age int) {FMT. Printf ("My name is%s, gender is%s,%d!\n", name, gender, age)}func main () {Say ("Chen Yi Hui", "male", 20)}

In the above example, the function say is defined and called manually in the Main method. This is always possible, but sometimes we have to face a situation where, for example, in web development, we register the route, the server accepts the request, and then calls the corresponding handler based on the request path. This handler must not be called manually by us, but rather by the server side to find the corresponding handler and call automatically based on the route matching.

It's time to introduce inject and try to rewrite the above code with inject:

Package Mainimport ("FMT" "Github.com/codegangsta/inject") type specialstring interface{}func Say (name string, gender specialstring, age int) {FMT. Printf ("My name is%s, gender is%s,%d!\n", name, gender, age)}func main () {inj: = inject. NEW () INJ. Map ("Chen Yi-hui") inj. Mapto ("Male", (*specialstring) (nil)) INJ. MAP (INJ). Invoke (Say)}

$ cd $GOPATH/src/injector_test$ go build$./injector_testmy name is Chen one back, gender is male, age is 20!

Can't you read it? It doesn't matter, because we have not enough knowledge of inject, all from the analysis of inject source.

Inject package only 2 files, one is the Inject.go file, and one is inject_test.go, but we only focus on inject.go files.

Inject.go short, including comments and blank lines only 157 lines. Define 4 interfaces, including a parent interface and three sub-interfaces, and you'll know the benefits of that definition.

For convenience, I removed all the comments:

Type Injector Interface {applicatorinvokertypemappersetparent (Injector)}type applicator interface {Apply (interface{} ) Error}type Invoker Interface {Invoke (interface{}) ([]reflect. Value, error)}type Typemapper interface {Map (interface{}) Typemappermapto (interface{}, interface{}) Typemapperget ( Reflect. Type) reflect. Value}

Interface Injector is the interface applicator, interface Invoker, interface Typemapper parent interface, so the type of injector interface is implemented, it is necessary to implement the applicator interface, Invoker interface and Typemapper interface.

The applicator interface only specifies the apply member, which is used to inject a struct.

The Invoker interface only specifies the invoke member, which is used to execute the callee.

The Typemapper interface specifies three members, both map and Mapto are used to inject parameters, but they have different uses. Gets the arguments that are injected when the get is used for invocation.

In addition, injector specifies the setparent behavior, which is used to set the parent injector, which is actually equivalent to finding inheritance. That is, the Get method is traced back to the parent when the injected parameter is fetched, which is a recursive process until the parameter is found or the nil terminates.

Type injector struct {values map[reflect. Type]reflect. Valueparent injector}func interfaceof (Value interface{}) reflect. Type {t: = reflect. TypeOf (value) for t.kind () = = reflect. Ptr {t = T.elem ()}if t.kind ()! = reflect. Interface {Panic ("called Inject. Interfaceof with a value, which is a pointer to an interface. (*myinterface) (nil) ")}return T}func New () Injector {return &injector{values:make (map[reflect. Type]reflect. Value),}}

Injector is the only struct defined in the inject package, and all operations are based on the injector struct. It has two members of values and parent. Values are used to save the injected parameter, which is a reflect. Type when the key, reflect. Value is a map of values, this is important and understanding this will help you understand map and Mapto. The new method initializes the injector struct and returns a pointer to the injector struct. Note, however, that this return value is wrapped by the injector interface.

Although the Interfaceof method has only a few implementations of code, it is the core of injector. The argument to the Interfaceof method must be a pointer to an interface type, and if not, the panic is raised. The return type of the Interfaceof method is Reflect.type, and you should remember that the member of Injector values is a map of the Reflect.type type when the key. The function of this method is simply to get the type of the parameter, not to care about its value. I have previously introduced an article (*interface{}) (nil), interested friends can go to see: Golang: Detailed interface and nil.

In order to deepen our understanding, let me give you an example:

Package Mainimport ("FMT" "Github.com/codegangsta/inject") type specialstring Interface{}func main () {FMT. Println (inject. Interfaceof ((*interface{}) (nil)) FMT. Println (inject. Interfaceof ((*specialstring) (nil))}

$ cd $GOPATH/src/injector_test$ go build$./injector_testinterface {}main. Specialstring

The output above is not surprising. The Interfaceof method is used to get the parameter type, without worrying about what value it is specifically storing. It is worth mentioning that we have defined a specialstring interface. We have also defined the Specialstring interface in the previous code, used in the parameter declaration of the Say method, and then you will know why. Of course you don't necessarily have to name specialstring.

Func (i *injector) Map (Val interface{}) typemapper {i.values[reflect. TypeOf (val)] = reflect. ValueOf (val) return I}func (i *injector) Mapto (Val interface{}, Ifaceptr interface{}) typemapper {i.values[interfaceof ( IFACEPTR)] = reflect. ValueOf (val) return I}func (i *injector) Get (t reflect. Type) reflect. Value {val: = I.values[t]if!val. IsValid () && i.parent! = Nil {val = i.parent.get (t)}return Val}func (i *injector) SetParent (parent injector) {I.PA Rent = parent}

Both the map and Mapto methods are used to inject parameters, which are stored in the member values of injector. The functions of the two methods are identical, the only difference being that the map method uses the type of the parameter value itself as the key, and the Mapto method has an extra parameter that can specify the specific type when the key is. However, the second parameter of the Mapto method Ifaceptr must be an interface pointer type because the final ifaceptr will be used as a parameter to the Interfaceof method.

Why do I need a Mapto method? Because the injected parameters are stored in a type-key map, it is conceivable that when a function has more than one parameter in the same type, the parameters that are injected after the map is executed will overwrite the previous parameter injected by the map.

The SetParent method is used to specify a parent injector for a injector. The Get method takes the corresponding value from the values member of the injector by Reflect.type, it may check whether the parent is set until an invalid value is found or returned, and the return value of the last get method is verified by the IsValid method. Give an example to deepen your understanding:

Package Mainimport ("FMT" "Github.com/codegangsta/inject" "reflect") type specialstring Interface{}func main () {inj: = Inject. NEW () INJ. Map ("Chen Yi-hui") inj. Mapto ("Male", (*specialstring) (nil)) INJ. Map (FMT). Println ("String is valid?", INJ. Get (reflect. TypeOf ("Surname Chen Name One time"). IsValid ()) fmt. Println ("specialstring is valid?", INJ. Get (inject. Interfaceof ((*specialstring) (nil)). IsValid ()) fmt. PRINTLN ("int is valid?", INJ. Get (reflect. TypeOf (18)). IsValid ()) fmt. Println ("[]byte is valid?", INJ. Get (reflect. TypeOf ([]byte ("Golang")). IsValid ()) Inj2: = inject. New () inj2. Map ([]byte ("Test")) INJ. SetParent (INJ2) fmt. Println ("[]byte is valid?", INJ. Get (reflect. TypeOf ([]byte ("Golang")). IsValid ())}

$ cd $GOPATH/src/injector_test$ go build$./injector_teststring is valid? Truespecialstring is valid? Trueint is valid? True[]byte is valid? False[]byte is valid? True

The above example should know what kind of behavior setparent is. Does it look like an object-oriented lookup chain?

Func (inj *injector) Invoke (F interface{}) ([]reflect. Value, error) {t: = reflect. TypeOf (f) var in = make ([]reflect. Value, T.numin ())//panic if T is not kind of funcfor I: = 0; I < t.numin (); i++ {argtype: = t.in (i) Val: = INJ. Get (Argtype) if!val. IsValid () {return nil, fmt. Errorf ("Value not found for type%v", Argtype)}in[i] = Val}return reflect. ValueOf (f). Call (in), nil}

The Invoke method is used for dynamic execution of functions, although it is possible to inject parameters via map or mapto before execution, because functions executed by invoke take out the injected parameters and then invoke them through the call method in the reflect package. The parameter F that the invoke receives is an interface type, but the underlying type of f must be func, otherwise it will be panic.

Package Mainimport ("FMT" "Github.com/codegangsta/inject") type specialstring interface{}func Say (name string, gender specialstring, age int) {FMT. Printf ("My name is%s, gender is%s,%d!\n", name, gender, age)}func main () {inj: = inject. NEW () INJ. Map ("Chen Yi-hui") inj. Mapto ("Male", (*specialstring) (nil)) Inj2: = inject. New () inj2. MAP (INJ). SetParent (INJ2) INJ. Invoke (Say)}

In the above example, if the Specialstring interface is not defined as the type of the gender parameter, and both name and gender are defined as string types, then gender overrides the value of name. If you don't understand, it is recommended that you read this article a few times from the beginning.

Func (inj *injector) Apply (Val interface{}) Error {V: = reflect. ValueOf (val) for v.kind () = = reflect. Ptr {v = v.elem ()}if v.kind ()! = reflect. Struct {return Nil}t: = V.type () for I: = 0; I < V.numfield (); i++ {f: = V.field (i) Structfield: = T.field (i) if F.canset () && Structfield.tag = = "Inject" {ft: = F.type () V: = INJ. Get (ft) if!v.isvalid () {return FMT. Errorf ("Value not found for type%v", ft)}f.set (v)}}return nil}

The Apply method is used to inject a struct's field into a pointer to a struct of the underlying type. The prerequisite for injection is that the field must be exported (that is, the field name starts with an uppercase letter), and the tag of this field is set to ' inject '. Take an example to illustrate:

Package Mainimport ("FMT" "Github.com/codegangsta/inject") type specialstring interface{}type teststruct struct {Name String ' inject ' Nick []bytegender specialstring ' inject ' uid int ' inject ' age int ' inject '}func m Ain () {s: = Teststruct{}inj: = inject. NEW () INJ. Map ("Chen Yi-hui") inj. Mapto ("Male", (*specialstring) (nil)) Inj2: = inject. New () inj2. MAP (INJ). SetParent (INJ2) INJ. Apply (&s) fmt. Println ("S.name =", S.name) fmt. Println ("S.gender =", S.gender) fmt. Println ("S.age =", S.age)}

$ cd $GOPATH/src/injector_test$ go build$./injector_tests. Name = Chen Once back s. Gender = male S. Age = 20

The criminal star wrote a blog post for reference: in Golang with the name of the call function , suggest everyone to see.

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.