About the inject
In fact, Inject itself is a very early library, but it has been very popular in the GO runtime DI library, and recently it has also used Inject to improve the efficiency of gin development.
While I have time, I’ll take a look at this small library of less than 700 lines of code.
The whole process
Depend on the node
Inject abstract every node injected into an Object structure, in fact we only need to provide the actual Value of this Object, other attributes are handled internally.
object := inject.Object{value:&obj}
Copy the code
Dependency graph building
In inject, we only need to inject nodes of root node, other nodes can be derived by type.
It is important to note that the auto-built instance does not have any parameters, so if our type needs to initialize, it cannot be handled by Inject. We need to manually create it and then Provide it to Graph.
Dependency graph building is not a common Topo sort, so let’s take a closer look at how it builds a dependency graph.
Depend on the preservation of information
How do dependency diagrams save dependencies
We don’t talk about topo sort here, but look at how inject stores dependent structure information in Provide
There are two types of dependencies, named and unnamed.
Let’s start with non-named dependencies, which is the most common default.
Dependency information is stored in the provide method:
func (g *Graph) Provide(objects ... *Object) error {
for _, o := range objects {
o.reflectType = reflect.TypeOf(o.Value)
o.reflectValue = reflect.ValueOf(o.Value)
ifo.Fields ! =nil {
return fmt.Errorf(
"fields were specified on object %s when it was provided",
o,
)
}
if o.Name == "" {
// A pointer type is required
if! isStructPtr(o.reflectType) {panic()}if! o.private {if g.unnamedType == nil {
g.unnamedType = make(map[reflect.Type]bool)}// Duplicate dependencies
if g.unnamedType[o.reflectType] {
panic()
}
g.unnamedType[o.reflectType] = true
}
g.unnamed = append(g.unnamed, o)
} else {
if g.named == nil {
g.named = make(map[string]*Object)
}
ifg.named[o.Name] ! =nil {
return fmt.Errorf("provided two instances named %s", o.Name)
}
g.named[o.Name] = o
}
}
return nil
}
Copy the code
Provide stores dependencies in the name and unnamedType maps for subsequent work.
Those familiar with DI know that dependencies are identified with type only and named dependencies are identified with strings directly.
It is important to note that the above private measure actually determines whether a singleton is a dependency: for those unfamiliar with DI, a singleton is whether the dependency needs to be re-instantiated when the class is created. If it is a Private dependency, then naturally no other class can depend on it, only this class.
If a dependency is not a singleton, it needs to be recreated every time it is encountered, and there is not much need to save this information.
Structure construction
The method of structuring is in populateUnnamedInterface, which is the source code
It’s going to be a very big loop with a label
Implementation is to initialize some variables
StructLoop:
for i := 0; i < o.reflectValue.Elem().NumField(); i++ {
field := o.reflectValue.Elem().Field(i)
fieldType := field.Type()
fieldTag := o.reflectType.Elem().Field(i).Tag
fieldName := o.reflectType.Elem().Field(i).Name
tag, err := parseTag(string(fieldTag))
Copy the code
Skip some of the irrelevant processing and look at a few small details:
-
Do not handle fields that have already been assigned:
if! isNilOrZero(field, fieldType) {continue } Copy the code
-
Process inline structures
// If the supplied value is a structure instead of a structure pointer, the modifier must be inline if fieldType.Kind() == reflect.Struct { // Inline cannot be used with private if tag.Private { panic()}if! tag.Inline {panic() } err := g.Provide(&Object{ Value: field.Addr().Interface(), private: true, embedded: o.reflectType.Elem().Field(i).Anonymous, }) iferr ! =nil { return err } continue } Copy the code
You can see that you manually pass the pointer to the inline structure as Value to the Object and call Provide
The interesting point here is that if the dependency is inline, that means we need to re-create a new instance of the dependency each time, so private is set to true
Here are two large branches, one for named dependencies and one for non-named dependencies:
-
Handling of named dependencies
iftag.Name ! ="" { existing := g.named[tag.Name] // The naming dependency, Panic, is not provided if existing == nil { panic()}// If the named dependency does not match the actual requirements, panic if! existing.reflectType.AssignableTo(fieldType) {panic() } field.Set(reflect.ValueOf(existing.Value)) o.addDep(fieldName, existing) continue StructLoop } Copy the code
The processing of named dependencies is actually very simple, directly from the dependency graph to look up the registered named dependencies, and then determine whether it meets the requirements, and then inject values.
-
Handling of non-named dependencies
The handling of non-named dependencies is more complicated and falls into two cases:
-
Dependency already provided
for _, existing := range g.unnamed { if existing.private { continue } if existing.reflectType.AssignableTo(fieldType) { field.Set(reflect.ValueOf(existing.Value)) o.addDep(fieldName, existing) continue StructLoop } } Copy the code
If the dependency already exists in the dependency diagram, just inject the value.
-
Dependencies are not yet provided
if! tag.Private {for _, existing := range g.unnamed { if existing.private { continue } if existing.reflectType.AssignableTo(fieldType) { field.Set(reflect.ValueOf(existing.Value)) ifg.Logger ! =nil { g.Logger.Debugf( "assigned existing %s to field %s in %s", existing, o.reflectType.Elem().Field(i).Name, o, ) } o.addDep(fieldName, existing) continue StructLoop } } } newValue := reflect.New(fieldType.Elem()) newObject := &Object{ Value: newValue.Interface(), private: tag.Private, created: true,}// Add the newly ceated object to the known set of objects. err = g.Provide(newObject) iferr ! =nil { return err } // Finally assign the newly created object to our field. field.Set(newValue) o.addDep(fieldName, newObject) Copy the code
If the dependency is not already provided, we need to create a New dependency and call Provide to inject it.
Of course, if the dependency is not private, then the cached dependency can be reused directly. Private dependencies must be New again
-
Interface to build
There is a special point to note here, inject cannot automatically build an interface dependency. This is easy to understand because the build is done with reflect.New, but an reflect.Interface obviously does not have a Type that can be New.
So the interface has to be manually provided into the Graph.
Inject a dependency interface that is already in the dependency graph.
Look at the source code, in the populateUnnamedInterface method
for i := 0; i < o.reflectValue.Elem().NumField(); i++ {
field := o.reflectValue.Elem().Field(i)
fieldType := field.Type()
fieldTag := o.reflectType.Elem().Field(i).Tag
fieldName := o.reflectType.Elem().Field(i).Name
tag, err := parseTag(string(fieldTag))
iferr ! =nil {
return fmt.Errorf(
"unexpected tag format `%s` for field %s in type %s".string(fieldTag),
o.reflectType.Elem().Field(i).Name,
o.reflectType,
)
}
// Handle only interfaces
iffieldType.Kind() ! = reflect.Interface {continue
}
// Do not handle explicitly assigned fields
if! isNilOrZero(field, fieldType) {continue
}
// If it has already been processed
iftag.Name ! ="" {
panic()}// Find one, and only one assignable value for the field.
var found *Object
...
}
Copy the code
It’s easy to see here, but if it’s not Kind of reflect.Interface, it’s not handled here.
Keep looking at the source code
var found *Object
for _, existing := range g.unnamed {
if existing.private {
continue
}
if existing.reflectType.AssignableTo(fieldType) {
Inject cannot be selected if multiple dependencies are provided that implement it
iffound ! =nil {
panic()
}
found = existing
/ / assignment
field.Set(reflect.ValueOf(existing.Value))
// Add the dependency graph
o.addDep(fieldName, existing)
}
}
Copy the code
The key code is in the for loop above. It does several things:
- Iterate over all dependencies
- Find if the interface for the current field can be implemented by the dependency
- Ensure that there is one and only one implementation of this interface in the entire dependency diagram
Typeof((*interface)(nil)).elem ())), but inject does not get the interface type ahead of time, so it cannot rely on pointer changes to nil to determine.
So inject determine whether can implement this interface, USES is existing. ReflectType. AssignableTo (fieldType).
Finally, it becomes clear that if one and only one of the provided dependencies implements the interface, assign the field to that dependency and add it to the dependency graph.
Note that if it is a private dependency, it needs to be re-instantiated each time, instead of using the singleton pattern.
Private processing
The handling of private dependencies was actually mentioned in the construction time above
if ! tag.Private { for _, existing := range g.unnamed { if existing.private { continue } if existing.reflectType.AssignableTo(fieldType) { field.Set(reflect.ValueOf(existing.Value)) if g.Logger ! = nil { g.Logger.Debugf( "assigned existing %s to field %s in %s", existing, o.reflectType.Elem().Field(i).Name, o, ) } o.addDep(fieldName, existing) continue StructLoop } } } else { new ... }Copy the code
The above code makes it clear that the cache can be removed if the dependency is not private, and recreated if the cache is not there.
Private dependencies must be new one instance at a time
conclusion
As we can see from the source code analysis above, the dependency graph of Inject is very interesting. It is not like analyzing code to get the dependency directly and then do topo sort. Instead, we get a map of dependencies by providing them at runtime, traverse the map to see if the dependencies exist, and then set them directly.