├── .github └── workflows │ └── go.yml ├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── benchmark_test.go ├── go.mod ├── go.sum ├── ioc.go └── ioc_test.go /.github/workflows/go.yml: -------------------------------------------------------------------------------- 1 | # This workflow will build a golang project 2 | # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-go 3 | 4 | name: Go 5 | 6 | on: 7 | push: 8 | branches: [ "master" ] 9 | pull_request: 10 | branches: [ "master" ] 11 | 12 | jobs: 13 | 14 | build: 15 | runs-on: ubuntu-latest 16 | steps: 17 | - uses: actions/checkout@v3 18 | 19 | - name: Set up Go 20 | uses: actions/setup-go@v4 21 | with: 22 | go-version: '1.20' 23 | 24 | - name: Build 25 | run: go build -v ./... 26 | 27 | - name: Test 28 | run: make go-test 29 | 30 | - name: Benchmark 31 | run: make go-bench 32 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | *.test 24 | *.prof 25 | *.out 26 | coverage.html 27 | .vscode 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Jerry Bai 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | go-test: 2 | go test -count=1 -coverprofile=coverage.out -timeout=30s ./... 3 | go tool cover -html=coverage.out -o coverage.html 4 | 5 | go-bench: 6 | go test -run=none -count=1 -benchtime=1000000x -benchmem -bench=. ./... -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ioc 2 | 3 | Inversion of Control (IoC) 4 | 5 | ## Feature 6 | 7 | * 1) Support service as singleton and transient 8 | 9 | * 2) Support resolve service by parent if not found in current 10 | 11 | * 3) Support inject to function or *struct with services that has registered 12 | 13 | Should add struct tag 'ioc-inject:"true"' to field if want to be injected, but field type `ioc.Resolver` is not necessary. 14 | 15 | * 4) Support override exists service 16 | 17 | Register to parent's container, and then register to current's to override parent's. 18 | 19 | * 5) Support inject to singleton instance automatically 20 | 21 | Inject to singleton instance and it's initialize method `Initialize(XXX)` or another one which the returns of method `InitializeMethodName() string` automatically. 22 | 23 | It will use zero value instead of panic if depended service not registerd. 24 | 25 | ## Usage 26 | 27 | ```go 28 | package main 29 | 30 | import ( 31 | "github.com/berkaroad/ioc" 32 | ) 33 | 34 | type Interface1 interface { 35 | GetC2Name() string 36 | } 37 | 38 | type Interface2 interface { 39 | GetName() string 40 | } 41 | 42 | type Class1 struct { 43 | Resolver ioc.Resolver 44 | C2 *Class2 `ioc-inject:"true"` 45 | } 46 | 47 | func (c *Class1) GetC2Name() string { 48 | return c.C2.Name 49 | } 50 | 51 | type Class2 struct { 52 | Name string 53 | resolver ioc.Resolver 54 | } 55 | 56 | func (c *Class2) GetName() string { 57 | return c.Name 58 | } 59 | 60 | func (c *Class2) Initialize(resolver ioc.Resolver) string { 61 | c.resolver = resolver 62 | return c.Name 63 | } 64 | 65 | type Interface3 interface { 66 | GetName() string 67 | } 68 | 69 | type Class3 struct { 70 | Name string 71 | resolver ioc.Resolver 72 | } 73 | 74 | func (c *Class3) GetName() string { 75 | return "Class3-" + c.Name 76 | } 77 | 78 | // specific custom initialize method name 79 | func (c *Class3) InitializeMethodName() string { 80 | return "MyInitialize" 81 | } 82 | 83 | // custom initialize method 84 | func (c *Class2) MyInitialize(resolver ioc.Resolver) string { 85 | c.resolver = resolver 86 | return c.Name 87 | } 88 | 89 | type Class4 struct { 90 | Name string 91 | } 92 | 93 | func (c *Class4) GetName() string { 94 | return "Class3-" + c.Name 95 | } 96 | 97 | func main() { 98 | // register service to *struct 99 | ioc.AddSingleton[*Class2](&Class2{Name: "Jerry Bai"}) 100 | ioc.AddTransient[*Class1](func() *Class1 { 101 | var svc Class1 102 | // inject to *struct 103 | ioc.Inject(&svc) 104 | } 105 | 106 | // register service to interface. 107 | ioc.AddSingleton[Interface2](&Class2{Name: "Jerry Bai"}) 108 | ioc.AddTransient[Interface1](func() Interface1 { 109 | var svc Class1 110 | // inject to *struct 111 | ioc.Inject(&svc) 112 | } 113 | 114 | // get service from ioc 115 | c1 := ioc.GetService[*Class1] 116 | c2 := ioc.GetService[*Class2] 117 | i1 := ioc.GetService[Interface1] 118 | i2 := ioc.GetService[Interface2] 119 | 120 | // inject to function 121 | ioc.Inject(func(c1 *Class1, c2 *Class2, i1 Interface1, i2 Interface2, resolver ioc.Resolver) { 122 | println("c1.C2Name=", c1.C2.Name) 123 | println("c2.Name=", c2.Name) 124 | println("i1.GetC2Name=()", i1.GetC2Name()) 125 | println("i2.GetName=()", i2.GetName()) 126 | }) 127 | 128 | // override exists service 129 | c := ioc.New() 130 | ioc.SetParent(c) 131 | ioc.AddSingletonToC[Interface3](c, &Class3{Name: "Jerry Bai"}) // add service to parent's container 132 | i3 := ioc.GetService[Interface3]() // *Class3, 'Interface3' only exists in parent's container 133 | ioc.AddSingleton[Interface3](&Class4{Name: "Jerry Bai"}) // add service to global's container 134 | i3 = ioc.GetService[Interface3]() // *Class4, 'Interface3' exists in both global and parent's container 135 | } 136 | ``` 137 | 138 | ## Benchmark 139 | 140 | ```sh 141 | go test -run=none -count=1 -benchtime=1000000x -benchmem -bench=. ./... 142 | 143 | goos: linux 144 | goarch: amd64 145 | pkg: github.com/berkaroad/ioc 146 | cpu: AMD Ryzen 7 5800H with Radeon Graphics 147 | BenchmarkGetSingletonService-4 1000000 26.16 ns/op 0 B/op 0 allocs/op 148 | BenchmarkGetTransientService-4 1000000 370.9 ns/op 48 B/op 1 allocs/op 149 | BenchmarkGetTransientServiceNative-4 1000000 131.9 ns/op 48 B/op 1 allocs/op 150 | BenchmarkInjectToFunc-4 1000000 659.5 ns/op 144 B/op 5 allocs/op 151 | BenchmarkInjectToFuncNative-4 1000000 89.26 ns/op 0 B/op 0 allocs/op 152 | BenchmarkInjectToStruct-4 1000000 311.7 ns/op 0 B/op 0 allocs/op 153 | BenchmarkInjectToStructNative-4 1000000 87.64 ns/op 0 B/op 0 allocs/op 154 | PASS 155 | ok github.com/berkaroad/ioc 1.686s 156 | ``` 157 | -------------------------------------------------------------------------------- /benchmark_test.go: -------------------------------------------------------------------------------- 1 | // The MIT License (MIT) 2 | // 3 | // # Copyright (c) 2016 Jerry Bai 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 11 | // 12 | // The above copyright notice and this permission notice shall be included in all 13 | // copies or substantial portions of the Software. 14 | // 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | // SOFTWARE. 22 | package ioc 23 | 24 | import ( 25 | "context" 26 | "testing" 27 | ) 28 | 29 | func BenchmarkGetSingletonService(b *testing.B) { 30 | globalContainer = New() 31 | AddSingleton[ProductCategoryRepository](&ProductCategoryRepositoryImpl{}) 32 | AddSingleton[ProductCategoryRepository2](&ProductCategoryRepositoryImpl{}) 33 | AddSingleton[*ProductCategoryApplicationServiceImpl](&ProductCategoryApplicationServiceImpl{}) 34 | 35 | b.ResetTimer() 36 | for i := 0; i < b.N; i++ { 37 | svc := GetService[*ProductCategoryApplicationServiceImpl]() 38 | svc.Get(context.TODO(), "123") 39 | } 40 | } 41 | 42 | func BenchmarkGetTransientService(b *testing.B) { 43 | globalContainer = New() 44 | AddSingleton[ProductCategoryRepository](&ProductCategoryRepositoryImpl{}) 45 | AddSingleton[ProductCategoryRepository2](&ProductCategoryRepositoryImpl{}) 46 | AddTransient[*ProductCategoryApplicationServiceImpl](func() *ProductCategoryApplicationServiceImpl { 47 | svc := &ProductCategoryApplicationServiceImpl{} 48 | Inject(svc) 49 | return svc 50 | }) 51 | 52 | b.ResetTimer() 53 | for i := 0; i < b.N; i++ { 54 | svc := GetService[*ProductCategoryApplicationServiceImpl]() 55 | svc.Get(context.TODO(), "123") 56 | } 57 | } 58 | 59 | func BenchmarkGetTransientServiceNative(b *testing.B) { 60 | globalContainer = New() 61 | AddSingleton[ProductCategoryRepository](&ProductCategoryRepositoryImpl{}) 62 | AddSingleton[ProductCategoryRepository2](&ProductCategoryRepositoryImpl{}) 63 | 64 | b.ResetTimer() 65 | for i := 0; i < b.N; i++ { 66 | svc := &ProductCategoryApplicationServiceImpl{} 67 | svc.Resolver = GetService[Resolver]() 68 | svc.Repo = GetService[ProductCategoryRepository]() 69 | svc.Repo2 = GetService[ProductCategoryRepository2]() 70 | svc.Get(context.TODO(), "123") 71 | } 72 | } 73 | 74 | func BenchmarkInjectToFunc(b *testing.B) { 75 | globalContainer = New() 76 | AddSingleton[ProductCategoryRepository](&ProductCategoryRepositoryImpl{}) 77 | AddSingleton[ProductCategoryRepository2](&ProductCategoryRepositoryImpl{}) 78 | svc := &ProductCategoryApplicationServiceImpl{} 79 | 80 | b.ResetTimer() 81 | for i := 0; i < b.N; i++ { 82 | Inject(svc.Initialize) 83 | svc.Get(context.TODO(), "123") 84 | } 85 | } 86 | 87 | func BenchmarkInjectToFuncNative(b *testing.B) { 88 | globalContainer = New() 89 | AddSingleton[ProductCategoryRepository](&ProductCategoryRepositoryImpl{}) 90 | AddSingleton[ProductCategoryRepository2](&ProductCategoryRepositoryImpl{}) 91 | svc := &ProductCategoryApplicationServiceImpl{} 92 | 93 | b.ResetTimer() 94 | for i := 0; i < b.N; i++ { 95 | svc.Initialize(GetService[Resolver](), GetService[ProductCategoryRepository](), GetService[ProductCategoryRepository2]()) 96 | svc.Get(context.TODO(), "123") 97 | } 98 | } 99 | 100 | func BenchmarkInjectToStruct(b *testing.B) { 101 | globalContainer = New() 102 | AddSingleton[ProductCategoryRepository](&ProductCategoryRepositoryImpl{}) 103 | AddSingleton[ProductCategoryRepository2](&ProductCategoryRepositoryImpl{}) 104 | svc := &ProductCategoryApplicationServiceImpl{} 105 | 106 | b.ResetTimer() 107 | for i := 0; i < b.N; i++ { 108 | Inject(svc) 109 | svc.Get(context.TODO(), "123") 110 | } 111 | } 112 | 113 | func BenchmarkInjectToStructNative(b *testing.B) { 114 | globalContainer = New() 115 | AddSingleton[ProductCategoryRepository](&ProductCategoryRepositoryImpl{}) 116 | AddSingleton[ProductCategoryRepository2](&ProductCategoryRepositoryImpl{}) 117 | svc := &ProductCategoryApplicationServiceImpl{} 118 | 119 | b.ResetTimer() 120 | for i := 0; i < b.N; i++ { 121 | svc.Resolver = GetService[Resolver]() 122 | svc.Repo = GetService[ProductCategoryRepository]() 123 | svc.Repo2 = GetService[ProductCategoryRepository2]() 124 | svc.Get(context.TODO(), "123") 125 | } 126 | } 127 | 128 | type ProductCategoryApplicationService interface { 129 | Get(ctx context.Context, id string) ProductCategory 130 | } 131 | 132 | var _ ProductCategoryApplicationService = (*ProductCategoryApplicationServiceImpl)(nil) 133 | 134 | type ProductCategoryApplicationServiceImpl struct { 135 | Resolver Resolver 136 | Repo ProductCategoryRepository `ioc-inject:"true"` 137 | Repo2 ProductCategoryRepository2 `ioc-inject:"true"` 138 | } 139 | 140 | func (svc *ProductCategoryApplicationServiceImpl) Initialize(resolver Resolver, repo ProductCategoryRepository, repo2 ProductCategoryRepository2) { 141 | svc.Resolver = resolver 142 | svc.Repo = repo 143 | svc.Repo2 = repo2 144 | } 145 | 146 | func (svc *ProductCategoryApplicationServiceImpl) Get(ctx context.Context, id string) ProductCategory { 147 | return svc.Repo.Get(id) 148 | } 149 | 150 | type ProductCategoryRepository interface { 151 | Get(id string) ProductCategory 152 | } 153 | 154 | var _ ProductCategoryRepository = (*ProductCategoryRepositoryImpl)(nil) 155 | 156 | type ProductCategoryRepositoryImpl struct{} 157 | 158 | func (repo *ProductCategoryRepositoryImpl) Get(id string) ProductCategory { 159 | return ProductCategory{ 160 | ID: id, 161 | } 162 | } 163 | 164 | type ProductCategory struct { 165 | ID string 166 | Name string 167 | } 168 | 169 | type ProductCategoryRepository2 interface { 170 | Get(id string) ProductCategory 171 | } 172 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/berkaroad/ioc 2 | 3 | go 1.18 4 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/berkaroad/ioc/3c373d0452b214bcd0b752bc9084e15c1c84bca4/go.sum -------------------------------------------------------------------------------- /ioc.go: -------------------------------------------------------------------------------- 1 | // Package ioc is Inversion of Control (IoC). 2 | // Support singleton and transient. 3 | // 4 | // The MIT License (MIT) 5 | // 6 | // # Copyright (c) 2016 Jerry Bai 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to deal 10 | // in the Software without restriction, including without limitation the rights 11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | // copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in all 16 | // copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 | // SOFTWARE. 25 | package ioc 26 | 27 | import ( 28 | "errors" 29 | "fmt" 30 | "reflect" 31 | "sync" 32 | ) 33 | 34 | const DefaultInitializeMethodName string = "Initialize" 35 | 36 | // CustomInitializer that use customize initialize method instead of default method 'Initialize' 37 | type CustomInitializer interface { 38 | // InitializeMethodName indicate the initialize method name. 39 | // Won't be invoked if the method returns is not exists. 40 | InitializeMethodName() string 41 | } 42 | 43 | var globalContainer Container = New() 44 | var resolverType reflect.Type = reflect.TypeOf((*Resolver)(nil)).Elem() 45 | 46 | // New ioc container, and add singleton service 'ioc.Resolver' to it. 47 | func New() Container { 48 | var c Container = &defaultContainer{} 49 | c.AddSingleton(resolverType, c) 50 | return c 51 | } 52 | 53 | // Inversion of Control container. 54 | type Container interface { 55 | Resolver 56 | 57 | // AddSingleton to add singleton instance. 58 | // 59 | // // service 60 | // type Service1 interface { 61 | // Method1() 62 | // } 63 | // // implementation of service 64 | // type ServiceImplementation1 struct { 65 | // Field1 string 66 | // } 67 | // func(si *ServiceImplementation1) Method1() {} 68 | // func(si *ServiceImplementation1) Initialize(resolver ioc.Resolver) { 69 | // si.resolver = resolver 70 | // } 71 | // 72 | // var container ioc.Container 73 | // // interface as service, register as singleton 74 | // err := container.AddSingleton(reflect.TypeOf((*Service1)(nil)).Elem(), &ServiceImplementation1{Field1: "abc"}) 75 | // // or *struct as service, register as singleton 76 | // err = container.AddSingleton(reflect.TypeOf((*ServiceImplementation1)(nil)), &ServiceImplementation1{Field1: "abc"}) 77 | AddSingleton(serviceType reflect.Type, instance any) error 78 | 79 | // AddTransient to add transient by instance factory. 80 | // 81 | // // service 82 | // type Service1 interface { 83 | // Method1() 84 | // } 85 | // // implementation of service 86 | // type ServiceImplementation1 struct { 87 | // Field1 string 88 | // } 89 | // func(si *ServiceImplementation1) Method1() {} 90 | // 91 | // var container ioc.Container 92 | // // interface as service, register as transient 93 | // err = container.AddTransient(reflect.TypeOf((*Service1)(nil)).Elem(), func() any { 94 | // return &ServiceImplementation1{Field1: "abc"} 95 | // }) 96 | // // or *struct as service, register as transient 97 | // err = container.AddTransient(reflect.TypeOf((*ServiceImplementation1)(nil)), func() any { 98 | // return &ServiceImplementation1{Field1: "abc"} 99 | // }) 100 | AddTransient(serviceType reflect.Type, instanceFactory func() any) error 101 | } 102 | 103 | // Resolver can resolve service. 104 | type Resolver interface { 105 | // Set parent resolver, for resolving from parent if service not found in current. 106 | SetParent(parent Resolver) 107 | 108 | // Resolve to get service. 109 | // 110 | // // service 111 | // type Service1 interface { 112 | // Method1() 113 | // } 114 | // // implementation of service 115 | // type ServiceImplementation1 struct { 116 | // Field1 string 117 | // } 118 | // func(si *ServiceImplementation1) Method1() {} 119 | // 120 | // var container ioc.Container 121 | // // interface as service 122 | // service1 := container.Resolve(reflect.TypeOf((*Service1)(nil)).Elem()) 123 | // // or *struct as service 124 | // service2 := container.Resolve(reflect.TypeOf((*ServiceImplementation1)(nil))) 125 | Resolve(serviceType reflect.Type) reflect.Value 126 | } 127 | 128 | // AddSingleton to add singleton instance. 129 | // 130 | // It will panic if 'TService' or 'instance' is invalid. 131 | // 132 | // // service 133 | // type Service1 interface { 134 | // Method1() 135 | // } 136 | // // implementation of service 137 | // type ServiceImplementation1 struct { 138 | // Field1 string 139 | // 140 | // resolver ioc.Resolver 141 | // } 142 | // func(si *ServiceImplementation1) Method1() {} 143 | // func(si *ServiceImplementation1) Initialize(resolver ioc.Resolver) { 144 | // si.resolver = resolver 145 | // } 146 | // 147 | // // interface as service 148 | // ioc.AddSingleton[Service1](&ServiceImplementation1{Field1: "abc"}) 149 | // // or *struct as service 150 | // ioc.AddSingleton[*ServiceImplementation1](&ServiceImplementation1{Field1: "abc"}) 151 | func AddSingleton[TService any](instance TService) { 152 | AddSingletonToC[TService](globalContainer, instance) 153 | } 154 | 155 | // AddSingletonToC to add singleton instance to container. 156 | // 157 | // It will panic if 'TService' or 'instance' is invalid. 158 | func AddSingletonToC[TService any](container Container, instance TService) { 159 | err := container.AddSingleton(reflect.TypeOf((*TService)(nil)).Elem(), instance) 160 | if err != nil { 161 | panic(err) 162 | } 163 | getFieldsToInject(reflect.ValueOf(instance).Type()) 164 | } 165 | 166 | // AddTransient to add transient service instance factory. 167 | // 168 | // It will panic if 'TService' or 'instance' is invalid. 169 | // 170 | // // service 171 | // type Service1 interface { 172 | // Method1() 173 | // } 174 | // // implementation of service 175 | // type ServiceImplementation1 struct { 176 | // Field1 string 177 | // } 178 | // func(si *ServiceImplementation1) Method1() {} 179 | // 180 | // // interface as service 181 | // ioc.AddTransient[Service1](func() Service1 { 182 | // return &ServiceImplementation1{Field1: "abc"} 183 | // }) 184 | // // or *struct as service 185 | // ioc.AddTransient[*ServiceImplementation1](func() *ServiceImplementation1 { 186 | // return &ServiceImplementation1{Field1: "abc"} 187 | // }) 188 | func AddTransient[TService any](instanceFactory func() TService) { 189 | AddTransientToC[TService](globalContainer, instanceFactory) 190 | } 191 | 192 | // AddTransientToC to add transient service instance factory to container. 193 | // 194 | // It will panic if 'TService' or 'instance' is invalid. 195 | func AddTransientToC[TService any](container Container, instanceFactory func() TService) { 196 | if instanceFactory == nil { 197 | panic("param 'instanceFactory' is null") 198 | } 199 | err := container.AddTransient(reflect.TypeOf((*TService)(nil)).Elem(), func() any { 200 | return instanceFactory() 201 | }) 202 | if err != nil { 203 | panic(err) 204 | } 205 | } 206 | 207 | // GetService to get service. 208 | // 209 | // // service 210 | // type Service1 interface { 211 | // Method1() 212 | // } 213 | // // implementation of service 214 | // type ServiceImplementation1 struct { 215 | // Field1 string 216 | // } 217 | // func(si *ServiceImplementation1) Method1() {} 218 | // 219 | // // interface as service 220 | // service1 := ioc.GetService[Service1]() 221 | // // or *struct as service 222 | // service2 := ioc.GetService[*ServiceImplementation1]() 223 | func GetService[TService any]() TService { 224 | return GetServiceFromC[TService](globalContainer) 225 | } 226 | 227 | // GetServiceFromC to get service from container. 228 | func GetServiceFromC[TService any](container Container) TService { 229 | var instance TService 230 | instanceVal := container.Resolve(reflect.TypeOf((*TService)(nil)).Elem()) 231 | if !instanceVal.IsValid() { 232 | return instance 233 | } 234 | instanceInterface := instanceVal.Interface() 235 | if instanceInterface != nil { 236 | if val, ok := instanceInterface.(TService); ok { 237 | instance = val 238 | } 239 | } 240 | return instance 241 | } 242 | 243 | // Inject to func or *struct with service. 244 | // Field with type 'ioc.Resolver', will always been injected. 245 | // 246 | // It will panic if param type in func not registered in container. 247 | // 248 | // // service 249 | // type Service1 interface { 250 | // Method1() 251 | // } 252 | // 253 | // // implementation of service 254 | // type ServiceImplementation1 struct { 255 | // Field1 string 256 | // } 257 | // func(si *ServiceImplementation1) Method1() {} 258 | // 259 | // // client 260 | // type Client struct { 261 | // Field1 Service1 `ioc-inject:"true"` 262 | // Field2 *ServiceImplementation1 `ioc-inject:"true"` 263 | // } 264 | // func(c *Client) Method1(p1 Service1, p2 *ServiceImplementation1) { 265 | // c.Field1 = p1 266 | // c.Field2 = p2 267 | // } 268 | // 269 | // var c client 270 | // // inject to func 271 | // ioc.Inject(c.Method1) 272 | // // inject to *struct 273 | // ioc.Inject(&c) 274 | func Inject(target any) { 275 | InjectFromC(globalContainer, target) 276 | } 277 | 278 | // InjectFromC to inject to func or *struct or their's reflect.Value with service from container. 279 | // Field with type 'ioc.Resolver', will always been injected. 280 | // 281 | // It will panic if param type in func not registered in container. 282 | func InjectFromC(container Container, target any) { 283 | var targetVal reflect.Value 284 | if val, ok := target.(reflect.Value); ok { 285 | targetVal = val 286 | } else { 287 | targetVal = reflect.ValueOf(target) 288 | } 289 | if !targetVal.IsValid() || targetVal.IsZero() { 290 | return 291 | } 292 | targetType := targetVal.Type() 293 | if targetType.Kind() == reflect.Func { 294 | // inject to func 295 | var in = make([]reflect.Value, targetType.NumIn()) 296 | for i := 0; i < targetType.NumIn(); i++ { 297 | argType := targetType.In(i) 298 | val := container.Resolve(argType) 299 | if !val.IsValid() { 300 | in[i] = reflect.Zero(argType) 301 | } else { 302 | in[i] = val 303 | } 304 | } 305 | targetVal.Call(in) 306 | } else if targetType.Kind() == reflect.Pointer && targetType.Elem().Kind() == reflect.Struct { 307 | // skip implementation of ioc.Resolver 308 | if targetType.Implements(resolverType) { 309 | return 310 | } 311 | 312 | // inject to *struct 313 | structType := targetType.Elem() 314 | fields := getFieldsToInject(structType) 315 | for _, field := range fields { 316 | fieldVal := targetVal.Elem().Field(field.FieldIndex) 317 | val := container.Resolve(field.FieldType) 318 | if val.IsValid() { 319 | fieldVal.Set(val) 320 | } 321 | } 322 | } 323 | } 324 | 325 | // Set parent resolver, for resolving from parent if service not found in current. 326 | func SetParent(parent Resolver) { 327 | globalContainer.SetParent(parent) 328 | } 329 | 330 | var structTypeToFieldsCache sync.Map 331 | 332 | func getFieldsToInject(targetType reflect.Type) []structField { 333 | structType := targetType 334 | for structType.Kind() == reflect.Pointer { 335 | structType = structType.Elem() 336 | } 337 | if structType.Kind() != reflect.Struct { 338 | return nil 339 | } 340 | 341 | if val, ok := structTypeToFieldsCache.Load(structType); ok { 342 | return val.([]structField) 343 | } 344 | fields := make([]structField, 0, structType.NumField()) 345 | for i := 0; i < structType.NumField(); i++ { 346 | field := structType.Field(i) 347 | if !field.IsExported() || field.Anonymous { 348 | continue 349 | } 350 | canInject := field.Type == resolverType 351 | if !canInject { 352 | if val, ok := field.Tag.Lookup("ioc-inject"); ok && val == "true" { 353 | canInject = true 354 | } 355 | } 356 | if canInject { 357 | fields = append(fields, structField{ 358 | FieldIndex: i, 359 | FieldType: field.Type, 360 | }) 361 | } 362 | } 363 | structTypeToFieldsCache.Store(structType, fields) 364 | return fields 365 | } 366 | 367 | type structField struct { 368 | FieldIndex int 369 | FieldType reflect.Type 370 | } 371 | 372 | var _ Container = (*defaultContainer)(nil) 373 | 374 | type defaultContainer struct { 375 | bindings sync.Map 376 | parent Resolver 377 | locker sync.Mutex 378 | } 379 | 380 | func (c *defaultContainer) Resolve(serviceType reflect.Type) reflect.Value { 381 | binding := c.getBinding(serviceType) 382 | if binding != nil { 383 | if binding.Instance.IsValid() { 384 | if !binding.InstanceInitialized { 385 | defer binding.Unlock() 386 | binding.Lock() 387 | Inject(binding.Instance) 388 | if binding.InstanceInitializer.IsValid() { 389 | func() { 390 | defer recover() 391 | Inject(binding.InstanceInitializer) 392 | }() 393 | } 394 | binding.InstanceInitialized = true 395 | } 396 | return binding.Instance 397 | } 398 | return reflect.ValueOf(binding.InstanceFactory()) 399 | } else { 400 | parent := c.parent 401 | if parent != nil { 402 | return parent.Resolve(serviceType) 403 | } else { 404 | return reflect.Value{} 405 | } 406 | } 407 | } 408 | 409 | func (c *defaultContainer) SetParent(parent Resolver) { 410 | defer c.locker.Unlock() 411 | c.locker.Lock() 412 | if parent == nil || c.parent == parent { 413 | return 414 | } 415 | 416 | if c.parent == nil { 417 | c.parent = parent 418 | } else { 419 | c.parent.SetParent(parent) 420 | } 421 | } 422 | 423 | func (c *defaultContainer) AddSingleton(serviceType reflect.Type, instance any) error { 424 | if serviceType == nil { 425 | return errors.New("param 'serviceType' is null") 426 | } 427 | if instance == nil || reflect.ValueOf(instance).IsZero() { 428 | return errors.New("param 'instance' is null") 429 | } 430 | binding := c.getBinding(serviceType) 431 | if binding != nil { 432 | // ignore exists service in current container 433 | return nil 434 | } 435 | binding = &serviceBinding{ServiceType: serviceType, Instance: reflect.ValueOf(instance)} 436 | if serviceType != resolverType { 437 | initializeMethodName := DefaultInitializeMethodName 438 | if initializer, ok := binding.Instance.Interface().(CustomInitializer); ok { 439 | initializeMethodName = initializer.InitializeMethodName() 440 | } 441 | if foundMethod := binding.Instance.MethodByName(initializeMethodName); foundMethod.IsValid() { 442 | methodType := foundMethod.Type() 443 | for i := 0; i < methodType.NumIn(); i++ { 444 | if methodType.In(i) == serviceType { 445 | return fmt.Errorf("cycle reference: param[%d]'s type in method '%s' equals to service '%v'", i, initializeMethodName, serviceType) 446 | } 447 | } 448 | binding.InstanceInitializer = foundMethod 449 | } 450 | } 451 | return c.addBinding(binding) 452 | } 453 | 454 | func (c *defaultContainer) AddTransient(serviceType reflect.Type, instanceFactory func() any) error { 455 | if serviceType == nil { 456 | return errors.New("param 'serviceType' is null") 457 | } 458 | if instanceFactory == nil { 459 | return errors.New("param 'instanceFactory' is null") 460 | } 461 | binding := c.getBinding(serviceType) 462 | if binding != nil { 463 | // ignore exists service in current container 464 | return nil 465 | } 466 | binding = &serviceBinding{ServiceType: serviceType, InstanceFactory: instanceFactory} 467 | return c.addBinding(binding) 468 | } 469 | 470 | func (c *defaultContainer) addBinding(binding *serviceBinding) error { 471 | if binding != nil && binding.ServiceType != nil { 472 | if binding.ServiceType.Kind() != reflect.Interface && 473 | !(binding.ServiceType.Kind() == reflect.Pointer && binding.ServiceType.Elem().Kind() == reflect.Struct) { 474 | return fmt.Errorf("type of service '%v' should be an interface or *struct", binding.ServiceType) 475 | } 476 | if binding.Instance.IsValid() { 477 | if !binding.Instance.Type().AssignableTo(binding.ServiceType) { 478 | return fmt.Errorf("instance should implement the service '%v'", binding.ServiceType) 479 | } 480 | } 481 | c.bindings.LoadOrStore(binding.ServiceType, binding) 482 | } 483 | return nil 484 | } 485 | 486 | func (c *defaultContainer) getBinding(serviceType reflect.Type) *serviceBinding { 487 | if bindingVal, ok := c.bindings.Load(serviceType); ok { 488 | binding := bindingVal.(*serviceBinding) 489 | return binding 490 | } 491 | return nil 492 | } 493 | 494 | type serviceBinding struct { 495 | ServiceType reflect.Type 496 | Instance reflect.Value 497 | InstanceInitializer reflect.Value 498 | InstanceInitialized bool 499 | InstanceFactory func() any 500 | 501 | initializerLocker sync.Mutex 502 | } 503 | 504 | func (b *serviceBinding) Lock() { 505 | b.initializerLocker.Lock() 506 | } 507 | 508 | func (b *serviceBinding) Unlock() { 509 | b.initializerLocker.Unlock() 510 | } 511 | -------------------------------------------------------------------------------- /ioc_test.go: -------------------------------------------------------------------------------- 1 | // The MIT License (MIT) 2 | // 3 | // # Copyright (c) 2016 Jerry Bai 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 11 | // 12 | // The above copyright notice and this permission notice shall be included in all 13 | // copies or substantial portions of the Software. 14 | // 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | // SOFTWARE. 22 | package ioc 23 | 24 | import ( 25 | "fmt" 26 | "reflect" 27 | "testing" 28 | ) 29 | 30 | func TestAddSingleton(t *testing.T) { 31 | t.Run("use interface as service and get service success", func(t *testing.T) { 32 | globalContainer = New() 33 | svc1 := &serviceInstance1{name: "instance1"} 34 | AddSingleton[service1](svc1) 35 | AddSingleton[service1](svc1) // ignore exists 36 | svc1FromIoc := GetService[service1]() 37 | if svc1FromIoc == nil { 38 | t.Error("get service null") 39 | return 40 | } 41 | if svc1FromIoc != svc1 { 42 | t.Error("service should be singleton") 43 | return 44 | } 45 | }) 46 | 47 | t.Run("use *struct as service and get service success", func(t *testing.T) { 48 | globalContainer = New() 49 | svc1 := &serviceInstance1{name: "instance1"} 50 | AddSingleton[*serviceInstance1](svc1) 51 | AddSingleton[*serviceInstance1](svc1) // ignore exists 52 | svc1FromIoc := GetService[*serviceInstance1]() 53 | if svc1FromIoc == nil { 54 | t.Error("get service null") 55 | return 56 | } 57 | if svc1FromIoc != svc1 { 58 | t.Error("service should be singleton") 59 | return 60 | } 61 | }) 62 | 63 | t.Run("invalid service should fail", func(t *testing.T) { 64 | globalContainer = New() 65 | func() { 66 | defer func() { 67 | if r := recover(); r == nil { 68 | t.Error("type of service 'serviceInstance1' should be interface or *struct") 69 | } else { 70 | fmt.Printf("panic: %v\n", r) 71 | } 72 | }() 73 | AddSingleton[serviceInstance1](serviceInstance1{}) 74 | }() 75 | }) 76 | 77 | t.Run("null service instance should fail", func(t *testing.T) { 78 | globalContainer = New() 79 | func() { 80 | defer func() { 81 | if r := recover(); r == nil { 82 | t.Error("instance couldn't be null") 83 | } else { 84 | fmt.Printf("panic: %v\n", r) 85 | } 86 | }() 87 | AddSingleton[*serviceInstance1](nil) 88 | }() 89 | }) 90 | 91 | t.Run("use *struct as service and cycle reference in 'Initialize()' should fail", func(t *testing.T) { 92 | globalContainer = New() 93 | func() { 94 | defer func() { 95 | if r := recover(); r == nil { 96 | t.Error("cycle reference in 'Initialize()' in service 'serviceInstance9'") 97 | } else { 98 | fmt.Printf("panic: %v\n", r) 99 | } 100 | }() 101 | AddSingleton[*serviceInstance9](&serviceInstance9{}) 102 | }() 103 | }) 104 | 105 | t.Run("use interface as service and cycle reference in 'Initialize()' should fail", func(t *testing.T) { 106 | globalContainer = New() 107 | func() { 108 | defer func() { 109 | if r := recover(); r == nil { 110 | t.Error("cycle reference in 'Initialize()' in service 'serviceInstance9'") 111 | } else { 112 | fmt.Printf("panic: %v\n", r) 113 | } 114 | }() 115 | AddSingleton[service1](&serviceInstance10{}) 116 | }() 117 | }) 118 | } 119 | 120 | func TestAddTransient(t *testing.T) { 121 | t.Run("use interface as service and get service success", func(t *testing.T) { 122 | globalContainer = New() 123 | AddTransient[service2](func() service2 { return &serviceInstance2{name: "instance2"} }) 124 | AddTransient[service2](func() service2 { return &serviceInstance2{name: "instance2"} }) // ignore exists 125 | svc2FromIoc := GetService[service2]() 126 | if svc2FromIoc == nil { 127 | t.Error("get service null") 128 | return 129 | } 130 | if svc2FromIoc.GetName() != "instance2" { 131 | t.Error("name of service should be instance2") 132 | return 133 | } 134 | svc2 := svc2FromIoc 135 | svc2FromIoc = GetService[service2]() 136 | if svc2FromIoc == svc2 { 137 | t.Error("service should be transient") 138 | return 139 | } 140 | }) 141 | 142 | t.Run("use *struct as service and get service success", func(t *testing.T) { 143 | globalContainer = New() 144 | AddTransient[*serviceInstance2](func() *serviceInstance2 { return &serviceInstance2{name: "instance2"} }) 145 | AddTransient[*serviceInstance2](func() *serviceInstance2 { return &serviceInstance2{name: "instance2"} }) // ignore exists 146 | svc2FromIoc := GetService[*serviceInstance2]() 147 | if svc2FromIoc == nil { 148 | t.Error("get service null") 149 | return 150 | } 151 | if svc2FromIoc.GetName() != "instance2" { 152 | t.Error("name of service should be instance2") 153 | return 154 | } 155 | svc2 := svc2FromIoc 156 | svc2FromIoc = GetService[*serviceInstance2]() 157 | if svc2FromIoc == svc2 { 158 | t.Error("service should be transient") 159 | return 160 | } 161 | }) 162 | 163 | t.Run("invalid service should fail", func(t *testing.T) { 164 | globalContainer = New() 165 | func() { 166 | defer func() { 167 | if r := recover(); r == nil { 168 | t.Error("type of service 'serviceInstance1' should be interface or *struct") 169 | } else { 170 | fmt.Printf("panic: %v\n", r) 171 | } 172 | }() 173 | AddTransient[serviceInstance1](func() serviceInstance1 { return serviceInstance1{} }) 174 | }() 175 | }) 176 | 177 | t.Run("null service instance factory should fail", func(t *testing.T) { 178 | globalContainer = New() 179 | func() { 180 | defer func() { 181 | if r := recover(); r == nil { 182 | t.Error("instance factory couldn't be null") 183 | } else { 184 | fmt.Printf("panic: %v\n", r) 185 | } 186 | }() 187 | AddTransient[*serviceInstance1](nil) 188 | }() 189 | }) 190 | } 191 | 192 | func TestGetService(t *testing.T) { 193 | t.Run("get service with func 'Initialize()' should success", func(t *testing.T) { 194 | globalContainer = New() 195 | AddSingleton[*serviceInstance7](&serviceInstance7{name: "instance7"}) 196 | AddSingleton[*serviceInstance8](&serviceInstance8{}) 197 | 198 | svc8 := GetService[*serviceInstance8]() 199 | if svc8.GetS7Name() != "instance7" { 200 | t.Error("should function 'initialize()' invoked success") 201 | return 202 | } 203 | }) 204 | 205 | t.Run("get service with custom initialize function should success", func(t *testing.T) { 206 | globalContainer = New() 207 | AddSingleton[*serviceInstance11](&serviceInstance11{name: "instance11"}) 208 | AddSingleton[*serviceInstance12](&serviceInstance12{name: "instance12"}) 209 | svc11 := GetService[*serviceInstance11]() 210 | if svc11.s12 == nil || svc11.s12.name != "instance12" { 211 | t.Error("should custom initialize function invoked success") 212 | return 213 | } 214 | }) 215 | 216 | t.Run("replace exists service should success", func(t *testing.T) { 217 | globalContainer = New() 218 | anotherC := New() 219 | SetParent(anotherC) 220 | 221 | AddSingletonToC[*serviceInstance7](anotherC, &serviceInstance7{name: "instance7"}) 222 | AddSingletonToC[*serviceInstance8](anotherC, &serviceInstance8{}) 223 | 224 | // replace exists service 225 | AddSingleton[*serviceInstance7](&serviceInstance7{name: "new-instance7"}) 226 | 227 | svc8 := GetService[*serviceInstance8]() 228 | if svc8.GetS7Name() != "new-instance7" { 229 | t.Error("should replace exists service success") 230 | return 231 | } 232 | }) 233 | 234 | t.Run("func 'Initialize()' missing service should fail", func(t *testing.T) { 235 | globalContainer = New() 236 | AddSingleton[*serviceInstance8](&serviceInstance8{}) 237 | func() { 238 | defer func() { 239 | if r := recover(); r == nil { 240 | t.Error("missing service should fail") 241 | } else { 242 | fmt.Printf("panic: %v\n", r) 243 | } 244 | }() 245 | svc8 := GetService[*serviceInstance8]() 246 | if svc8.GetS7Name() != "instance7" { 247 | t.Error("should function 'initialize()' invoked success") 248 | return 249 | } 250 | }() 251 | }) 252 | } 253 | 254 | func TestInject(t *testing.T) { 255 | t.Run("inject to func should success", func(t *testing.T) { 256 | globalContainer = New() 257 | AddSingleton[service3](&serviceInstance3{name: "instance3"}) 258 | AddTransient[*serviceInstance3](func() *serviceInstance3 { return &serviceInstance3{name: "instance3"} }) 259 | AddTransient[service4](func() service4 { return &serviceInstance4{name: "instance4"} }) 260 | AddSingleton[*serviceInstance4](&serviceInstance4{name: "instance4"}) 261 | 262 | var c client 263 | Inject(c.Func1) 264 | Inject((&c).Func1) 265 | if c.F1 == nil || c.F1 != GetService[service3]() { 266 | t.Error("singleton instance should same after inject") 267 | return 268 | } 269 | if c.F2 == nil || c.F2 == GetService[*serviceInstance3]() { 270 | t.Error("transient instance should different after inject") 271 | return 272 | } 273 | if c.F3 == nil || c.F3 == GetService[service4]() { 274 | t.Error("transient instance should different after inject") 275 | return 276 | } 277 | if c.F4 == nil || c.F4 != GetService[*serviceInstance4]() { 278 | t.Error("singleton instance should same after inject") 279 | return 280 | } 281 | }) 282 | 283 | t.Run("inject to func, that depends on part of unregisterd service, should also invoke success", func(t *testing.T) { 284 | globalContainer = New() 285 | svc1 := &serviceInstance1{name: "instance1"} 286 | AddSingleton[service1](svc1) 287 | 288 | invoked := false 289 | Inject(func(s1 service1, s2 service2, s3 *serviceInstance3, f1 int, f2 struct{ Name string }, f3 *struct{ Title string }) { 290 | invoked = true 291 | if s1 == nil || s1 != svc1 { 292 | t.Error("singleton instance should same after inject") 293 | return 294 | } 295 | if s2 != nil || s3 != nil { 296 | t.Error("unregister instance should be nil") 297 | return 298 | } 299 | }) 300 | if !invoked { 301 | t.Error("function after inject should be invoked") 302 | return 303 | } 304 | }) 305 | 306 | t.Run("inject to struct should success", func(t *testing.T) { 307 | globalContainer = New() 308 | AddSingleton[service3](&serviceInstance3{name: "instance3"}) 309 | AddTransient[*serviceInstance3](func() *serviceInstance3 { return &serviceInstance3{name: "instance3"} }) 310 | AddTransient[service4](func() service4 { return &serviceInstance4{name: "instance4"} }) 311 | AddSingleton[*serviceInstance4](&serviceInstance4{name: "instance4"}) 312 | 313 | var c client 314 | Inject(&c) 315 | 316 | if c.F3 != nil || c.F5 == nil || c.F5 == GetService[service4]() { 317 | t.Error("transient instance should same after inject, and only inject to field with tag 'ioc-inject:\"true\"'") 318 | return 319 | } 320 | if c.F4 != nil || c.F6 == nil || c.F6 != GetService[*serviceInstance4]() { 321 | t.Error("singleton instance should different after inject, and only inject to field with tag 'ioc-inject:\"true\"'") 322 | return 323 | } 324 | }) 325 | 326 | t.Run("inject to impletementation of ioc.Resolve should ignore", func(t *testing.T) { 327 | globalContainer = New() 328 | c := &defaultContainer{} 329 | InjectFromC(c, c) 330 | if c.parent != nil { 331 | t.Error("inject to impletementation of ioc.Resolve should ignore") 332 | return 333 | } 334 | }) 335 | 336 | t.Run("inject to invalid reflect.Value should ignore", func(t *testing.T) { 337 | globalContainer = New() 338 | c := &defaultContainer{} 339 | InjectFromC(c, reflect.Value{}) 340 | }) 341 | 342 | t.Run("inject to null should ignore", func(t *testing.T) { 343 | globalContainer = New() 344 | c := &defaultContainer{} 345 | InjectFromC(c, (*serviceInstance1)(nil)) 346 | }) 347 | } 348 | 349 | func TestSetParent(t *testing.T) { 350 | t.Run("resolve from parent success", func(t *testing.T) { 351 | globalContainer = New() 352 | 353 | anotherC := New() 354 | AddSingletonToC[service6](anotherC, &serviceInstance6{name: "instance6"}) 355 | AddTransientToC[*serviceInstance6](anotherC, func() *serviceInstance6 { return &serviceInstance6{name: "instance6"} }) 356 | 357 | if svc := GetService[service6](); svc != nil { 358 | t.Error("service should not found in current") 359 | return 360 | } 361 | if svc := GetService[*serviceInstance6](); svc != nil { 362 | t.Error("service should not found in current") 363 | return 364 | } 365 | SetParent(anotherC) 366 | if svc := GetService[service6](); svc == nil { 367 | t.Error("service should found in parent") 368 | return 369 | } 370 | if svc := GetService[*serviceInstance6](); svc == nil { 371 | t.Error("service should found in parent") 372 | return 373 | } 374 | }) 375 | 376 | t.Run("override parent's service success", func(t *testing.T) { 377 | globalContainer = New() 378 | 379 | anotherC := New() 380 | AddSingletonToC[service6](anotherC, &serviceInstance6{name: "instance6"}) 381 | AddTransientToC[*serviceInstance6](anotherC, func() *serviceInstance6 { return &serviceInstance6{name: "instance6"} }) 382 | SetParent(anotherC) 383 | if svc := GetService[service6](); svc == nil || svc.GetName() != "instance6" { 384 | t.Error("service should found in parent") 385 | return 386 | } 387 | if svc := GetService[*serviceInstance6](); svc == nil || svc.GetName() != "instance6" { 388 | t.Error("service should found in parent") 389 | return 390 | } 391 | 392 | AddSingleton[service6](&serviceInstance6{name: "override-instance6"}) 393 | AddTransient[*serviceInstance6](func() *serviceInstance6 { return &serviceInstance6{name: "override-instance6"} }) 394 | if svc := GetService[service6](); svc == nil || svc.GetName() != "override-instance6" { 395 | t.Error("service should override in global") 396 | return 397 | } 398 | if svc := GetService[*serviceInstance6](); svc == nil || svc.GetName() != "override-instance6" { 399 | t.Error("service should override in global") 400 | return 401 | } 402 | }) 403 | 404 | t.Run("parent is null or equals with last one should ignore", func(t *testing.T) { 405 | globalContainer = New() 406 | 407 | anotherC := New() 408 | AddSingletonToC[service6](anotherC, &serviceInstance6{name: "instance6"}) 409 | AddTransientToC[*serviceInstance6](anotherC, func() *serviceInstance6 { return &serviceInstance6{name: "instance6"} }) 410 | 411 | if svc := GetService[service6](); svc != nil { 412 | t.Error("service should not found in current") 413 | return 414 | } 415 | if svc := GetService[*serviceInstance6](); svc != nil { 416 | t.Error("service should not found in current") 417 | return 418 | } 419 | SetParent(anotherC) 420 | if svc := GetService[service6](); svc == nil { 421 | t.Error("service should found in parent") 422 | return 423 | } 424 | if svc := GetService[*serviceInstance6](); svc == nil { 425 | t.Error("service should found in parent") 426 | return 427 | } 428 | SetParent(nil) 429 | if svc := GetService[service6](); svc == nil { 430 | t.Error("service should found in parent") 431 | return 432 | } 433 | if svc := GetService[*serviceInstance6](); svc == nil { 434 | t.Error("service should found in parent") 435 | return 436 | } 437 | SetParent(anotherC) 438 | if svc := GetService[service6](); svc == nil { 439 | t.Error("service should found in parent") 440 | return 441 | } 442 | if svc := GetService[*serviceInstance6](); svc == nil { 443 | t.Error("service should found in parent") 444 | return 445 | } 446 | }) 447 | 448 | t.Run("set parent twice, last parent should been new parent's parent ", func(t *testing.T) { 449 | globalContainer = New() 450 | 451 | anotherC := New() 452 | AddSingletonToC[service6](anotherC, &serviceInstance6{name: "instance6"}) 453 | AddTransientToC[*serviceInstance6](anotherC, func() *serviceInstance6 { return &serviceInstance6{name: "instance6"} }) 454 | 455 | anotherC2 := New() 456 | AddSingletonToC[service5](anotherC2, &serviceInstance5{name: "instance5"}) 457 | AddTransientToC[*serviceInstance5](anotherC2, func() *serviceInstance5 { return &serviceInstance5{name: "instance5"} }) 458 | if svc := GetService[service6](); svc != nil { 459 | t.Error("service should not found in current") 460 | return 461 | } 462 | if svc := GetService[*serviceInstance6](); svc != nil { 463 | t.Error("service should not found in current") 464 | return 465 | } 466 | SetParent(anotherC) 467 | if svc := GetService[service6](); svc == nil { 468 | t.Error("service should found in parent") 469 | return 470 | } 471 | if svc := GetService[*serviceInstance6](); svc == nil { 472 | t.Error("service should found in parent") 473 | return 474 | } 475 | SetParent(anotherC2) 476 | if svc := GetService[service6](); svc == nil { 477 | t.Error("service should found in parent") 478 | return 479 | } 480 | if svc := GetService[*serviceInstance6](); svc == nil { 481 | t.Error("service should found in parent") 482 | return 483 | } 484 | if svc := GetService[service5](); svc == nil { 485 | t.Error("service should found in parent") 486 | return 487 | } 488 | if svc := GetService[*serviceInstance5](); svc == nil { 489 | t.Error("service should found in parent") 490 | return 491 | } 492 | }) 493 | } 494 | 495 | func TestContainerAddSingleton(t *testing.T) { 496 | t.Run("null service type should fail", func(t *testing.T) { 497 | globalContainer = New() 498 | 499 | c := New() 500 | err := c.AddSingleton(nil, nil) 501 | if err == nil { 502 | t.Error("service type should be null") 503 | return 504 | } 505 | }) 506 | 507 | t.Run("null service instance should fail", func(t *testing.T) { 508 | globalContainer = New() 509 | 510 | c := New() 511 | err := c.AddSingleton(reflect.TypeOf((*serviceInstance1)(nil)), nil) 512 | if err == nil { 513 | t.Error("null service instance should fail") 514 | return 515 | } 516 | }) 517 | 518 | t.Run("service instance should impletement service", func(t *testing.T) { 519 | globalContainer = New() 520 | 521 | c := New() 522 | err := c.AddSingleton(reflect.TypeOf((*service2)(nil)).Elem(), &serviceInstance1{}) 523 | if err == nil { 524 | t.Error("service instance should impletement service") 525 | return 526 | } 527 | }) 528 | } 529 | 530 | func TestContainerAddTransient(t *testing.T) { 531 | t.Run("", func(t *testing.T) { 532 | globalContainer = New() 533 | 534 | c := New() 535 | err := c.AddTransient(nil, nil) 536 | if err == nil { 537 | t.Error("null service type should fail") 538 | return 539 | } 540 | }) 541 | 542 | t.Run("null service instance factory should fail", func(t *testing.T) { 543 | globalContainer = New() 544 | 545 | c := New() 546 | err := c.AddTransient(reflect.TypeOf((*serviceInstance1)(nil)), nil) 547 | if err == nil { 548 | t.Error("null service instance factory should fail") 549 | return 550 | } 551 | }) 552 | } 553 | 554 | type service1 interface { 555 | GetName() string 556 | } 557 | 558 | type service2 interface { 559 | GetName() string 560 | Rename(name string) 561 | } 562 | 563 | type service3 interface { 564 | service1 565 | } 566 | 567 | type service4 interface { 568 | service2 569 | } 570 | 571 | type service5 interface { 572 | service1 573 | } 574 | 575 | type service6 interface { 576 | service2 577 | } 578 | 579 | type serviceInstance1 struct { 580 | name string 581 | } 582 | 583 | func (instance *serviceInstance1) GetName() string { 584 | return instance.name 585 | } 586 | 587 | type serviceInstance2 struct { 588 | name string 589 | } 590 | 591 | func (instance *serviceInstance2) GetName() string { 592 | return instance.name 593 | } 594 | 595 | func (instance *serviceInstance2) Rename(name string) { 596 | instance.name = name 597 | } 598 | 599 | type serviceInstance3 struct { 600 | name string 601 | } 602 | 603 | func (instance *serviceInstance3) GetName() string { 604 | return instance.name 605 | } 606 | 607 | type serviceInstance4 struct { 608 | name string 609 | } 610 | 611 | func (instance *serviceInstance4) GetName() string { 612 | return instance.name 613 | } 614 | 615 | func (instance *serviceInstance4) Rename(name string) { 616 | instance.name = name 617 | } 618 | 619 | type serviceInstance5 struct { 620 | name string 621 | } 622 | 623 | func (instance *serviceInstance5) GetName() string { 624 | return instance.name 625 | } 626 | 627 | type serviceInstance6 struct { 628 | name string 629 | } 630 | 631 | func (instance *serviceInstance6) GetName() string { 632 | return instance.name 633 | } 634 | 635 | func (instance *serviceInstance6) Rename(name string) { 636 | instance.name = name 637 | } 638 | 639 | type client struct { 640 | F1 service3 641 | F2 *serviceInstance3 642 | F3 service4 643 | F4 *serviceInstance4 644 | F5 service4 `ioc-inject:"true" ` 645 | F6 *serviceInstance4 `ioc-inject:"true" ` 646 | } 647 | 648 | func (c *client) Func1(p1 service3, p2 *serviceInstance3, p3 service4, p4 *serviceInstance4) { 649 | c.F1 = p1 650 | c.F2 = p2 651 | c.F3 = p3 652 | c.F4 = p4 653 | } 654 | 655 | type serviceInstance7 struct { 656 | name string 657 | } 658 | 659 | func (instance *serviceInstance7) GetName() string { 660 | return instance.name 661 | } 662 | 663 | type serviceInstance8 struct { 664 | s7 *serviceInstance7 665 | } 666 | 667 | func (instance *serviceInstance8) GetS7Name() string { 668 | return instance.s7.name 669 | } 670 | 671 | func (instance *serviceInstance8) Initialize(s7 *serviceInstance7) { 672 | instance.s7 = s7 673 | } 674 | 675 | type serviceInstance9 struct { 676 | name string 677 | s9 *serviceInstance9 678 | } 679 | 680 | func (instance *serviceInstance9) GetName() string { 681 | return instance.name 682 | } 683 | 684 | func (instance *serviceInstance9) Initialize(s9 *serviceInstance9) { 685 | instance.s9 = s9 686 | } 687 | 688 | type serviceInstance10 struct { 689 | name string 690 | s10 service1 691 | } 692 | 693 | func (instance *serviceInstance10) GetName() string { 694 | return instance.name 695 | } 696 | 697 | func (instance *serviceInstance10) Initialize(s10 service1) { 698 | instance.s10 = s10 699 | } 700 | 701 | type serviceInstance11 struct { 702 | name string 703 | s12 *serviceInstance12 704 | } 705 | 706 | func (instance *serviceInstance11) GetName() string { 707 | return instance.name 708 | } 709 | 710 | func (instance *serviceInstance11) InitializeMethodName() string { 711 | return "CustomInitialize" 712 | } 713 | 714 | func (instance *serviceInstance11) CustomInitialize(s12 *serviceInstance12) { 715 | instance.s12 = s12 716 | } 717 | 718 | type serviceInstance12 struct { 719 | name string 720 | } 721 | 722 | func (instance *serviceInstance12) GetName() string { 723 | return instance.name 724 | } 725 | --------------------------------------------------------------------------------