golang implements the dependency injection approach in less than 30 lines of code
- 2020-06-15 09:18:13
- OfStack
This article introduces the method of implementing dependency injection in less than 30 lines of golang code and shares it with you as follows:
The project address
go-di-demo
This project depends on
Standard library implementation with no additional dependencies
The advantages of dependency injection
Anyone using java will be familiar with the spring framework 1. The spring core is an IoC(Inversion of Control/dependency injection) container, with one big advantage being decoupling. 1 generally only depends on the container, not on the specific class, when your class has changes, you need to change at most 1 container related code, business code is not affected.
The dependency injection principle of golang
In general, similar to java, the steps are as follows :(golang does not support dynamic object creation, so you need to create the object manually first and then inject it; java can create the object dynamically directly)
Read object dependencies by reflection (golang is implemented by tag) Look in the container for an instance of the object If there is an instance of the object or a factory method to create the object, inject the object or use the factory to create the object and inject it If there is no instance of the object, an error is reported
Code implementation
A typical container implementation is as follows, the dependency type refers to singleton/prototype of spring, object singleton object and instance object respectively:
package di
import (
"sync"
"reflect"
"fmt"
"strings"
"errors"
)
var (
ErrFactoryNotFound = errors.New("factory not found")
)
type factory = func() (interface{}, error)
// The container
type Container struct {
sync.Mutex
singletons map[string]interface{}
factories map[string]factory
}
// Container instantiation
func NewContainer() *Container {
return &Container{
singletons: make(map[string]interface{}),
factories: make(map[string]factory),
}
}
// Register the singleton object
func (p *Container) SetSingleton(name string, singleton interface{}) {
p.Lock()
p.singletons[name] = singleton
p.Unlock()
}
// Gets the singleton object
func (p *Container) GetSingleton(name string) interface{} {
return p.singletons[name]
}
// Get the instance object
func (p *Container) GetPrototype(name string) (interface{}, error) {
factory, ok := p.factories[name]
if !ok {
return nil, ErrFactoryNotFound
}
return factory()
}
// Set up the instance object factory
func (p *Container) SetPrototype(name string, factory factory) {
p.Lock()
p.factories[name] = factory
p.Unlock()
}
// dependent
func (p *Container) Ensure(instance interface{}) error {
elemType := reflect.TypeOf(instance).Elem()
ele := reflect.ValueOf(instance).Elem()
for i := 0; i < elemType.NumField(); i++ { // Through field
fieldType := elemType.Field(i)
tag := fieldType.Tag.Get("di") // To obtain tag
diName := p.injectName(tag)
if diName == "" {
continue
}
var (
diInstance interface{}
err error
)
if p.isSingleton(tag) {
diInstance = p.GetSingleton(diName)
}
if p.isPrototype(tag) {
diInstance, err = p.GetPrototype(diName)
}
if err != nil {
return err
}
if diInstance == nil {
return errors.New(diName + " dependency not found")
}
ele.Field(i).Set(reflect.ValueOf(diInstance))
}
return nil
}
// Gets the name of the dependency to be injected
func (p *Container) injectName(tag string) string {
tags := strings.Split(tag, ",")
if len(tags) == 0 {
return ""
}
return tags[0]
}
// Checks for singleton dependencies
func (p *Container) isSingleton(tag string) bool {
tags := strings.Split(tag, ",")
for _, name := range tags {
if name == "prototype" {
return false
}
}
return true
}
// Checks for instance dependencies
func (p *Container) isPrototype(tag string) bool {
tags := strings.Split(tag, ",")
for _, name := range tags {
if name == "prototype" {
return true
}
}
return false
}
// Print the instance inside the container
func (p *Container) String() string {
lines := make([]string, 0, len(p.singletons)+len(p.factories)+2)
lines = append(lines, "singletons:")
for name, item := range p.singletons {
line := fmt.Sprintf(" %s: %x %s", name, &item, reflect.TypeOf(item).String())
lines = append(lines, line)
}
lines = append(lines, "factories:")
for name, item := range p.factories {
line := fmt.Sprintf(" %s: %x %s", name, &item, reflect.TypeOf(item).String())
lines = append(lines, line)
}
return strings.Join(lines, "\n")
}
The most important is the Ensure method, which scans all the export fields of the instance and reads the di tag, starting the injection if it exists.
Determine the type of di tag to determine whether to inject an singleton or prototype object
test
The following is the test entry code, the complete code is in github warehouse, those who are interested can browse:
package main
import (
"di"
"database/sql"
"fmt"
"os"
_ "github.com/go-sql-driver/mysql"
"demo"
)
func main() {
container := di.NewContainer()
db, err := sql.Open("mysql", "root:root@tcp(localhost)/sampledb")
if err != nil {
fmt.Printf("error: %s\n", err.Error())
os.Exit(1)
}
container.SetSingleton("db", db)
container.SetPrototype("b", func() (interface{}, error) {
return demo.NewB(), nil
})
a := demo.NewA()
if err := container.Ensure(a); err != nil {
fmt.Println(err)
return
}
// Print the pointer to ensure that the singleton and instance pointer addresses
fmt.Printf("db: %p\ndb1: %p\nb: %p\nb1: %p\n", a.Db, a.Db1, &a.B, &a.B1)
}
After execution, the printed result is:
[
db: 0xc4200b6140
db1: 0xc4200b6140
b: 0xc4200a0330
b1: 0xc4200a0338
You can see a pointer to one of the two db instances, indicating that it is the same instance, while the two b Pointers are different, indicating that it is not an instance.
Write in the last
Dependency injection is a good way to manage the instantiation and dependencies between multiple objects. In combination with configuration files, the instances that need to be injected into the container are registered in the application initialization phase, and the container can be injected anywhere in the application at the time of instantiation. No additional dependencies.