1 /** 2 * Contains functionality for autowiring dependencies using a dependency container. 3 * 4 * This module is used in a dependency container for autowiring dependencies when resolving them. 5 * You typically only need this module if you want inject dependencies into a class instance not 6 * managed by a dependency container. 7 * 8 * Part of the Poodinis Dependency Injection framework. 9 * 10 * Authors: 11 * Mike Bierlee, m.bierlee@lostmoment.com 12 * Copyright: 2014-2025 Mike Bierlee 13 * License: 14 * This software is licensed under the terms of the MIT license. 15 * The full terms of the license can be found in the LICENSE file. 16 */ 17 18 module poodinis.autowire; 19 20 import poodinis.container : DependencyContainer, PreDestroy, ResolveException, ResolveOption; 21 import poodinis.registration : Registration, InstantiationContext; 22 import poodinis.factory : InstanceFactory, InstanceFactoryParameters, CreatesSingleton; 23 import poodinis.valueinjection : ValueInjector, ValueInjectionException, 24 ValueNotAvailableException, Value, MandatoryValue; 25 import poodinis.altphobos : isFunction; 26 import poodinis.imports : createImportsString; 27 28 import std.exception : enforce; 29 import std.string : format; 30 import std.traits : BaseClassesTuple, FieldNameTuple, fullyQualifiedName, hasUDA, isDynamicArray; 31 import std.range : ElementType; 32 33 debug { 34 import std.stdio : writeln; 35 } 36 37 private struct UseMemberType { 38 } 39 40 /** 41 * UDA for annotating class members as candidates for autowiring. 42 * 43 * Optionally a template parameter can be supplied to specify the type of a qualified class. The qualified type 44 * of a concrete class is used to autowire members declared by supertype. If no qualifier is supplied, the type 45 * of the member is used as qualifier. 46 * 47 * Note: @Autowire is considered legacy, but not deprecated. Using @Inject is preferred. 48 * 49 * Examples: 50 * Annotate member of class to be autowired: 51 * --- 52 * class Car { 53 * @Autowire 54 * Engine engine; 55 * } 56 * --- 57 * 58 * Annotate member of class with qualifier: 59 * --- 60 * class FuelEngine : Engine { ... } 61 * class ElectricEngine : Engine { ... } 62 * 63 * class HybridCar { 64 * @Autowire!FuelEngine 65 * Engine fuelEngine; 66 * 67 * @Autowire!ElectricEngine 68 * Engine electricEngine; 69 * } 70 * --- 71 * The members of an instance of "HybridCar" will now be autowired properly, because the autowire mechanism will 72 * autowire member "fuelEngine" as if it's of type "FuelEngine". This means that the members of instance "fuelEngine" 73 * will also be autowired because the autowire mechanism knows that member "fuelEngine" is an instance of "FuelEngine" 74 * 75 * See_Also: Inject 76 */ 77 struct Autowire(QualifierType) { 78 QualifierType qualifier; 79 } 80 81 /** 82 * UDA for marking autowired dependencies optional. 83 * Optional dependencies will not lead to a resolveException when there is no type registered for them. 84 * The member will remain null. 85 */ 86 struct OptionalDependency { 87 } 88 89 /** 90 * UDA for annotating class members to be autowired with a new instance regardless of their registration scope. 91 * 92 * Examples: 93 *--- 94 * class Car { 95 * @Autowire 96 * @AssignNewInstance 97 * Antenna antenna; 98 * } 99 *--- 100 * antenna will always be assigned a new instance of class Antenna. 101 */ 102 struct AssignNewInstance { 103 } 104 105 private void printDebugAutowiredInstance(TypeInfo instanceType, void* instanceAddress) { 106 debug { 107 writeln(format("DEBUG: Autowiring members of [%s@%s]", instanceType, instanceAddress)); 108 } 109 } 110 111 /** 112 * Autowires members of a given instance using dependencies registered in the given container. 113 * 114 * All members of the given instance, which are annotated using the "Autowire" UDA, are autowired. 115 * Members can have any visibility (public, private, etc). All members are resolved using the given 116 * container. Qualifiers are used to determine the type of class to resolve for any member of instance. 117 * 118 * See_Also: Autowire 119 */ 120 void autowire(Type)(shared(DependencyContainer) container, Type instance, bool isExistingInstance = false) { 121 debug (poodinisVerbose) { 122 printDebugAutowiredInstance(typeid(Type), &instance); 123 } 124 125 // Recurse into base class if there are more between Type and Object in the hierarchy 126 static if (BaseClassesTuple!Type.length > 1) { 127 autowire!(BaseClassesTuple!Type[0])(container, instance, isExistingInstance); 128 } 129 130 foreach (index, name; FieldNameTuple!Type) { 131 autowireMember!(name, index, Type)(container, instance, isExistingInstance); 132 } 133 } 134 135 private void printDebugAutowiringCandidate(TypeInfo candidateInstanceType, 136 void* candidateInstanceAddress, TypeInfo instanceType, void* instanceAddress, string member) { 137 debug { 138 writeln(format("DEBUG: Autowired instance [%s@%s] to [%s@%s].%s", candidateInstanceType, 139 candidateInstanceAddress, instanceType, instanceAddress, member)); 140 } 141 } 142 143 private void printDebugAutowiringArray(TypeInfo superTypeInfo, 144 TypeInfo instanceType, void* instanceAddress, string member) { 145 debug { 146 writeln(format("DEBUG: Autowired all registered instances of super type %s to [%s@%s].%s", 147 superTypeInfo, instanceType, instanceAddress, member)); 148 } 149 } 150 151 private void autowireMember(string member, size_t memberIndex, Type)( 152 shared(DependencyContainer) container, Type instance, bool isExistingInstance) { 153 foreach (attribute; __traits(getAttributes, Type.tupleof[memberIndex])) { 154 static if (is(attribute == Autowire!T, T)) { 155 injectInstance!(member, memberIndex, typeof(attribute.qualifier))(container, instance); 156 } else static if (__traits(isSame, attribute, Autowire)) { 157 injectInstance!(member, memberIndex, UseMemberType)(container, instance); 158 } else static if (is(typeof(attribute) == Value) || is(typeof(attribute) == MandatoryValue)) { 159 if (isExistingInstance) { 160 continue; 161 } 162 enum key = attribute.key; 163 enum isMandatory = is(typeof(attribute) == MandatoryValue); 164 injectValue!(member, memberIndex, key, isMandatory)(container, instance); 165 } 166 } 167 } 168 169 private void injectInstance(string member, size_t memberIndex, QualifierType, Type)( 170 shared(DependencyContainer) container, Type instance) { 171 if (instance.tupleof[memberIndex] is null) { 172 alias MemberType = typeof(Type.tupleof[memberIndex]); 173 enum isOptional = hasUDA!(Type.tupleof[memberIndex], OptionalDependency); 174 175 static if (isDynamicArray!MemberType) { 176 injectMultipleInstances!(member, memberIndex, isOptional, MemberType)(container, 177 instance); 178 } else { 179 injectSingleInstance!(member, memberIndex, isOptional, MemberType, QualifierType)(container, 180 instance); 181 } 182 } 183 } 184 185 private void injectMultipleInstances(string member, size_t memberIndex, 186 bool isOptional, MemberType, Type)(shared(DependencyContainer) container, Type instance) { 187 alias MemberElementType = ElementType!MemberType; 188 static if (isOptional) { 189 auto instances = container.resolveAll!MemberElementType(ResolveOption.noResolveException); 190 } else { 191 auto instances = container.resolveAll!MemberElementType; 192 } 193 194 instance.tupleof[memberIndex] = instances; 195 debug (poodinisVerbose) { 196 printDebugAutowiringArray(typeid(MemberElementType), typeid(Type), &instance, member); 197 } 198 } 199 200 private void injectSingleInstance(string member, size_t memberIndex, 201 bool isOptional, MemberType, QualifierType, Type)( 202 shared(DependencyContainer) container, Type instance) { 203 debug (poodinisVerbose) { 204 TypeInfo qualifiedInstanceType = typeid(MemberType); 205 } 206 207 enum assignNewInstance = hasUDA!(Type.tupleof[memberIndex], AssignNewInstance); 208 209 MemberType qualifiedInstance; 210 static if (!is(QualifierType == UseMemberType)) { 211 qualifiedInstance = createOrResolveInstance!(MemberType, QualifierType, 212 assignNewInstance, isOptional)(container); 213 debug (poodinisVerbose) { 214 qualifiedInstanceType = typeid(QualifierType); 215 } 216 } else { 217 qualifiedInstance = createOrResolveInstance!(MemberType, MemberType, 218 assignNewInstance, isOptional)(container); 219 } 220 221 instance.tupleof[memberIndex] = qualifiedInstance; 222 223 debug (poodinisVerbose) { 224 printDebugAutowiringCandidate(qualifiedInstanceType, 225 &qualifiedInstance, typeid(Type), &instance, member); 226 } 227 } 228 229 private QualifierType createOrResolveInstance(MemberType, QualifierType, 230 bool createNew, bool isOptional)(shared(DependencyContainer) container) { 231 static if (createNew) { 232 auto instanceFactory = new InstanceFactory(); 233 instanceFactory.factoryParameters = InstanceFactoryParameters(typeid(MemberType), 234 CreatesSingleton.no); 235 return cast(MemberType) instanceFactory.getInstance(); 236 } else { 237 static if (isOptional) { 238 return container.resolve!(MemberType, QualifierType)(ResolveOption.noResolveException); 239 } else { 240 return container.resolve!(MemberType, QualifierType); 241 } 242 } 243 } 244 245 private void injectValue(string member, size_t memberIndex, string key, bool mandatory, Type)( 246 shared(DependencyContainer) container, Type instance) { 247 alias MemberType = typeof(Type.tupleof[memberIndex]); 248 try { 249 auto injector = container.resolve!(ValueInjector!MemberType); 250 instance.tupleof[memberIndex] = injector.get(key); 251 debug (poodinisVerbose) { 252 printDebugValueInjection(typeid(Type), &instance, member, typeid(MemberType), key); 253 } 254 } catch (ResolveException e) { 255 throw new ValueInjectionException(format( 256 "Could not inject value of type %s into %s.%s: value injector is missing for this type.", 257 typeid(MemberType), typeid(Type), member)); 258 } catch (ValueNotAvailableException e) { 259 static if (mandatory) { 260 throw new ValueInjectionException(format("Could not inject value of type %s into %s.%s", 261 typeid(MemberType), typeid(Type), member), e); 262 } 263 } 264 } 265 266 private void printDebugValueInjection(TypeInfo instanceType, 267 void* instanceAddress, string member, TypeInfo valueType, string key) { 268 debug { 269 writeln(format("DEBUG: Injected value with key '%s' of type %s into [%s@%s].%s", 270 key, valueType, instanceType, instanceAddress, member)); 271 } 272 } 273 274 /** 275 * Autowire the given instance using the globally available dependency container. 276 * 277 * See_Also: DependencyContainer 278 * Deprecated: Using the global container is undesired. See DependencyContainer.getInstance(). 279 */ 280 deprecated void globalAutowire(Type)(Type instance) { 281 DependencyContainer.getInstance().autowire(instance); 282 } 283 284 class AutowiredRegistration(RegistrationType : Object) : Registration { 285 private shared(DependencyContainer) container; 286 287 this(TypeInfo registeredType, InstanceFactory instanceFactory, 288 shared(DependencyContainer) originatingContainer) { 289 super(registeredType, typeid(RegistrationType), instanceFactory, originatingContainer); 290 } 291 292 override Object getInstance( 293 InstantiationContext context = new AutowireInstantiationContext()) { 294 enforce(!(originatingContainer is null), 295 "The registration's originating container is null. There is no way to resolve autowire dependencies."); 296 297 RegistrationType instance = cast(RegistrationType) super.getInstance(context); 298 299 AutowireInstantiationContext autowireContext = cast(AutowireInstantiationContext) context; 300 enforce(!(autowireContext is null), 301 "Given instantiation context type could not be cast to an AutowireInstantiationContext. If you relied on using the default assigned context: make sure you're calling getInstance() on an instance of type AutowiredRegistration!"); 302 if (autowireContext.autowireInstance) { 303 bool isExistingInstance = instanceFactory.factoryParameters 304 .existingInstance !is null; 305 originatingContainer.autowire(instance, isExistingInstance); 306 } 307 308 this.preDestructor = getPreDestructor(instance); 309 310 return instance; 311 } 312 313 private void delegate() getPreDestructor(RegistrationType instance) { 314 void delegate() preDestructor = null; 315 static foreach (memberName; __traits(allMembers, RegistrationType)) { 316 static if (__traits(compiles, __traits(getOverloads, RegistrationType, memberName))) { 317 static foreach (overload; __traits(getOverloads, RegistrationType, memberName)) { 318 static if (__traits(compiles, __traits(getProtection, overload)) 319 && __traits(getProtection, overload) == "public" 320 && isFunction!overload 321 && hasUDA!(overload, PreDestroy)) { 322 preDestructor = &__traits(getMember, instance, memberName); 323 } 324 } 325 } 326 } 327 328 return preDestructor; 329 } 330 } 331 332 class AutowireInstantiationContext : InstantiationContext { 333 bool autowireInstance = true; 334 } 335 336 version (unittest) : // 337 338 import poodinis; 339 import poodinis.testclasses; 340 import std.exception; 341 342 // Test autowiring concrete type to existing instance 343 unittest { 344 auto container = new shared DependencyContainer(); 345 container.register!ComponentA; 346 auto componentB = new ComponentB(); 347 container.autowire(componentB); 348 assert(componentB !is null, "Autowirable dependency failed to autowire"); 349 } 350 351 // Test autowiring interface type to existing instance 352 unittest { 353 auto container = new shared DependencyContainer(); 354 container.register!(InterfaceA, ComponentC); 355 auto componentD = new ComponentD(); 356 container.autowire(componentD); 357 assert(componentD.componentC !is null, "Autowirable dependency failed to autowire"); 358 } 359 360 // Test autowiring private members 361 unittest { 362 auto container = new shared DependencyContainer(); 363 container.register!(InterfaceA, ComponentC); 364 auto componentD = new ComponentD(); 365 container.autowire(componentD); 366 assert(componentD.privateComponentC is componentD.componentC, 367 "Autowire private dependency failed"); 368 } 369 370 // Test autowiring will only happen once 371 unittest { 372 auto container = new shared DependencyContainer(); 373 container.register!(InterfaceA, ComponentC).newInstance(); 374 auto componentD = new ComponentD(); 375 container.autowire(componentD); 376 auto expectedComponent = componentD.componentC; 377 container.autowire(componentD); 378 auto actualComponent = componentD.componentC; 379 assert(expectedComponent is actualComponent, 380 "Autowiring the second time wired a different instance"); 381 } 382 383 // Test autowiring unregistered type 384 unittest { 385 auto container = new shared DependencyContainer(); 386 auto componentD = new ComponentD(); 387 assertThrown!(ResolveException)(container.autowire(componentD), 388 "Autowiring unregistered type should throw ResolveException"); 389 } 390 391 // Test autowiring member with non-autowire attribute does not autowire 392 unittest { 393 auto container = new shared DependencyContainer(); 394 auto componentE = new ComponentE(); 395 container.autowire(componentE); 396 assert(componentE.componentC is null, 397 "Autowiring should not occur for members with attributes other than @Autowire"); 398 } 399 400 // Test autowire class with alias declaration 401 unittest { 402 auto container = new shared DependencyContainer(); 403 container.register!ComponentA; 404 auto componentDeclarationCocktail = new ComponentDeclarationCocktail(); 405 406 container.autowire(componentDeclarationCocktail); 407 408 assert(componentDeclarationCocktail.componentA !is null, 409 "Autowiring class with non-assignable declarations failed"); 410 } 411 412 // Test autowire class with qualifier 413 unittest { 414 auto container = new shared DependencyContainer(); 415 container.register!(InterfaceA, ComponentC); 416 container.register!(InterfaceA, ComponentX); 417 auto componentX = container.resolve!(InterfaceA, ComponentX); 418 419 auto monkeyShine = new MonkeyShine(); 420 container.autowire(monkeyShine); 421 422 assert(monkeyShine.component is componentX, "Autowiring class with qualifier failed"); 423 } 424 425 // Test autowire class with multiple qualifiers 426 unittest { 427 auto container = new shared DependencyContainer(); 428 container.register!(InterfaceA, ComponentC); 429 container.register!(InterfaceA, ComponentX); 430 auto componentC = container.resolve!(InterfaceA, ComponentC); 431 auto componentX = container.resolve!(InterfaceA, ComponentX); 432 433 auto bootstrapBootstrap = new BootstrapBootstrap(); 434 container.autowire(bootstrapBootstrap); 435 436 assert(bootstrapBootstrap.componentX is componentX, 437 "Autowiring class with multiple qualifiers failed"); 438 assert(bootstrapBootstrap.componentC is componentC, 439 "Autowiring class with multiple qualifiers failed"); 440 } 441 442 // Test getting instance from autowired registration will autowire instance 443 unittest { 444 auto container = new shared DependencyContainer(); 445 container.register!ComponentA; 446 447 auto registration = new AutowiredRegistration!ComponentB(typeid(ComponentB), 448 new InstanceFactory(), container).initializeFactoryType().singleInstance(); 449 auto instance = cast(ComponentB) registration.getInstance( 450 new AutowireInstantiationContext()); 451 452 assert(instance.componentA !is null); 453 } 454 455 // Test autowiring a dynamic array with all qualified types 456 unittest { 457 auto container = new shared DependencyContainer(); 458 container.register!(InterfaceA, ComponentC); 459 container.register!(InterfaceA, ComponentX); 460 461 auto lord = new LordOfTheComponents(); 462 container.autowire(lord); 463 464 assert(lord.components.length == 2, "Dynamic array was not autowired"); 465 } 466 467 // Test autowiring new instance of singleinstance registration with newInstance UDA 468 unittest { 469 auto container = new shared DependencyContainer(); 470 container.register!ComponentA; 471 472 auto regularComponentA = container.resolve!ComponentA; 473 auto charlie = new ComponentCharlie(); 474 475 container.autowire(charlie); 476 477 assert(charlie.componentA !is regularComponentA, 478 "Autowiring class with AssignNewInstance did not yield a different instance"); 479 } 480 481 // Test autowiring members from base class 482 unittest { 483 auto container = new shared DependencyContainer(); 484 container.register!ComponentA; 485 container.register!ComponentB; 486 container.register!ComponentZ; 487 488 auto instance = new ComponentZ(); 489 container.autowire(instance); 490 491 assert(instance.componentA !is null); 492 } 493 494 // Test autowiring optional dependencies 495 unittest { 496 auto container = new shared DependencyContainer(); 497 auto instance = new OuttaTime(); 498 499 container.autowire(instance); 500 501 assert(instance.interfaceA is null); 502 assert(instance.componentA is null); 503 assert(instance.componentCs is null); 504 } 505 506 // Test autowiring class using value injection 507 unittest { 508 auto container = new shared DependencyContainer(); 509 510 container.register!(ValueInjector!int, TestInjector); 511 container.register!ComponentA; 512 auto instance = new ValuedClass(); 513 514 container.autowire(instance); 515 516 assert(instance.intValue == 8); 517 assert(instance.unrelated !is null); 518 } 519 520 // Test that @Value is not injected at all for existingInstance (default value case) 521 unittest { 522 auto container = new shared DependencyContainer(); 523 container.register!(ValueInjector!int, TestInjector); 524 container.register!ComponentA; 525 auto instance1 = new ValuedClass(); 526 container.register!ValuedClass.existingInstance(instance1); 527 auto resolved1 = container.resolve!ValuedClass; 528 assert(resolved1 is instance1, "Should resolve to the same instance"); 529 assert(resolved1.intValue == int.init, "@Value should not inject for existingInstance, even if default"); 530 assert(resolved1.unrelated !is null, "Other dependencies should still be autowired"); 531 } 532 533 // Test that @Value is not injected at all for existingInstance (non-default value case) 534 unittest { 535 auto container = new shared DependencyContainer(); 536 container.register!(ValueInjector!int, TestInjector); 537 container.register!ComponentA; 538 auto instance2 = new ValuedClass(); 539 instance2.intValue = 42; 540 container.register!ValuedClass.existingInstance(instance2); 541 auto resolved2 = container.resolve!ValuedClass; 542 assert(resolved2 is instance2, "Should resolve to the same instance"); 543 assert(resolved2.intValue == 42, "@Value should not inject for existingInstance, even if non-default"); 544 assert(resolved2.unrelated !is null, "Other dependencies should still be autowired"); 545 }