1 /** 2 * Contains the implementation of the dependency container. 3 * 4 * Part of the Poodinis Dependency Injection framework. 5 * 6 * Authors: 7 * Mike Bierlee, m.bierlee@lostmoment.com 8 * Copyright: 2014-2025 Mike Bierlee 9 * License: 10 * This software is licensed under the terms of the MIT license. 11 * The full terms of the license can be found in the LICENSE file. 12 */ 13 14 module poodinis.container; 15 16 import poodinis.registration : Registration, singleInstance, 17 toConcreteTypeListString, initializeFactoryType; 18 import poodinis.autowire : AutowiredRegistration, AutowireInstantiationContext; 19 import poodinis.factory : ConstructorInjectingInstanceFactory; 20 import poodinis.valueinjection : ValueInjectionException; 21 import poodinis.altphobos : isFunction; 22 import poodinis.imports : createImportsString; 23 24 import std.string : format; 25 import std.algorithm : canFind; 26 import std.traits : fullyQualifiedName, hasUDA, BaseTypeTuple; 27 import std.meta : AliasSeq; 28 29 debug (poodinisVerbose) { 30 import std.stdio : writeln; 31 } 32 33 /** 34 * Exception thrown when errors occur while resolving a type in a dependency container. 35 */ 36 class ResolveException : Exception { 37 this(string message, TypeInfo resolveType) { 38 super(format("Exception while resolving type %s: %s", resolveType.toString(), message)); 39 } 40 41 this(Throwable cause, TypeInfo resolveType) { 42 super(format("Exception while resolving type %s", resolveType.toString()), cause); 43 } 44 } 45 46 /** 47 * Exception thrown when errors occur while registering a type in a dependency container. 48 */ 49 class RegistrationException : Exception { 50 this(string message, TypeInfo registrationType) { 51 super(format("Exception while registering type %s: %s", 52 registrationType.toString(), message)); 53 } 54 } 55 56 /** 57 * Options which influence the process of registering dependencies 58 */ 59 enum RegistrationOption { 60 none = 0, 61 /** 62 * Prevent a concrete type being registered on itself. With this option you will always need 63 * to use the supertype as the type of the dependency. 64 */ 65 doNotAddConcreteTypeRegistration = 1 << 0, 66 } 67 68 /** 69 * Options which influence the process of resolving dependencies 70 */ 71 enum ResolveOption { 72 none = 0, 73 /** 74 * Registers the type you're trying to resolve before returning it. 75 * This essentially makes registration optional for resolving by concerete types. 76 * Resolinvg will still fail when trying to resolve a dependency by supertype. 77 */ 78 registerBeforeResolving = 1 << 0, 79 80 /** 81 * Does not throw a resolve exception when a type is not registered but will 82 * return null instead. If the type is an array, an empty array is returned instead. 83 */ 84 noResolveException = 1 << 1 85 } 86 87 /** 88 * Methods marked with this UDA within dependencies are called after that dependency 89 * is constructed by the dependency container. 90 * 91 * Multiple methods can be marked and will all be called after construction. The order in which 92 * methods are called is undetermined. Methods should have the signature void(void). 93 */ 94 struct PostConstruct { 95 } 96 97 /** 98 * Methods marked with this UDA within dependencies are called before the container 99 * loses the dependency's registration. 100 * 101 * This method is called when removeRegistration or clearAllRegistrations is called. 102 * It will also be called when the container's destructor is called. 103 */ 104 struct PreDestroy { 105 } 106 107 /** 108 * The dependency container maintains all dependencies registered with it. 109 * 110 * Dependencies registered by a container can be resolved as long as they are still registered with the container. 111 * Upon resolving a dependency, an instance is fetched according to a specific scope which dictates how instances of 112 * dependencies are created. Resolved dependencies will be autowired before being returned. 113 * 114 * In most cases you want to use a global singleton dependency container provided by getInstance() to manage all dependencies. 115 * You can still create new instances of this class for exceptional situations. 116 */ 117 synchronized class DependencyContainer { 118 private Registration[][TypeInfo] registrations; 119 120 private Registration[] autowireStack; 121 122 private RegistrationOption persistentRegistrationOptions; 123 private ResolveOption persistentResolveOptions; 124 125 ~this() { 126 clearAllRegistrations(); 127 } 128 129 /** 130 * Register a dependency by concrete class type. 131 * 132 * A dependency registered by concrete class type can only be resolved by concrete class type. 133 * No qualifiers can be used when resolving dependencies which are registered by concrete type. 134 * 135 * The default registration scope is "single instance" scope. 136 * 137 * Returns: 138 * A registration is returned which can be used to change the registration scope. 139 * 140 * Examples: 141 * Register and resolve a class by concrete type: 142 * --- 143 * class Cat : Animal { ... } 144 * container.register!Cat; 145 * --- 146 * 147 * See_Also: singleInstance, newInstance, existingInstance 148 */ 149 Registration register(ConcreteType)(RegistrationOption options = RegistrationOption.none) { 150 return register!(ConcreteType, ConcreteType)(options); 151 } 152 153 /** 154 * Register a dependency by super type. 155 * 156 * A dependency registered by super type can only be resolved by super type. A qualifier is typically 157 * used to resolve dependencies registered by super type. 158 * 159 * The default registration scope is "single instance" scope. 160 * 161 * Examples: 162 * Register and resolve by super type 163 * --- 164 * class Cat : Animal { ... } 165 * container.register!(Animal, Cat); 166 * --- 167 * 168 * See_Also: singleInstance, newInstance, existingInstance, RegistrationOption 169 */ 170 Registration register(SuperType, ConcreteType: 171 SuperType)(RegistrationOption options = RegistrationOption.none) 172 if (!is(ConcreteType == struct)) { 173 TypeInfo registeredType = typeid(SuperType); 174 TypeInfo_Class concreteType = typeid(ConcreteType); 175 176 debug (poodinisVerbose) { 177 writeln(format("DEBUG: Register type %s (as %s)", 178 concreteType.toString(), registeredType.toString())); 179 } 180 181 auto existingRegistration = getExistingRegistration(registeredType, concreteType); 182 if (existingRegistration) { 183 return existingRegistration; 184 } 185 186 auto instanceFactory = new ConstructorInjectingInstanceFactory!ConcreteType(this); 187 auto newRegistration = new AutowiredRegistration!ConcreteType(registeredType, 188 instanceFactory, this); 189 newRegistration.initializeFactoryType().singleInstance(); 190 191 static if (!is(SuperType == ConcreteType)) { 192 if (!hasOption(options, persistentRegistrationOptions, 193 RegistrationOption.doNotAddConcreteTypeRegistration)) { 194 auto concreteTypeRegistration = register!ConcreteType; 195 concreteTypeRegistration.linkTo(newRegistration); 196 } 197 } 198 199 registrations[registeredType] ~= cast(shared(Registration)) newRegistration; 200 return newRegistration; 201 } 202 203 private bool hasOption(OptionType)(OptionType options, 204 OptionType persistentOptions, OptionType option) { 205 return ((options | persistentOptions) & option) != 0; 206 } 207 208 private OptionType buildFlags(OptionType)(OptionType[] options) { 209 OptionType flags; 210 foreach (option; options) { 211 flags |= option; 212 } 213 return flags; 214 } 215 216 private Registration getExistingRegistration(TypeInfo registrationType, TypeInfo qualifierType) { 217 auto existingCandidates = registrationType in registrations; 218 if (existingCandidates) { 219 return getRegistration(cast(Registration[])*existingCandidates, qualifierType); 220 } 221 222 return null; 223 } 224 225 private Registration getRegistration(Registration[] candidates, TypeInfo concreteType) { 226 foreach (existingRegistration; candidates) { 227 if (existingRegistration.instanceType == concreteType) { 228 return existingRegistration; 229 } 230 } 231 232 return null; 233 } 234 235 /** 236 * Resolve dependencies. 237 * 238 * Dependencies can only resolved using this method if they are registered by concrete type or the only 239 * concrete type registered by super type. 240 * 241 * Resolved dependencies are automatically autowired before being returned. 242 * 243 * Returns: 244 * An instance is returned which is created according to the registration scope with which they are registered. 245 * 246 * Throws: 247 * ResolveException when type is not registered. 248 * 249 * Examples: 250 * Resolve dependencies registered by super type and concrete type: 251 * --- 252 * class Cat : Animal { ... } 253 * class Dog : Animal { ... } 254 * 255 * container.register!(Animal, Cat); 256 * container.register!Dog; 257 * 258 * container.resolve!Animal; 259 * container.resolve!Dog; 260 * --- 261 * You cannot resolve a dependency when it is registered by multiple super types: 262 * --- 263 * class Cat : Animal { ... } 264 * class Dog : Animal { ... } 265 * 266 * container.register!(Animal, Cat); 267 * container.register!(Animal, Dog); 268 * 269 * container.resolve!Animal; // Error: multiple candidates for type "Animal" 270 * container.resolve!Dog; // Error: No type is registered by concrete type "Dog", only by super type "Animal" 271 * --- 272 * You need to use the resolve method which allows you to specify a qualifier. 273 */ 274 RegistrationType resolve(RegistrationType)( 275 ResolveOption resolveOptions = ResolveOption.none) 276 if (!is(RegistrationType == struct)) { 277 return resolve!(RegistrationType, RegistrationType)(resolveOptions); 278 } 279 280 /** 281 * Resolve dependencies using a qualifier. 282 * 283 * Dependencies can only resolved using this method if they are registered by super type. 284 * 285 * Resolved dependencies are automatically autowired before being returned. 286 * 287 * Returns: 288 * An instance is returned which is created according to the registration scope with which they are registered. 289 * 290 * Throws: 291 * ResolveException when type is not registered or there are multiple candidates available for type. 292 * 293 * Examples: 294 * Resolve dependencies registered by super type: 295 * --- 296 * class Cat : Animal { ... } 297 * class Dog : Animal { ... } 298 * 299 * container.register!(Animal, Cat); 300 * container.register!(Animal, Dog); 301 * 302 * container.resolve!(Animal, Cat); 303 * container.resolve!(Animal, Dog); 304 * --- 305 */ 306 QualifierType resolve(RegistrationType, QualifierType: 307 RegistrationType)(ResolveOption resolveOptions = ResolveOption.none) 308 if (!is(QualifierType == struct)) { 309 TypeInfo resolveType = typeid(RegistrationType); 310 TypeInfo qualifierType = typeid(QualifierType); 311 312 debug (poodinisVerbose) { 313 writeln("DEBUG: Resolving type " ~ resolveType.toString() ~ " with qualifier " ~ qualifierType.toString()); 314 } 315 316 auto candidates = resolveType in registrations; 317 if (!candidates) { 318 static if (is(typeof(typeid(QualifierType)) == TypeInfo_Class) && !__traits(isAbstractClass, QualifierType)) { 319 if (hasOption(resolveOptions, persistentResolveOptions, ResolveOption 320 .registerBeforeResolving)) { 321 register!(RegistrationType, QualifierType)(); 322 return resolve!(RegistrationType, QualifierType)(resolveOptions); 323 } 324 } 325 326 if (hasOption(resolveOptions, persistentResolveOptions, 327 ResolveOption.noResolveException)) { 328 return null; 329 } 330 331 throw new ResolveException("Type not registered.", resolveType); 332 } 333 334 Registration registration = getQualifiedRegistration(resolveType, 335 qualifierType, cast(Registration[])*candidates); 336 337 try { 338 QualifierType newInstance = resolveAutowiredInstance!QualifierType(registration); 339 callPostConstructors(newInstance); 340 return newInstance; 341 } catch (ValueInjectionException e) { 342 throw new ResolveException(e, resolveType); 343 } 344 } 345 346 bool isRegistered(RegistrationType)() { 347 TypeInfo typeInfo = typeid(RegistrationType); 348 auto candidates = typeInfo in registrations; 349 return candidates !is null; 350 } 351 352 private QualifierType resolveAutowiredInstance(QualifierType)(Registration registration) { 353 QualifierType instance; 354 if (!(cast(Registration[]) autowireStack).canFind(registration)) { 355 autowireStack ~= cast(shared(Registration)) registration; 356 instance = cast(QualifierType) registration.getInstance( 357 new AutowireInstantiationContext()); 358 autowireStack = autowireStack[0 .. $ - 1]; 359 } else { 360 auto autowireContext = new AutowireInstantiationContext(); 361 autowireContext.autowireInstance = false; 362 instance = cast(QualifierType) registration.getInstance(autowireContext); 363 } 364 return instance; 365 } 366 367 /** 368 * Resolve all dependencies registered to a super type. 369 * 370 * Returns: 371 * An array of autowired instances is returned. The order is undetermined. 372 * 373 * Examples: 374 * --- 375 * class Cat : Animal { ... } 376 * class Dog : Animal { ... } 377 * 378 * container.register!(Animal, Cat); 379 * container.register!(Animal, Dog); 380 * 381 * Animal[] animals = container.resolveAll!Animal; 382 * --- 383 */ 384 RegistrationType[] resolveAll(RegistrationType)( 385 ResolveOption resolveOptions = ResolveOption.none) { 386 RegistrationType[] instances; 387 TypeInfo resolveType = typeid(RegistrationType); 388 389 auto qualifiedRegistrations = resolveType in registrations; 390 if (!qualifiedRegistrations) { 391 if (hasOption(resolveOptions, persistentResolveOptions, 392 ResolveOption.noResolveException)) { 393 return []; 394 } 395 396 throw new ResolveException("Type not registered.", resolveType); 397 } 398 399 foreach (registration; cast(Registration[])*qualifiedRegistrations) { 400 instances ~= resolveAutowiredInstance!RegistrationType(registration); 401 } 402 403 return instances; 404 } 405 406 private Registration getQualifiedRegistration(TypeInfo resolveType, 407 TypeInfo qualifierType, Registration[] candidates) { 408 if (resolveType == qualifierType) { 409 if (candidates.length > 1) { 410 string candidateList = candidates.toConcreteTypeListString(); 411 throw new ResolveException( 412 "Multiple qualified candidates available: " ~ candidateList ~ ". Please use a qualifier.", 413 resolveType); 414 } 415 416 return candidates[0]; 417 } 418 419 return getRegistration(candidates, qualifierType); 420 } 421 422 private void callPostConstructors(Type)(Type instance) { 423 static foreach (memberName; __traits(allMembers, Type)) { 424 static foreach (overload; __traits(getOverloads, Type, memberName)) { 425 static if (__traits(compiles, __traits(getProtection, overload)) 426 && __traits(getProtection, overload) == "public" 427 && isFunction!overload 428 && hasUDA!(overload, PostConstruct)) { 429 __traits(getMember, instance, memberName)(); 430 } 431 } 432 } 433 } 434 435 /** 436 * Clears all dependency registrations managed by this container. 437 */ 438 void clearAllRegistrations() { 439 foreach (registrationsOfType; registrations) { 440 callPreDestructorsOfRegistrations(registrationsOfType); 441 } 442 registrations.destroy(); 443 } 444 445 /** 446 * Removes a registered dependency by type. 447 * 448 * A dependency can be removed either by super type or concrete type, depending on how they are registered. 449 * 450 * Examples: 451 * --- 452 * container.removeRegistration!Animal; 453 * --- 454 */ 455 void removeRegistration(RegistrationType)() { 456 auto registrationsOfType = *(typeid(RegistrationType) in registrations); 457 callPreDestructorsOfRegistrations(registrationsOfType); 458 registrations.remove(typeid(RegistrationType)); 459 } 460 461 private void callPreDestructorsOfRegistrations(shared(Registration[]) registrations) { 462 foreach (registration; registrations) { 463 Registration unsharedRegistration = cast(Registration) registration; 464 if (unsharedRegistration.preDestructor !is null) { 465 unsharedRegistration.preDestructor()(); 466 } 467 } 468 } 469 470 /** 471 * Apply persistent registration options which will be used everytime register() is called. 472 */ 473 void setPersistentRegistrationOptions(RegistrationOption options) { 474 persistentRegistrationOptions = options; 475 } 476 477 /** 478 * Unsets all applied persistent registration options 479 */ 480 void unsetPersistentRegistrationOptions() { 481 persistentRegistrationOptions = RegistrationOption.none; 482 } 483 484 /** 485 * Apply persistent resolve options which will be used everytime resolve() is called. 486 */ 487 void setPersistentResolveOptions(ResolveOption options) { 488 persistentResolveOptions = options; 489 } 490 491 /** 492 * Unsets all applied persistent resolve options 493 */ 494 void unsetPersistentResolveOptions() { 495 persistentResolveOptions = ResolveOption.none; 496 } 497 498 } 499 500 version (unittest) : // 501 502 import poodinis; 503 import poodinis.testclasses; 504 import poodinis.foreigndependencies; 505 506 import std.exception; 507 import core.thread; 508 509 // Test register concrete type 510 unittest { 511 auto container = new shared DependencyContainer(); 512 auto registration = container.register!TestClass; 513 assert(registration.registeredType == typeid(TestClass), 514 "Type of registered type not the same"); 515 } 516 517 // Test resolve registered type 518 unittest { 519 auto container = new shared DependencyContainer(); 520 container.register!TestClass; 521 TestClass actualInstance = container.resolve!TestClass; 522 assert(actualInstance !is null, "Resolved type is null"); 523 assert(cast(TestClass) actualInstance, "Resolved class is not the same type as expected"); 524 } 525 526 // Test register interface 527 unittest { 528 auto container = new shared DependencyContainer(); 529 container.register!(TestInterface, TestClass); 530 TestInterface actualInstance = container.resolve!TestInterface; 531 assert(actualInstance !is null, "Resolved type is null"); 532 assert(cast(TestInterface) actualInstance, 533 "Resolved class is not the same type as expected"); 534 } 535 536 // Test resolve non-registered type 537 unittest { 538 auto container = new shared DependencyContainer(); 539 assertThrown!ResolveException(container.resolve!TestClass, 540 "Resolving non-registered type does not fail"); 541 } 542 543 // Test clear registrations 544 unittest { 545 auto container = new shared DependencyContainer(); 546 container.register!TestClass; 547 container.clearAllRegistrations(); 548 assertThrown!ResolveException(container.resolve!TestClass, 549 "Resolving cleared type does not fail"); 550 } 551 552 // Test resolve single instance for type 553 unittest { 554 auto container = new shared DependencyContainer(); 555 container.register!TestClass.singleInstance(); 556 auto instance1 = container.resolve!TestClass; 557 auto instance2 = container.resolve!TestClass; 558 assert(instance1 is instance2, 559 "Resolved instance from single instance scope is not the each time it is resolved"); 560 } 561 562 // Test resolve new instance for type 563 unittest { 564 auto container = new shared DependencyContainer(); 565 container.register!TestClass.newInstance(); 566 auto instance1 = container.resolve!TestClass; 567 auto instance2 = container.resolve!TestClass; 568 assert(instance1 !is instance2, 569 "Resolved instance from new instance scope is the same each time it is resolved"); 570 } 571 572 // Test resolve existing instance for type 573 unittest { 574 auto container = new shared DependencyContainer(); 575 auto expectedInstance = new TestClass(); 576 container.register!TestClass.existingInstance(expectedInstance); 577 auto actualInstance = container.resolve!TestClass; 578 assert(expectedInstance is actualInstance, 579 "Resolved instance from existing instance scope is not the same as the registered instance"); 580 } 581 582 // Test creating instance via custom initializer on resolve 583 unittest { 584 auto container = new shared DependencyContainer(); 585 auto expectedInstance = new TestClass(); 586 container.register!TestClass.initializedBy({ return expectedInstance; }); 587 auto actualInstance = container.resolve!TestClass; 588 assert(expectedInstance is actualInstance, 589 "Resolved instance does not come from the custom initializer"); 590 } 591 592 // Test creating instance via initializedBy creates new instance every time 593 unittest { 594 auto container = new shared DependencyContainer(); 595 container.register!TestClass.initializedBy({ return new TestClass(); }); 596 auto firstInstance = container.resolve!TestClass; 597 auto secondInstance = container.resolve!TestClass; 598 assert(firstInstance !is secondInstance, "Resolved instance are not different instances"); 599 } 600 601 // Test creating instance via initializedOnceBy creates a singleton instance 602 unittest { 603 auto container = new shared DependencyContainer(); 604 container.register!TestClass.initializedOnceBy({ return new TestClass(); }); 605 auto firstInstance = container.resolve!TestClass; 606 auto secondInstance = container.resolve!TestClass; 607 assert(firstInstance is secondInstance, "Resolved instance are different instances"); 608 } 609 610 // Test autowire resolved instances 611 unittest { 612 auto container = new shared DependencyContainer(); 613 container.register!AutowiredClass; 614 container.register!ComponentClass; 615 auto componentInstance = container.resolve!ComponentClass; 616 auto autowiredInstance = container.resolve!AutowiredClass; 617 assert(componentInstance.autowiredClass is autowiredInstance, 618 "Member is not autwired upon resolving"); 619 } 620 621 // Test circular autowiring 622 unittest { 623 auto container = new shared DependencyContainer(); 624 container.register!ComponentMouse; 625 container.register!ComponentCat; 626 auto mouse = container.resolve!ComponentMouse; 627 auto cat = container.resolve!ComponentCat; 628 assert(mouse.cat is cat && cat.mouse is mouse && mouse !is cat, 629 "Circular dependencies should be autowirable"); 630 } 631 632 // Test remove registration 633 unittest { 634 auto container = new shared DependencyContainer(); 635 container.register!TestClass; 636 container.removeRegistration!TestClass; 637 assertThrown!ResolveException(container.resolve!TestClass); 638 } 639 640 // Test autowiring does not autowire member where instance is non-null 641 unittest { 642 auto container = new shared DependencyContainer(); 643 auto existingA = new AutowiredClass(); 644 auto existingB = new ComponentClass(); 645 existingB.autowiredClass = existingA; 646 647 container.register!AutowiredClass; 648 container.register!ComponentClass.existingInstance(existingB); 649 auto resolvedA = container.resolve!AutowiredClass; 650 auto resolvedB = container.resolve!ComponentClass; 651 652 assert(resolvedB.autowiredClass is existingA && resolvedA !is existingA, 653 "Autowiring shouldn't rewire member when it is already wired to an instance"); 654 } 655 656 // Test autowiring circular dependency by third-degree 657 unittest { 658 auto container = new shared DependencyContainer(); 659 container.register!Eenie; 660 container.register!Meenie; 661 container.register!Moe; 662 663 auto eenie = container.resolve!Eenie; 664 665 assert(eenie.meenie.moe.eenie is eenie, 666 "Autowiring third-degree circular dependency failed"); 667 } 668 669 // Test autowiring deep circular dependencies 670 unittest { 671 auto container = new shared DependencyContainer(); 672 container.register!Ittie; 673 container.register!Bittie; 674 container.register!Bunena; 675 676 auto ittie = container.resolve!Ittie; 677 678 assert(ittie.bittie is ittie.bittie.banana.bittie, "Autowiring deep dependencies failed."); 679 } 680 681 // Test autowiring deep circular dependencies with newInstance scope does not autowire new instance second time 682 unittest { 683 auto container = new shared DependencyContainer(); 684 container.register!Ittie.newInstance(); 685 container.register!Bittie.newInstance(); 686 container.register!Bunena.newInstance(); 687 688 auto ittie = container.resolve!Ittie; 689 690 assert(ittie.bittie.banana.bittie.banana is null, 691 "Autowiring deep dependencies with newInstance scope autowired a reoccuring type."); 692 } 693 694 // Test autowiring type registered by interface 695 unittest { 696 auto container = new shared DependencyContainer(); 697 container.register!Bunena; 698 container.register!Bittie; 699 container.register!(SuperInterface, SuperImplementation); 700 701 SuperImplementation superInstance = cast(SuperImplementation) container 702 .resolve!SuperInterface; 703 704 assert(!(superInstance.banana is null), 705 "Instance which was resolved by interface type was not autowired."); 706 } 707 708 // Test reusing a container after clearing all registrations 709 unittest { 710 auto container = new shared DependencyContainer(); 711 container.register!Banana; 712 container.clearAllRegistrations(); 713 try { 714 container.resolve!Banana; 715 } catch (ResolveException e) { 716 container.register!Banana; 717 return; 718 } 719 assert(false); 720 } 721 722 // Test register multiple concrete classess to same interface type 723 unittest { 724 auto container = new shared DependencyContainer(); 725 container.register!(Color, Blue); 726 container.register!(Color, Red); 727 } 728 729 // Test removing all registrations for type with multiple registrations. 730 unittest { 731 auto container = new shared DependencyContainer(); 732 container.register!(Color, Blue); 733 container.register!(Color, Red); 734 container.removeRegistration!Color; 735 } 736 737 // Test registering same registration again 738 unittest { 739 auto container = new shared DependencyContainer(); 740 auto firstRegistration = container.register!(Color, Blue); 741 auto secondRegistration = container.register!(Color, Blue); 742 743 assert(firstRegistration is secondRegistration, 744 "First registration is not the same as the second of equal types"); 745 } 746 747 // Test resolve registration with multiple qualifiers 748 unittest { 749 auto container = new shared DependencyContainer(); 750 container.register!(Color, Blue); 751 container.register!(Color, Red); 752 try { 753 container.resolve!Color; 754 } catch (ResolveException e) { 755 return; 756 } 757 assert(false); 758 } 759 760 // Test resolve registration with multiple qualifiers using a qualifier 761 unittest { 762 auto container = new shared DependencyContainer(); 763 container.register!(Color, Blue); 764 container.register!(Color, Red); 765 auto blueInstance = container.resolve!(Color, Blue); 766 auto redInstance = container.resolve!(Color, Red); 767 768 assert(blueInstance !is redInstance, 769 "Resolving type with multiple, different registrations yielded the same instance"); 770 assert(blueInstance !is null, "Resolved blue instance to null"); 771 assert(redInstance !is null, "Resolved red instance to null"); 772 } 773 774 // Test autowire of unqualified member typed by interface. 775 unittest { 776 auto container = new shared DependencyContainer(); 777 container.register!Spiders; 778 container.register!(TestInterface, TestClass); 779 780 auto instance = container.resolve!Spiders; 781 782 assert(!(instance is null), "Container failed to autowire member by interface"); 783 } 784 785 // Register existing registration 786 unittest { 787 auto container = new shared DependencyContainer(); 788 789 auto firstRegistration = container.register!TestClass; 790 auto secondRegistration = container.register!TestClass; 791 792 assert(firstRegistration is secondRegistration, 793 "Registering the same registration twice registers the dependencies twice."); 794 } 795 796 // Register existing registration by supertype 797 unittest { 798 auto container = new shared DependencyContainer(); 799 800 auto firstRegistration = container.register!(TestInterface, TestClass); 801 auto secondRegistration = container.register!(TestInterface, TestClass); 802 803 assert(firstRegistration is secondRegistration, 804 "Registering the same registration by super type twice registers the dependencies twice."); 805 } 806 807 // Resolve dependency depending on itself 808 unittest { 809 auto container = new shared DependencyContainer(); 810 container.register!Recursive; 811 812 auto instance = container.resolve!Recursive; 813 814 assert(instance.recursive is instance, "Resolving dependency that depends on itself fails."); 815 assert(instance.recursive.recursive is instance, 816 "Resolving dependency that depends on itself fails."); 817 } 818 819 // Test autowire stack pop-back 820 unittest { 821 auto container = new shared DependencyContainer(); 822 container.register!Moolah; 823 container.register!Wants.newInstance(); 824 container.register!John; 825 826 container.resolve!Wants; 827 auto john = container.resolve!John; 828 829 assert(john.wants.moolah !is null, "Autowire stack did not clear entries properly"); 830 } 831 832 // Test resolving registration registered in different thread 833 unittest { 834 auto container = new shared DependencyContainer(); 835 836 auto thread = new Thread(delegate() { container.register!TestClass; }); 837 thread.start(); 838 thread.join(); 839 840 container.resolve!TestClass; 841 } 842 843 // Test resolving instance previously resolved in different thread 844 unittest { 845 auto container = new shared DependencyContainer(); 846 shared(TestClass) actualTestClass; 847 848 container.register!TestClass; 849 850 auto thread = new Thread(delegate() { 851 actualTestClass = cast(shared(TestClass)) container.resolve!TestClass; 852 }); 853 thread.start(); 854 thread.join(); 855 856 shared(TestClass) expectedTestClass = cast(shared(TestClass)) container.resolve!TestClass; 857 858 assert(expectedTestClass is actualTestClass, 859 "Instance resolved in main thread is not the one resolved in thread"); 860 } 861 862 // Test registering type with option doNotAddConcreteTypeRegistration 863 unittest { 864 auto container = new shared DependencyContainer(); 865 container.register!(TestInterface, 866 TestClass)(RegistrationOption.doNotAddConcreteTypeRegistration); 867 868 auto firstInstance = container.resolve!TestInterface; 869 assertThrown!ResolveException(container.resolve!TestClass); 870 } 871 872 // Test registering conrete type with registration option doNotAddConcreteTypeRegistration does nothing 873 unittest { 874 auto container = new shared DependencyContainer(); 875 container.register!TestClass(RegistrationOption.doNotAddConcreteTypeRegistration); 876 container.resolve!TestClass; 877 } 878 879 // Test registering type will register by contrete type by default 880 unittest { 881 auto container = new shared DependencyContainer(); 882 container.register!(TestInterface, TestClass); 883 884 auto firstInstance = container.resolve!TestInterface; 885 auto secondInstance = container.resolve!TestClass; 886 887 assert(firstInstance is secondInstance); 888 } 889 890 // Test resolving all registrations to an interface 891 unittest { 892 auto container = new shared DependencyContainer(); 893 container.register!(Color, Blue); 894 container.register!(Color, Red); 895 896 auto colors = container.resolveAll!Color; 897 898 assert(colors.length == 2, "resolveAll did not yield all instances of interface type"); 899 } 900 901 // Test autowiring instances resolved in array 902 unittest { 903 auto container = new shared DependencyContainer(); 904 container.register!UnrelatedClass; 905 container.register!(TestInterface, TestClassDeux); 906 907 auto instances = container.resolveAll!TestInterface; 908 auto instance = cast(TestClassDeux) instances[0]; 909 910 assert(instance.unrelated !is null); 911 } 912 913 // Test set persistent registration options 914 unittest { 915 auto container = new shared DependencyContainer(); 916 container.setPersistentRegistrationOptions( 917 RegistrationOption.doNotAddConcreteTypeRegistration); 918 container.register!(TestInterface, TestClass); 919 assertThrown!ResolveException(container.resolve!TestClass); 920 } 921 922 // Test unset persistent registration options 923 unittest { 924 auto container = new shared DependencyContainer(); 925 container.setPersistentRegistrationOptions( 926 RegistrationOption.doNotAddConcreteTypeRegistration); 927 container.unsetPersistentRegistrationOptions(); 928 container.register!(TestInterface, TestClass); 929 container.resolve!TestClass; 930 } 931 932 // Test registration when resolving 933 unittest { 934 auto container = new shared DependencyContainer(); 935 container.resolve!(TestInterface, TestClass)(ResolveOption.registerBeforeResolving); 936 container.resolve!TestClass; 937 } 938 939 // Test set persistent resolve options 940 unittest { 941 auto container = new shared DependencyContainer(); 942 container.setPersistentResolveOptions(ResolveOption.registerBeforeResolving); 943 container.resolve!TestClass; 944 } 945 946 // Test unset persistent resolve options 947 unittest { 948 auto container = new shared DependencyContainer(); 949 container.setPersistentResolveOptions(ResolveOption.registerBeforeResolving); 950 container.unsetPersistentResolveOptions(); 951 assertThrown!ResolveException(container.resolve!TestClass); 952 } 953 954 // Test ResolveOption registerBeforeResolving fails for interfaces 955 unittest { 956 auto container = new shared DependencyContainer(); 957 assertThrown!ResolveException( 958 container.resolve!TestInterface(ResolveOption.registerBeforeResolving)); 959 } 960 961 // Test ResolveOption noResolveException does not throw 962 unittest { 963 auto container = new shared DependencyContainer(); 964 auto instance = container.resolve!TestInterface(ResolveOption.noResolveException); 965 assert(instance is null); 966 } 967 968 // ResolveOption noResolveException does not throw for resolveAll 969 unittest { 970 auto container = new shared DependencyContainer(); 971 auto instances = container.resolveAll!TestInterface(ResolveOption.noResolveException); 972 assert(instances.length == 0); 973 } 974 975 // Test autowired, constructor injected class 976 unittest { 977 auto container = new shared DependencyContainer(); 978 container.register!Red; 979 container.register!Moolah; 980 container.register!Cocktail; 981 982 auto instance = container.resolve!Cocktail; 983 984 assert(instance !is null); 985 assert(instance.moolah is container.resolve!Moolah); 986 assert(instance.red is container.resolve!Red); 987 } 988 989 // Test autowired, constructor injected class where constructor argument is templated 990 unittest { 991 auto container = new shared DependencyContainer(); 992 container.register!PieChart; 993 container.register!(TemplatedComponent!PieChart); 994 container.register!(ClassWithTemplatedConstructorArg!PieChart); 995 auto instance = container.resolve!(ClassWithTemplatedConstructorArg!PieChart); 996 997 assert(instance !is null); 998 assert(instance.dependency !is null); 999 assert(instance.dependency.instance !is null); 1000 } 1001 1002 // Test injecting constructor with super-type parameter 1003 unittest { 1004 auto container = new shared DependencyContainer(); 1005 container.register!Wallpaper; 1006 container.register!(Color, Blue); 1007 1008 auto instance = container.resolve!Wallpaper; 1009 assert(instance !is null); 1010 assert(instance.color is container.resolve!Blue); 1011 } 1012 1013 // Test prevention of circular dependencies during constructor injection 1014 unittest { 1015 auto container = new shared DependencyContainer(); 1016 container.register!Pot; 1017 container.register!Kettle; 1018 1019 assertThrown!InstanceCreationException(container.resolve!Pot); 1020 } 1021 1022 // Test prevention of transitive circular dependencies during constructor injection 1023 unittest { 1024 auto container = new shared DependencyContainer(); 1025 container.register!Rock; 1026 container.register!Paper; 1027 container.register!Scissors; 1028 1029 assertThrown!InstanceCreationException(container.resolve!Rock); 1030 } 1031 1032 // Test injection of foreign dependency in constructor 1033 unittest { 1034 auto container = new shared DependencyContainer(); 1035 container.register!Ola; 1036 container.register!Hello; 1037 container.resolve!Hello; 1038 } 1039 1040 // Test PostConstruct method is called after resolving a dependency 1041 unittest { 1042 auto container = new shared DependencyContainer(); 1043 container.register!PostConstructionDependency; 1044 1045 auto instance = container.resolve!PostConstructionDependency; 1046 assert(instance.postConstructWasCalled == true); 1047 } 1048 1049 // Test PostConstruct of base type is called 1050 unittest { 1051 auto container = new shared DependencyContainer(); 1052 container.register!ChildOfPostConstruction; 1053 1054 auto instance = container.resolve!ChildOfPostConstruction; 1055 assert(instance.postConstructWasCalled == true); 1056 } 1057 1058 // Test PostConstruct of class implementing interface is not called 1059 unittest { 1060 auto container = new shared DependencyContainer(); 1061 container.register!ButThereWontBe; 1062 1063 auto instance = container.resolve!ButThereWontBe; 1064 assert(instance.postConstructWasCalled == false); 1065 } 1066 1067 // Test postconstruction happens after autowiring and value injection 1068 unittest { 1069 auto container = new shared DependencyContainer(); 1070 container.register!(ValueInjector!int, PostConstructingIntInjector); 1071 container.register!PostConstructionDependency; 1072 container.register!PostConstructWithAutowiring; 1073 auto instance = container.resolve!PostConstructWithAutowiring; 1074 } 1075 1076 // Test PreDestroy is called when removing a registration 1077 unittest { 1078 auto container = new shared DependencyContainer(); 1079 container.register!PreDestroyerOfFates; 1080 auto instance = container.resolve!PreDestroyerOfFates; 1081 container.removeRegistration!PreDestroyerOfFates; 1082 assert(instance.preDestroyWasCalled == true); 1083 } 1084 1085 // Test PreDestroy is called when removing all registrations 1086 unittest { 1087 auto container = new shared DependencyContainer(); 1088 container.register!PreDestroyerOfFates; 1089 auto instance = container.resolve!PreDestroyerOfFates; 1090 container.clearAllRegistrations(); 1091 assert(instance.preDestroyWasCalled == true); 1092 } 1093 1094 // Test PreDestroy is called when the container is destroyed 1095 unittest { 1096 auto container = new shared DependencyContainer(); 1097 container.register!PreDestroyerOfFates; 1098 auto instance = container.resolve!PreDestroyerOfFates; 1099 container.destroy(); 1100 1101 assert(instance.preDestroyWasCalled == true); 1102 } 1103 1104 // Test register class by ancestor type 1105 unittest { 1106 auto container = new shared DependencyContainer(); 1107 container.register!(Grandma, Kid); 1108 auto instance = container.resolve!Grandma; 1109 1110 assert(instance !is null); 1111 } 1112 1113 // Test autowiring classes with recursive template parameters 1114 unittest { 1115 auto container = new shared DependencyContainer(); 1116 container.register!CircularTemplateComponentA; 1117 container.register!CircularTemplateComponentB; 1118 1119 auto componentA = container.resolve!CircularTemplateComponentA; 1120 auto componentB = container.resolve!CircularTemplateComponentB; 1121 1122 assert(componentA !is null); 1123 assert(componentB !is null); 1124 1125 assert(componentA.instance is componentB); 1126 assert(componentB.instance is componentA); 1127 } 1128 1129 // Test autowiring class where a method is marked with @Autowire does nothing 1130 unittest { 1131 // It should also not show deprecation warning: 1132 // Deprecation: `__traits(getAttributes)` may only be used for individual functions, not overload sets such as: `lala` 1133 // the result of `__traits(getOverloads)` may be used to select the desired function to extract attributes from 1134 1135 auto container = new shared DependencyContainer(); 1136 container.register!AutowiredMethod; 1137 auto instance = container.resolve!AutowiredMethod; 1138 1139 assert(instance !is null); 1140 assert(instance.lala == 42); 1141 assert(instance.lala(77) == 77); 1142 } 1143 1144 // Test autowiring using @Autowire attribute 1145 unittest { 1146 auto container = new shared DependencyContainer(); 1147 container.register!ComponentA; 1148 container.register!WithAutowireAttribute; 1149 1150 auto instance = container.resolve!WithAutowireAttribute; 1151 assert(instance.componentA is container.resolve!ComponentA); 1152 }