├── go.mod ├── object.go ├── go.sum ├── container.go ├── README.md └── container_test.go /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/mix-go/xdi 2 | 3 | go 1.13 4 | 5 | require github.com/stretchr/testify v1.6.1 6 | -------------------------------------------------------------------------------- /object.go: -------------------------------------------------------------------------------- 1 | package xdi 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | ) 7 | 8 | type Object struct { 9 | Name string 10 | // 创建对象的闭包 11 | New func() (interface{}, error) 12 | // 每次都创建新的对象 13 | NewEverytime bool 14 | 15 | refresher refresher 16 | mutex sync.Mutex 17 | } 18 | 19 | func (t *Object) Refresh() error { 20 | if t.NewEverytime { 21 | return fmt.Errorf("xdi: '%s' is NewEverytime, unable to refresh", t.Name) 22 | } 23 | t.refresher.on() 24 | return nil 25 | } 26 | 27 | type refresher struct { 28 | val bool 29 | } 30 | 31 | func (t *refresher) on() { 32 | t.val = true 33 | } 34 | 35 | func (t *refresher) off() { 36 | t.val = false 37 | } 38 | 39 | func (t *refresher) status() bool { 40 | return t.val 41 | } 42 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= 2 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 3 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 4 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 5 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 6 | github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= 7 | github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 8 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 9 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 10 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= 11 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 12 | -------------------------------------------------------------------------------- /container.go: -------------------------------------------------------------------------------- 1 | package xdi 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "reflect" 7 | "sync" 8 | ) 9 | 10 | var DefaultContainer *Container 11 | 12 | func init() { 13 | DefaultContainer = New() 14 | } 15 | 16 | func New() *Container { 17 | return &Container{} 18 | } 19 | 20 | func Provide(objects ...*Object) error { 21 | return DefaultContainer.Provide(objects...) 22 | } 23 | 24 | func Populate(name string, ptr interface{}) error { 25 | return DefaultContainer.Populate(name, ptr) 26 | } 27 | 28 | type Container struct { 29 | Objects []*Object 30 | tidyObjects sync.Map 31 | instances sync.Map 32 | } 33 | 34 | func (t *Container) Provide(objects ...*Object) error { 35 | for _, o := range objects { 36 | if _, ok := t.tidyObjects.Load(o.Name); ok { 37 | return fmt.Errorf("xdi: object '%s' existing", o.Name) 38 | } 39 | t.tidyObjects.Store(o.Name, o) 40 | } 41 | return nil 42 | } 43 | 44 | func (t *Container) Object(name string) (*Object, error) { 45 | v, ok := t.tidyObjects.Load(name) 46 | if !ok { 47 | return nil, fmt.Errorf("xdi: object '%s' not found", name) 48 | } 49 | obj := v.(*Object) 50 | return obj, nil 51 | } 52 | 53 | func (t *Container) Populate(name string, ptr interface{}) (err error) { 54 | defer func() { 55 | if e := recover(); e != nil { 56 | err = errors.New("xdi: " + fmt.Sprint(e)) 57 | } 58 | }() 59 | 60 | obj, err := t.Object(name) 61 | if err != nil { 62 | return err 63 | } 64 | 65 | if reflect.ValueOf(ptr).Kind() != reflect.Ptr { 66 | return errors.New("xdi: argument can only be pointer type") 67 | } 68 | 69 | ptrCopy := func(ptr, newValue interface{}) { 70 | v := reflect.ValueOf(ptr) 71 | if v.Kind() == reflect.Ptr { 72 | v = v.Elem() 73 | } 74 | v.Set(reflect.ValueOf(newValue)) 75 | } 76 | 77 | if !obj.NewEverytime { 78 | refresher := &obj.refresher 79 | if p, ok := t.instances.Load(name); ok && !refresher.status() { 80 | ptrCopy(ptr, p) 81 | return nil 82 | } 83 | 84 | // 处理并发穿透 85 | // 之前是采用 LoadOrStore 重复创建但只保存一个 86 | // 现在采用 Mutex 直接锁死只创建一次 87 | obj.mutex.Lock() 88 | defer obj.mutex.Unlock() 89 | if p, ok := t.instances.Load(name); ok && !refresher.status() { 90 | ptrCopy(ptr, p) 91 | return nil 92 | } 93 | 94 | v, err := obj.New() 95 | if err != nil { 96 | return err 97 | } 98 | t.instances.Store(name, v) 99 | refresher.off() 100 | ptrCopy(ptr, v) 101 | return nil 102 | } else { 103 | v, err := obj.New() 104 | if err != nil { 105 | return err 106 | } 107 | ptrCopy(ptr, v) 108 | return nil 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | > Produced by OpenMix: [https://openmix.org](https://openmix.org/mix-go) 2 | 3 | ## Mix XDI 4 | 5 | DI, IoC container 6 | 7 | ## Overview 8 | 9 | A library for creating objects and managing their dependencies. This library can be used for managing dependencies in a unified way, managing global objects, and refreshing dynamic configurations. 10 | 11 | ## Installation 12 | 13 | ``` 14 | go get github.com/mix-go/xdi 15 | ``` 16 | 17 | ## Quick start 18 | 19 | Create a singleton through dependency configuration 20 | 21 | ```go 22 | package main 23 | 24 | import ( 25 | "github.com/mix-go/xdi" 26 | ) 27 | 28 | type Foo struct { 29 | } 30 | 31 | func init() { 32 | obj := &xdi.Object{ 33 | Name: "foo", 34 | New: func() (interface{}, error) { 35 | i := &Foo{} 36 | return i, nil 37 | }, 38 | } 39 | if err := xdi.Provide(obj); err != nil { 40 | panic(err) 41 | } 42 | } 43 | 44 | func main() { 45 | var foo *Foo 46 | if err := xdi.Populate("foo", &foo); err != nil { 47 | panic(err) 48 | } 49 | // use foo 50 | } 51 | ``` 52 | 53 | ## Reference 54 | 55 | Refer to another dependency configuration instance in the dependency configuration 56 | 57 | ```go 58 | package main 59 | 60 | import ( 61 | "github.com/mix-go/xdi" 62 | ) 63 | 64 | type Foo struct { 65 | Bar *Bar 66 | } 67 | 68 | type Bar struct { 69 | } 70 | 71 | func init() { 72 | objs := []*xdi.Object{ 73 | { 74 | Name: "foo", 75 | New: func() (interface{}, error) { 76 | // reference bar 77 | var bar *Bar 78 | if err := xdi.Populate("bar", &bar); err != nil { 79 | return nil, err 80 | } 81 | 82 | i := &Foo{ 83 | Bar: bar, 84 | } 85 | return i, nil 86 | }, 87 | }, 88 | { 89 | Name: "bar", 90 | New: func() (interface{}, error) { 91 | i := &Bar{} 92 | return i, nil 93 | }, 94 | NewEverytime: true, 95 | }, 96 | } 97 | if err := xdi.Provide(objs...); err != nil { 98 | panic(err) 99 | } 100 | } 101 | 102 | func main() { 103 | var foo *Foo 104 | if err := xdi.Populate("foo", &foo); err != nil { 105 | panic(err) 106 | } 107 | // use foo 108 | } 109 | ``` 110 | 111 | ## Refresh singleton 112 | 113 | When the configuration information changes during program execution, `Refresh()` can refresh the singleton instance to switch to using the new configuration. It is commonly used in microservice configuration centers. 114 | 115 | ```go 116 | obj, err := xdi.DefaultContainer.Object("foo") 117 | if err != nil { 118 | panic(err) 119 | } 120 | if err := obj.Refresh(); err != nil { 121 | panic(err) 122 | } 123 | ``` 124 | 125 | ## License 126 | 127 | Apache License Version 2.0, http://www.apache.org/licenses/ 128 | -------------------------------------------------------------------------------- /container_test.go: -------------------------------------------------------------------------------- 1 | package xdi 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "github.com/stretchr/testify/assert" 7 | "net/http" 8 | "sync" 9 | "testing" 10 | "time" 11 | ) 12 | 13 | type foo struct { 14 | Bar string 15 | Client *http.Client 16 | } 17 | 18 | func TestPopulate(t *testing.T) { 19 | a := assert.New(t) 20 | 21 | c := New() 22 | objs := []*Object{ 23 | { 24 | Name: "client", 25 | New: func() (i interface{}, e error) { 26 | timeout := time.Second * 10 27 | return &http.Client{ 28 | Timeout: timeout, 29 | }, nil 30 | }, 31 | }, 32 | { 33 | Name: "foo", 34 | New: func() (i interface{}, e error) { 35 | var hc *http.Client 36 | if err := c.Populate("client", &hc); err != nil { 37 | panic(err) 38 | } 39 | return &foo{ 40 | Bar: "", 41 | Client: hc, 42 | }, nil 43 | }, 44 | }, 45 | { 46 | Name: "bar", 47 | New: func() (i interface{}, e error) { 48 | return nil, errors.New("error") 49 | }, 50 | }, 51 | } 52 | _ = c.Provide(objs...) 53 | 54 | // 测试单例 55 | var f1 *foo 56 | _ = c.Populate("foo", &f1) 57 | var f2 *foo 58 | _ = c.Populate("foo", &f2) 59 | a.Equal(fmt.Sprintf("%p", f1), fmt.Sprintf("%p", f2)) 60 | 61 | // 错误使用测试 62 | var f3 foo 63 | err := c.Populate("foo", f3) // 非指针 64 | a.Contains(err.Error(), "argument can only be pointer type") 65 | var f4 foo 66 | err = c.Populate("foo", &f4) // New函数返回的指针,但是f4为引用 [panic: reflect.Set: value of type *xdi.foo is not assignable to type xdi.foo] 67 | a.Contains(err.Error(), "value of type *xdi.foo is not assignable to type xdi.foo") 68 | 69 | // 测试嵌套依赖 70 | var f *foo 71 | _ = c.Populate("foo", &f) 72 | text := fmt.Sprintf("%#v \n", f.Client) 73 | a.Contains(text, "Timeout:10000000000") 74 | 75 | // 测试多次失败场景 76 | var i interface{} 77 | err = c.Populate("bar", &i) 78 | a.Equal(err, errors.New("error")) 79 | err = c.Populate("bar", &i) 80 | a.Equal(err, errors.New("error")) 81 | } 82 | 83 | func TestSingletonConcurrency(t *testing.T) { 84 | a := assert.New(t) 85 | 86 | c := New() 87 | objs := []*Object{ 88 | { 89 | Name: "foo", 90 | New: func() (i interface{}, e error) { 91 | return &foo{ 92 | Bar: "", 93 | }, nil 94 | }, 95 | }, 96 | } 97 | _ = c.Provide(objs...) 98 | 99 | for i := 0; i < 1000; i++ { 100 | var mp sync.Map 101 | wg := &sync.WaitGroup{} 102 | for i := 0; i < 100; i++ { 103 | wg.Add(1) 104 | go func(wg *sync.WaitGroup, i int) { 105 | defer wg.Done() 106 | 107 | var f *foo 108 | _ = c.Populate("foo", &f) 109 | 110 | mp.Store(i, f) 111 | }(wg, i) 112 | } 113 | wg.Wait() 114 | 115 | ptrs := []interface{}{} 116 | mp.Range(func(key, value interface{}) bool { 117 | ptrs = append(ptrs, fmt.Sprintf("%p", value)) 118 | return true 119 | }) 120 | 121 | //fmt.Println(ptrs...) 122 | 123 | a.Equal(ptrs[0], ptrs[1], ptrs[2:]...) 124 | } 125 | } 126 | 127 | func TestSingletonConcurrencyError(t *testing.T) { 128 | a := assert.New(t) 129 | 130 | c := New() 131 | objs := []*Object{ 132 | { 133 | Name: "foo", 134 | New: func() (i interface{}, e error) { 135 | return nil, errors.New("new error") 136 | }, 137 | }, 138 | } 139 | _ = c.Provide(objs...) 140 | 141 | for i := 0; i < 1000; i++ { 142 | var mp sync.Map 143 | wg := &sync.WaitGroup{} 144 | for i := 0; i < 100; i++ { 145 | wg.Add(1) 146 | go func(wg *sync.WaitGroup, i int) { 147 | defer wg.Done() 148 | 149 | var f *foo 150 | _ = c.Populate("foo", &f) 151 | 152 | mp.Store(i, f) 153 | }(wg, i) 154 | } 155 | wg.Wait() 156 | 157 | ptrs := []interface{}{} 158 | mp.Range(func(key, value interface{}) bool { 159 | ptrs = append(ptrs, fmt.Sprintf("%p", value)) 160 | return true 161 | }) 162 | 163 | //fmt.Println(ptrs...) 164 | 165 | a.Equal(ptrs[0], ptrs[1], ptrs[2:]...) 166 | } 167 | } 168 | 169 | func TestSingletonConcurrencyRefresh(t *testing.T) { 170 | a := assert.New(t) 171 | 172 | c := New() 173 | objs := []*Object{ 174 | { 175 | Name: "foo", 176 | New: func() (i interface{}, e error) { 177 | return &foo{ 178 | Bar: "", 179 | }, nil 180 | }, 181 | }, 182 | } 183 | _ = c.Provide(objs...) 184 | 185 | for i := 0; i < 1000; i++ { 186 | var mp sync.Map 187 | wg := &sync.WaitGroup{} 188 | 189 | var f *foo 190 | _ = c.Populate("foo", &f) 191 | o, _ := c.Object("foo") 192 | _ = o.Refresh() 193 | 194 | for i := 0; i < 100; i++ { 195 | wg.Add(1) 196 | go func(wg *sync.WaitGroup, i int) { 197 | defer wg.Done() 198 | 199 | var f *foo 200 | _ = c.Populate("foo", &f) 201 | 202 | mp.Store(i, f) 203 | }(wg, i) 204 | } 205 | wg.Wait() 206 | 207 | ptrs := []interface{}{} 208 | mp.Range(func(key, value interface{}) bool { 209 | ptrs = append(ptrs, fmt.Sprintf("%p", value)) 210 | return true 211 | }) 212 | 213 | //fmt.Println(ptrs...) 214 | 215 | a.Equal(ptrs[0], ptrs[1], ptrs[2:]...) 216 | } 217 | } 218 | --------------------------------------------------------------------------------