1 /* 2 * Copyright (C) 2006 Google Inc. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.google.inject; 18 19 import static com.google.inject.Asserts.assertContains; 20 import static java.lang.annotation.RetentionPolicy.RUNTIME; 21 22 import com.google.common.collect.Iterables; 23 import com.google.common.collect.Maps; 24 import java.lang.annotation.ElementType; 25 import java.lang.annotation.Retention; 26 import java.lang.annotation.Target; 27 import java.util.ArrayList; 28 import java.util.List; 29 import java.util.Map; 30 import junit.framework.TestCase; 31 32 /** 33 * @author crazybob@google.com (Bob Lee) 34 * @author sameb@google.com (Sam Berlin) 35 */ 36 public class CircularDependencyTest extends TestCase { 37 38 @Override setUp()39 protected void setUp() throws Exception { 40 AImpl.nextId = 0; 41 BImpl.nextId = 0; 42 } 43 testCircularlyDependentConstructors()44 public void testCircularlyDependentConstructors() throws CreationException { 45 Injector injector = 46 Guice.createInjector( 47 new AbstractModule() { 48 @Override 49 protected void configure() { 50 bind(A.class).to(AImpl.class); 51 bind(B.class).to(BImpl.class); 52 } 53 }); 54 assertCircularDependencies(injector); 55 } 56 testCircularlyDependentConstructorsWithProviderMethods()57 public void testCircularlyDependentConstructorsWithProviderMethods() throws CreationException { 58 Injector injector = 59 Guice.createInjector( 60 new AbstractModule() { 61 62 @Provides 63 @Singleton 64 A a(B b) { 65 return new AImpl(b); 66 } 67 68 @Provides 69 B b(A a) { 70 return new BImpl(a); 71 } 72 }); 73 assertCircularDependencies(injector); 74 } 75 testCircularlyDependentConstructorsWithProviderInstances()76 public void testCircularlyDependentConstructorsWithProviderInstances() throws CreationException { 77 Injector injector = 78 Guice.createInjector( 79 new AbstractModule() { 80 @Override 81 protected void configure() { 82 bind(A.class) 83 .toProvider( 84 new Provider<A>() { 85 @Inject Provider<B> bp; 86 87 @Override 88 public A get() { 89 return new AImpl(bp.get()); 90 } 91 }) 92 .in(Singleton.class); 93 bind(B.class) 94 .toProvider( 95 new Provider<B>() { 96 @Inject Provider<A> ap; 97 98 @Override 99 public B get() { 100 return new BImpl(ap.get()); 101 } 102 }); 103 } 104 }); 105 assertCircularDependencies(injector); 106 } 107 testCircularlyDependentConstructorsWithProviderKeys()108 public void testCircularlyDependentConstructorsWithProviderKeys() throws CreationException { 109 Injector injector = 110 Guice.createInjector( 111 new AbstractModule() { 112 @Override 113 protected void configure() { 114 bind(A.class).toProvider(AP.class).in(Singleton.class); 115 bind(B.class).toProvider(BP.class); 116 } 117 }); 118 assertCircularDependencies(injector); 119 } 120 testCircularlyDependentConstructorsWithProvidedBy()121 public void testCircularlyDependentConstructorsWithProvidedBy() throws CreationException { 122 Injector injector = Guice.createInjector(); 123 assertCircularDependencies(injector); 124 } 125 assertCircularDependencies(Injector injector)126 private void assertCircularDependencies(Injector injector) { 127 A a = injector.getInstance(A.class); 128 assertNotNull(a.getB().getA()); 129 assertEquals(0, a.id()); 130 assertEquals(a.id(), a.getB().getA().id()); 131 assertEquals(0, a.getB().id()); 132 assertEquals(1, AImpl.nextId); 133 assertEquals(1, BImpl.nextId); 134 assertSame(a, injector.getInstance(A.class)); 135 } 136 137 @ProvidedBy(AutoAP.class) 138 public interface A { getB()139 B getB(); 140 id()141 int id(); 142 } 143 144 @Singleton 145 static class AImpl implements A { 146 static int nextId; 147 int id = nextId++; 148 149 final B b; 150 151 @Inject AImpl(B b)152 public AImpl(B b) { 153 this.b = b; 154 } 155 156 @Override id()157 public int id() { 158 return id; 159 } 160 161 @Override getB()162 public B getB() { 163 return b; 164 } 165 } 166 167 static class AP implements Provider<A> { 168 @Inject Provider<B> bp; 169 170 @Override get()171 public A get() { 172 return new AImpl(bp.get()); 173 } 174 } 175 176 @Singleton 177 static class AutoAP implements Provider<A> { 178 @Inject Provider<B> bp; 179 A a; 180 181 @Override get()182 public A get() { 183 if (a == null) { 184 a = new AImpl(bp.get()); 185 } 186 return a; 187 } 188 } 189 190 @ProvidedBy(BP.class) 191 public interface B { getA()192 A getA(); 193 id()194 int id(); 195 } 196 197 static class BImpl implements B { 198 static int nextId; 199 int id = nextId++; 200 201 final A a; 202 203 @Inject BImpl(A a)204 public BImpl(A a) { 205 this.a = a; 206 } 207 208 @Override id()209 public int id() { 210 return id; 211 } 212 213 @Override getA()214 public A getA() { 215 return a; 216 } 217 } 218 219 static class BP implements Provider<B> { 220 Provider<A> ap; 221 222 @Inject BP(Provider<A> ap)223 BP(Provider<A> ap) { 224 this.ap = ap; 225 } 226 227 @Override get()228 public B get() { 229 return new BImpl(ap.get()); 230 } 231 } 232 testUnresolvableCircularDependency()233 public void testUnresolvableCircularDependency() { 234 try { 235 Guice.createInjector().getInstance(C.class); 236 fail(); 237 } catch (ProvisionException expected) { 238 assertContains( 239 expected.getMessage(), 240 "Tried proxying " + C.class.getName() + " to support a circular dependency, ", 241 "but it is not an interface."); 242 } 243 } 244 testUnresolvableCircularDependenciesWithProviderInstances()245 public void testUnresolvableCircularDependenciesWithProviderInstances() { 246 try { 247 Guice.createInjector( 248 new AbstractModule() { 249 250 @Provides 251 C c(D d) { 252 return null; 253 } 254 255 @Provides 256 D d(C c) { 257 return null; 258 } 259 }) 260 .getInstance(C.class); 261 fail(); 262 } catch (ProvisionException expected) { 263 assertContains( 264 expected.getMessage(), 265 "Tried proxying " + C.class.getName() + " to support a circular dependency, ", 266 "but it is not an interface."); 267 } 268 } 269 testUnresolvableCircularDependenciesWithProviderKeys()270 public void testUnresolvableCircularDependenciesWithProviderKeys() { 271 try { 272 Guice.createInjector( 273 new AbstractModule() { 274 @Override 275 protected void configure() { 276 bind(C2.class).toProvider(C2P.class); 277 bind(D2.class).toProvider(D2P.class); 278 } 279 }) 280 .getInstance(C2.class); 281 fail(); 282 } catch (ProvisionException expected) { 283 assertContains( 284 expected.getMessage(), 285 "Tried proxying " + C2.class.getName() + " to support a circular dependency, ", 286 "but it is not an interface."); 287 } 288 } 289 testUnresolvableCircularDependenciesWithProvidedBy()290 public void testUnresolvableCircularDependenciesWithProvidedBy() { 291 try { 292 Guice.createInjector().getInstance(C2.class); 293 fail(); 294 } catch (ProvisionException expected) { 295 assertContains( 296 expected.getMessage(), 297 "Tried proxying " + C2.class.getName() + " to support a circular dependency, ", 298 "but it is not an interface."); 299 } 300 } 301 302 static class C { 303 @Inject C(D d)304 C(D d) {} 305 } 306 307 static class D { 308 @Inject D(C c)309 D(C c) {} 310 } 311 312 static class C2P implements Provider<C2> { 313 @Inject Provider<D2> dp; 314 315 @Override get()316 public C2 get() { 317 dp.get(); 318 return null; 319 } 320 } 321 322 static class D2P implements Provider<D2> { 323 @Inject Provider<C2> cp; 324 325 @Override get()326 public D2 get() { 327 cp.get(); 328 return null; 329 } 330 } 331 332 @ProvidedBy(C2P.class) 333 static class C2 { 334 @Inject C2(D2 d)335 C2(D2 d) {} 336 } 337 338 @ProvidedBy(D2P.class) 339 static class D2 { 340 @Inject D2(C2 c)341 D2(C2 c) {} 342 } 343 testDisabledCircularDependency()344 public void testDisabledCircularDependency() { 345 try { 346 Guice.createInjector( 347 new AbstractModule() { 348 @Override 349 protected void configure() { 350 binder().disableCircularProxies(); 351 } 352 }) 353 .getInstance(C.class); 354 fail(); 355 } catch (ProvisionException expected) { 356 assertContains( 357 expected.getMessage(), 358 "Found a circular dependency involving " 359 + C.class.getName() 360 + ", and circular dependencies are disabled."); 361 } 362 } 363 testDisabledCircularDependenciesWithProviderInstances()364 public void testDisabledCircularDependenciesWithProviderInstances() { 365 try { 366 Guice.createInjector( 367 new AbstractModule() { 368 @Override 369 protected void configure() { 370 binder().disableCircularProxies(); 371 } 372 373 @Provides 374 C c(D d) { 375 return null; 376 } 377 378 @Provides 379 D d(C c) { 380 return null; 381 } 382 }) 383 .getInstance(C.class); 384 fail(); 385 } catch (ProvisionException expected) { 386 assertContains( 387 expected.getMessage(), 388 "Found a circular dependency involving " 389 + C.class.getName() 390 + ", and circular dependencies are disabled."); 391 } 392 } 393 testDisabledCircularDependenciesWithProviderKeys()394 public void testDisabledCircularDependenciesWithProviderKeys() { 395 try { 396 Guice.createInjector( 397 new AbstractModule() { 398 @Override 399 protected void configure() { 400 binder().disableCircularProxies(); 401 bind(C2.class).toProvider(C2P.class); 402 bind(D2.class).toProvider(D2P.class); 403 } 404 }) 405 .getInstance(C2.class); 406 fail(); 407 } catch (ProvisionException expected) { 408 assertContains( 409 expected.getMessage(), 410 "Found a circular dependency involving " 411 + C2.class.getName() 412 + ", and circular dependencies are disabled."); 413 } 414 } 415 testDisabledCircularDependenciesWithProvidedBy()416 public void testDisabledCircularDependenciesWithProvidedBy() { 417 try { 418 Guice.createInjector( 419 new AbstractModule() { 420 @Override 421 protected void configure() { 422 binder().disableCircularProxies(); 423 } 424 }) 425 .getInstance(C2.class); 426 fail(); 427 } catch (ProvisionException expected) { 428 assertContains( 429 expected.getMessage(), 430 "Found a circular dependency involving " 431 + C2.class.getName() 432 + ", and circular dependencies are disabled."); 433 } 434 } 435 436 /** 437 * As reported by issue 349, we give a lousy trace when a class is circularly dependent on itself 438 * in multiple ways. 439 */ testCircularlyDependentMultipleWays()440 public void testCircularlyDependentMultipleWays() { 441 Injector injector = 442 Guice.createInjector( 443 new AbstractModule() { 444 @Override 445 protected void configure() { 446 binder.bind(A.class).to(E.class); 447 binder.bind(B.class).to(E.class); 448 } 449 }); 450 injector.getInstance(A.class); 451 } 452 testDisablingCircularDependencies()453 public void testDisablingCircularDependencies() { 454 Injector injector = 455 Guice.createInjector( 456 new AbstractModule() { 457 @Override 458 protected void configure() { 459 binder().disableCircularProxies(); 460 binder.bind(A.class).to(E.class); 461 binder.bind(B.class).to(E.class); 462 } 463 }); 464 465 try { 466 injector.getInstance(A.class); 467 fail("expected exception"); 468 } catch (ProvisionException expected) { 469 assertContains( 470 expected.getMessage(), 471 "Found a circular dependency involving " 472 + A.class.getName() 473 + ", and circular dependencies are disabled."); 474 } 475 } 476 477 @Singleton 478 static class E implements A, B { 479 @Inject E(A a, B b)480 public E(A a, B b) {} 481 482 @Override getB()483 public B getB() { 484 return this; 485 } 486 487 @Override getA()488 public A getA() { 489 return this; 490 } 491 492 @Override id()493 public int id() { 494 return 0; 495 } 496 } 497 testCircularDependencyProxyDelegateNeverInitialized()498 public void testCircularDependencyProxyDelegateNeverInitialized() { 499 Injector injector = 500 Guice.createInjector( 501 new AbstractModule() { 502 @Override 503 protected void configure() { 504 bind(F.class).to(RealF.class); 505 bind(G.class).to(RealG.class); 506 } 507 }); 508 F f = injector.getInstance(F.class); 509 assertEquals("F", f.g().f().toString()); 510 assertEquals("G", f.g().f().g().toString()); 511 } 512 513 public interface F { g()514 G g(); 515 } 516 517 @Singleton 518 public static class RealF implements F { 519 private final G g; 520 521 @Inject RealF(G g)522 RealF(G g) { 523 this.g = g; 524 } 525 526 @Override g()527 public G g() { 528 return g; 529 } 530 531 @Override toString()532 public String toString() { 533 return "F"; 534 } 535 } 536 537 public interface G { f()538 F f(); 539 } 540 541 @Singleton 542 public static class RealG implements G { 543 private final F f; 544 545 @Inject RealG(F f)546 RealG(F f) { 547 this.f = f; 548 } 549 550 @Override f()551 public F f() { 552 return f; 553 } 554 555 @Override toString()556 public String toString() { 557 return "G"; 558 } 559 } 560 561 /** 562 * Tests that ProviderInternalFactory can detect circular dependencies before it gets to 563 * Scopes.SINGLETON. This is especially important because the failure in Scopes.SINGLETON doesn't 564 * have enough context to provide a decent error message. 565 */ testCircularDependenciesDetectedEarlyWhenDependenciesHaveDifferentTypes()566 public void testCircularDependenciesDetectedEarlyWhenDependenciesHaveDifferentTypes() { 567 Injector injector = 568 Guice.createInjector( 569 new AbstractModule() { 570 @Override 571 protected void configure() { 572 bind(Number.class).to(Integer.class); 573 } 574 575 @Provides 576 @Singleton 577 Integer provideInteger(List list) { 578 return 2; 579 } 580 581 @Provides 582 List provideList(Integer integer) { 583 return new ArrayList(); 584 } 585 }); 586 try { 587 injector.getInstance(Number.class); 588 fail(); 589 } catch (ProvisionException expected) { 590 assertContains( 591 expected.getMessage(), 592 "Tried proxying " + Integer.class.getName() + " to support a circular dependency, ", 593 "but it is not an interface."); 594 } 595 } 596 testPrivateModulesDontTriggerCircularErrorsInProviders()597 public void testPrivateModulesDontTriggerCircularErrorsInProviders() { 598 Injector injector = 599 Guice.createInjector( 600 new AbstractModule() { 601 @Override 602 protected void configure() { 603 install( 604 new PrivateModule() { 605 @Override 606 protected void configure() { 607 bind(Foo.class); 608 expose(Foo.class); 609 } 610 611 @Provides 612 String provideString(Bar bar) { 613 return new String("private 1, " + bar.string); 614 } 615 }); 616 install( 617 new PrivateModule() { 618 @Override 619 protected void configure() { 620 bind(Bar.class); 621 expose(Bar.class); 622 } 623 624 @Provides 625 String provideString() { 626 return new String("private 2"); 627 } 628 }); 629 } 630 }); 631 Foo foo = injector.getInstance(Foo.class); 632 assertEquals("private 1, private 2", foo.string); 633 } 634 635 static class Foo { 636 @Inject String string; 637 } 638 639 static class Bar { 640 @Inject String string; 641 } 642 643 /** 644 * When Scope Providers call their unscoped Provider's get() methods are called, it's possible 645 * that the result is a circular proxy designed for one specific parameter (not for all possible 646 * parameters). But custom scopes typically cache the results without checking to see if the 647 * result is a proxy. This leads to caching a result that is unsuitable for reuse for other 648 * parameters. 649 * 650 * <p>This means that custom proxies have to do an {@code if(Scopes.isCircularProxy(..))} in order 651 * to avoid exceptions. 652 */ testCustomScopeCircularProxies()653 public void testCustomScopeCircularProxies() { 654 Injector injector = 655 Guice.createInjector( 656 new AbstractModule() { 657 @Override 658 protected void configure() { 659 bindScope(SimpleSingleton.class, new BasicSingleton()); 660 bind(H.class).to(HImpl.class); 661 bind(I.class).to(IImpl.class); 662 bind(J.class).to(JImpl.class); 663 } 664 }); 665 666 // The reason this happens is because the Scope gets these requests, in order: 667 // entry: Key<IImpl> (1 - from getInstance call) 668 // entry: Key<HImpl> 669 // entry: Key<IImpl> (2 - circular dependency from HImpl) 670 // result of 2nd Key<IImpl> - a com.google.inject.$Proxy, because it's a circular proxy 671 // result of Key<HImpl> - an HImpl 672 // entry: Key<JImpl> 673 // entry: Key<IImpl> (3 - another circular dependency, this time from JImpl) 674 // At this point, if the first Key<Impl> result was cached, our cache would have 675 // Key<IImpl> caching to an instanceof of I, but not an an instanceof of IImpl. 676 // If returned this, it would result in cglib giving a ClassCastException or 677 // java reflection giving an IllegalArgumentException when filling in parameters 678 // for the constructor, because JImpl wants an IImpl, not an I. 679 680 try { 681 injector.getInstance(IImpl.class); 682 fail(); 683 } catch (ProvisionException pe) { 684 assertContains( 685 Iterables.getOnlyElement(pe.getErrorMessages()).getMessage(), 686 "Tried proxying " 687 + IImpl.class.getName() 688 + " to support a circular dependency, but it is not an interface."); 689 } 690 } 691 692 interface H {} 693 694 interface I {} 695 696 interface J {} 697 698 @SimpleSingleton 699 static class HImpl implements H { 700 @Inject HImpl(I i)701 HImpl(I i) {} 702 } 703 704 @SimpleSingleton 705 static class IImpl implements I { 706 @Inject IImpl(HImpl i, J j)707 IImpl(HImpl i, J j) {} 708 } 709 710 @SimpleSingleton 711 static class JImpl implements J { 712 @Inject JImpl(IImpl i)713 JImpl(IImpl i) {} 714 } 715 716 @Target({ElementType.TYPE, ElementType.METHOD}) 717 @Retention(RUNTIME) 718 @ScopeAnnotation 719 public @interface SimpleSingleton {} 720 721 public static class BasicSingleton implements Scope { 722 private static Map<Key<?>, Object> cache = Maps.newHashMap(); 723 724 @Override scope(final Key<T> key, final Provider<T> unscoped)725 public <T> Provider<T> scope(final Key<T> key, final Provider<T> unscoped) { 726 return new Provider<T>() { 727 @Override 728 @SuppressWarnings("unchecked") 729 public T get() { 730 if (!cache.containsKey(key)) { 731 T t = unscoped.get(); 732 if (Scopes.isCircularProxy(t)) { 733 return t; 734 } 735 cache.put(key, t); 736 } 737 return (T) cache.get(key); 738 } 739 }; 740 } 741 } 742 743 public void testDisabledNonConstructorCircularDependencies() { 744 Injector injector = 745 Guice.createInjector( 746 new AbstractModule() { 747 @Override 748 protected void configure() { 749 binder().disableCircularProxies(); 750 } 751 }); 752 753 try { 754 injector.getInstance(K.class); 755 fail("expected exception"); 756 } catch (ProvisionException expected) { 757 assertContains( 758 expected.getMessage(), 759 "Found a circular dependency involving " 760 + K.class.getName() 761 + ", and circular dependencies are disabled."); 762 } 763 764 try { 765 injector.getInstance(L.class); 766 fail("expected exception"); 767 } catch (ProvisionException expected) { 768 assertContains( 769 expected.getMessage(), 770 "Found a circular dependency involving " 771 + L.class.getName() 772 + ", and circular dependencies are disabled."); 773 } 774 } 775 776 static class K { 777 @Inject L l; 778 } 779 780 static class L { 781 @Inject 782 void inject(K k) {} 783 } 784 } 785