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 25 import junit.framework.TestCase; 26 27 import java.lang.annotation.ElementType; 28 import java.lang.annotation.Retention; 29 import java.lang.annotation.Target; 30 import java.util.ArrayList; 31 import java.util.List; 32 import java.util.Map; 33 34 /** 35 * @author crazybob (at) google.com (Bob Lee) 36 * @author sameb (at) google.com (Sam Berlin) 37 */ 38 public class CircularDependencyTest extends TestCase { 39 40 @Override 41 protected void setUp() throws Exception { 42 AImpl.nextId = 0; 43 BImpl.nextId = 0; 44 } 45 46 public void testCircularlyDependentConstructors() 47 throws CreationException { 48 Injector injector = Guice.createInjector(new AbstractModule() { 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 57 public void testCircularlyDependentConstructorsWithProviderMethods() 58 throws CreationException { 59 Injector injector = Guice.createInjector(new AbstractModule() { 60 protected void configure() {} 61 62 @Provides @Singleton A a(B b) { return new AImpl(b); } 63 @Provides B b(A a) { return new BImpl(a); } 64 }); 65 assertCircularDependencies(injector); 66 } 67 68 public void testCircularlyDependentConstructorsWithProviderInstances() 69 throws CreationException { 70 Injector injector = Guice.createInjector(new AbstractModule() { 71 protected void configure() { 72 bind(A.class).toProvider(new Provider<A>() { 73 @Inject Provider<B> bp; 74 public A get() { 75 return new AImpl(bp.get()); 76 } 77 }).in(Singleton.class); 78 bind(B.class).toProvider(new Provider<B>() { 79 @Inject Provider<A> ap; 80 public B get() { 81 return new BImpl(ap.get()); 82 } 83 }); 84 } 85 }); 86 assertCircularDependencies(injector); 87 } 88 89 public void testCircularlyDependentConstructorsWithProviderKeys() 90 throws CreationException { 91 Injector injector = Guice.createInjector(new AbstractModule() { 92 protected void configure() { 93 bind(A.class).toProvider(AP.class).in(Singleton.class); 94 bind(B.class).toProvider(BP.class); 95 } 96 }); 97 assertCircularDependencies(injector); 98 } 99 100 public void testCircularlyDependentConstructorsWithProvidedBy() 101 throws CreationException { 102 Injector injector = Guice.createInjector(); 103 assertCircularDependencies(injector); 104 } 105 106 private void assertCircularDependencies(Injector injector) { 107 A a = injector.getInstance(A.class); 108 assertNotNull(a.getB().getA()); 109 assertEquals(0, a.id()); 110 assertEquals(a.id(), a.getB().getA().id()); 111 assertEquals(0, a.getB().id()); 112 assertEquals(1, AImpl.nextId); 113 assertEquals(1, BImpl.nextId); 114 assertSame(a, injector.getInstance(A.class)); 115 } 116 117 @ProvidedBy(AutoAP.class) 118 public interface A { 119 B getB(); 120 int id(); 121 } 122 123 @Singleton 124 static class AImpl implements A { 125 static int nextId; 126 int id = nextId++; 127 128 final B b; 129 @Inject public AImpl(B b) { 130 this.b = b; 131 } 132 public int id() { 133 return id; 134 } 135 public B getB() { 136 return b; 137 } 138 } 139 140 static class AP implements Provider<A> { 141 @Inject Provider<B> bp; 142 public A get() { 143 return new AImpl(bp.get()); 144 } 145 } 146 147 @Singleton 148 static class AutoAP implements Provider<A> { 149 @Inject Provider<B> bp; 150 A a; 151 152 public A get() { 153 if (a == null) { 154 a = new AImpl(bp.get()); 155 } 156 return a; 157 } 158 } 159 160 @ProvidedBy(BP.class) 161 public interface B { 162 A getA(); 163 int id(); 164 } 165 166 static class BImpl implements B { 167 static int nextId; 168 int id = nextId++; 169 170 final A a; 171 @Inject public BImpl(A a) { 172 this.a = a; 173 } 174 public int id() { 175 return id; 176 } 177 public A getA() { 178 return a; 179 } 180 } 181 182 static class BP implements Provider<B> { 183 Provider<A> ap; 184 @Inject BP(Provider<A> ap) { 185 this.ap = ap; 186 } 187 public B get() { 188 return new BImpl(ap.get()); 189 } 190 } 191 192 public void testUnresolvableCircularDependency() { 193 try { 194 Guice.createInjector().getInstance(C.class); 195 fail(); 196 } catch (ProvisionException expected) { 197 assertContains(expected.getMessage(), 198 "Tried proxying " + C.class.getName() + " to support a circular dependency, ", 199 "but it is not an interface."); 200 } 201 } 202 203 public void testUnresolvableCircularDependenciesWithProviderInstances() { 204 try { 205 Guice.createInjector(new AbstractModule() { 206 @Override protected void configure() {} 207 @Provides C c(D d) { return null; } 208 @Provides D d(C c) { return null; } 209 }).getInstance(C.class); 210 fail(); 211 } catch (ProvisionException expected) { 212 assertContains(expected.getMessage(), 213 "Tried proxying " + C.class.getName() + " to support a circular dependency, ", 214 "but it is not an interface."); 215 } 216 } 217 218 public void testUnresolvableCircularDependenciesWithProviderKeys() { 219 try { 220 Guice.createInjector(new AbstractModule() { 221 @Override protected void configure() { 222 bind(C2.class).toProvider(C2P.class); 223 bind(D2.class).toProvider(D2P.class); 224 } 225 }).getInstance(C2.class); 226 fail(); 227 } catch (ProvisionException expected) { 228 assertContains(expected.getMessage(), 229 "Tried proxying " + C2.class.getName() + " to support a circular dependency, ", 230 "but it is not an interface."); 231 } 232 } 233 234 public void testUnresolvableCircularDependenciesWithProvidedBy() { 235 try { 236 Guice.createInjector().getInstance(C2.class); 237 fail(); 238 } catch (ProvisionException expected) { 239 assertContains(expected.getMessage(), 240 "Tried proxying " + C2.class.getName() + " to support a circular dependency, ", 241 "but it is not an interface."); 242 } 243 } 244 245 static class C { 246 @Inject C(D d) {} 247 } 248 static class D { 249 @Inject D(C c) {} 250 } 251 252 static class C2P implements Provider<C2> { 253 @Inject Provider<D2> dp; 254 public C2 get() { 255 dp.get(); 256 return null; 257 } 258 } 259 static class D2P implements Provider<D2> { 260 @Inject Provider<C2> cp; 261 public D2 get() { 262 cp.get(); 263 return null; 264 } 265 } 266 @ProvidedBy(C2P.class) 267 static class C2 { 268 @Inject C2(D2 d) {} 269 } 270 @ProvidedBy(D2P.class) 271 static class D2 { 272 @Inject D2(C2 c) {} 273 } 274 275 public void testDisabledCircularDependency() { 276 try { 277 Guice.createInjector(new AbstractModule() { 278 @Override 279 protected void configure() { 280 binder().disableCircularProxies(); 281 } 282 }).getInstance(C.class); 283 fail(); 284 } catch (ProvisionException expected) { 285 assertContains(expected.getMessage(), 286 "Tried proxying " + C.class.getName() + " to support a circular dependency, ", 287 "but circular proxies are disabled."); 288 } 289 } 290 291 public void testDisabledCircularDependenciesWithProviderInstances() { 292 try { 293 Guice.createInjector(new AbstractModule() { 294 @Override protected void configure() { 295 binder().disableCircularProxies(); 296 } 297 @Provides C c(D d) { return null; } 298 @Provides D d(C c) { return null; } 299 }).getInstance(C.class); 300 fail(); 301 } catch (ProvisionException expected) { 302 assertContains(expected.getMessage(), 303 "Tried proxying " + C.class.getName() + " to support a circular dependency, ", 304 "but circular proxies are disabled."); 305 } 306 } 307 308 public void testDisabledCircularDependenciesWithProviderKeys() { 309 try { 310 Guice.createInjector(new AbstractModule() { 311 @Override protected void configure() { 312 binder().disableCircularProxies(); 313 bind(C2.class).toProvider(C2P.class); 314 bind(D2.class).toProvider(D2P.class); 315 } 316 }).getInstance(C2.class); 317 fail(); 318 } catch (ProvisionException expected) { 319 assertContains(expected.getMessage(), 320 "Tried proxying " + C2.class.getName() + " to support a circular dependency, ", 321 "but circular proxies are disabled."); 322 } 323 } 324 325 public void testDisabledCircularDependenciesWithProvidedBy() { 326 try { 327 Guice.createInjector(new AbstractModule() { 328 @Override 329 protected void configure() { 330 binder().disableCircularProxies(); 331 } 332 }).getInstance(C2.class); 333 fail(); 334 } catch (ProvisionException expected) { 335 assertContains(expected.getMessage(), 336 "Tried proxying " + C2.class.getName() + " to support a circular dependency, ", 337 "but circular proxies are disabled."); 338 } 339 } 340 341 /** 342 * As reported by issue 349, we give a lousy trace when a class is circularly 343 * dependent on itself in multiple ways. 344 */ 345 public void testCircularlyDependentMultipleWays() { 346 Injector injector = Guice.createInjector(new AbstractModule() { 347 protected void configure() { 348 binder.bind(A.class).to(E.class); 349 binder.bind(B.class).to(E.class); 350 } 351 }); 352 injector.getInstance(A.class); 353 } 354 355 public void testDisablingCircularProxies() { 356 Injector injector = Guice.createInjector(new AbstractModule() { 357 protected void configure() { 358 binder().disableCircularProxies(); 359 binder.bind(A.class).to(E.class); 360 binder.bind(B.class).to(E.class); 361 } 362 }); 363 364 try { 365 injector.getInstance(A.class); 366 fail("expected exception"); 367 } catch(ProvisionException expected) { 368 assertContains(expected.getMessage(), 369 "Tried proxying " + A.class.getName() + " to support a circular dependency, but circular proxies are disabled", 370 "Tried proxying " + B.class.getName() + " to support a circular dependency, but circular proxies are disabled"); 371 } 372 } 373 374 @Singleton 375 static class E implements A, B { 376 @Inject 377 public E(A a, B b) {} 378 379 public B getB() { 380 return this; 381 } 382 383 public A getA() { 384 return this; 385 } 386 387 public int id() { 388 return 0; 389 } 390 } 391 392 393 public void testCircularDependencyProxyDelegateNeverInitialized() { 394 Injector injector = Guice.createInjector(new AbstractModule() { 395 protected void configure() { 396 bind(F.class).to(RealF.class); 397 bind(G.class).to(RealG.class); 398 } 399 }); 400 F f = injector.getInstance(F.class); 401 assertEquals("F", f.g().f().toString()); 402 assertEquals("G", f.g().f().g().toString()); 403 404 } 405 406 public interface F { 407 G g(); 408 } 409 410 @Singleton 411 public static class RealF implements F { 412 private final G g; 413 @Inject RealF(G g) { 414 this.g = g; 415 } 416 417 public G g() { 418 return g; 419 } 420 421 @Override public String toString() { 422 return "F"; 423 } 424 } 425 426 public interface G { 427 F f(); 428 } 429 430 @Singleton 431 public static class RealG implements G { 432 private final F f; 433 @Inject RealG(F f) { 434 this.f = f; 435 } 436 437 public F f() { 438 return f; 439 } 440 441 @Override public String toString() { 442 return "G"; 443 } 444 } 445 446 /** 447 * Tests that ProviderInternalFactory can detect circular dependencies 448 * before it gets to Scopes.SINGLETON. This is especially important 449 * because the failure in Scopes.SINGLETON doesn't have enough context to 450 * provide a decent error message. 451 */ 452 public void testCircularDependenciesDetectedEarlyWhenDependenciesHaveDifferentTypes() { 453 Injector injector = Guice.createInjector(new AbstractModule() { 454 @Override 455 protected void configure() { 456 bind(Number.class).to(Integer.class); 457 } 458 459 @Provides @Singleton Integer provideInteger(List list) { 460 return new Integer(2); 461 } 462 463 @Provides List provideList(Integer integer) { 464 return new ArrayList(); 465 } 466 }); 467 try { 468 injector.getInstance(Number.class); 469 fail(); 470 } catch(ProvisionException expected) { 471 assertContains(expected.getMessage(), 472 "Tried proxying " + Integer.class.getName() + " to support a circular dependency, ", 473 "but it is not an interface."); 474 } 475 } 476 477 public void testPrivateModulesDontTriggerCircularErrorsInProviders() { 478 Injector injector = Guice.createInjector(new AbstractModule() { 479 @Override 480 protected void configure() { 481 install(new PrivateModule() { 482 @Override 483 protected void configure() { 484 bind(Foo.class); 485 expose(Foo.class); 486 } 487 @Provides String provideString(Bar bar) { 488 return new String("private 1, " + bar.string); 489 } 490 }); 491 install(new PrivateModule() { 492 @Override 493 protected void configure() { 494 bind(Bar.class); 495 expose(Bar.class); 496 } 497 @Provides String provideString() { 498 return new String("private 2"); 499 } 500 }); 501 } 502 }); 503 Foo foo = injector.getInstance(Foo.class); 504 assertEquals("private 1, private 2", foo.string); 505 } 506 static class Foo { 507 @Inject String string; 508 } 509 static class Bar { 510 @Inject String string; 511 } 512 513 /** 514 * When Scope Providers call their unscoped Provider's get() methods are 515 * called, it's possible that the result is a circular proxy designed for one 516 * specific parameter (not for all possible parameters). But custom scopes 517 * typically cache the results without checking to see if the result is a 518 * proxy. This leads to caching a result that is unsuitable for reuse for 519 * other parameters. 520 * 521 * This means that custom proxies have to do an 522 * {@code if(Scopes.isCircularProxy(..))} 523 * in order to avoid exceptions. 524 */ 525 public void testCustomScopeCircularProxies() { 526 Injector injector = Guice.createInjector(new AbstractModule() { 527 @Override 528 protected void configure() { 529 bindScope(SimpleSingleton.class, new BasicSingleton()); 530 bind(H.class).to(HImpl.class); 531 bind(I.class).to(IImpl.class); 532 bind(J.class).to(JImpl.class); 533 } 534 }); 535 536 // The reason this happens is because the Scope gets these requests, in order: 537 // entry: Key<IImpl> (1 - from getInstance call) 538 // entry: Key<HImpl> 539 // entry: Key<IImpl> (2 - circular dependency from HImpl) 540 // result of 2nd Key<IImpl> - a com.google.inject.$Proxy, because it's a circular proxy 541 // result of Key<HImpl> - an HImpl 542 // entry: Key<JImpl> 543 // entry: Key<IImpl> (3 - another circular dependency, this time from JImpl) 544 // At this point, if the first Key<Impl> result was cached, our cache would have 545 // Key<IImpl> caching to an instanceof of I, but not an an instanceof of IImpl. 546 // If returned this, it would result in cglib giving a ClassCastException or 547 // java reflection giving an IllegalArgumentException when filling in parameters 548 // for the constructor, because JImpl wants an IImpl, not an I. 549 550 try { 551 injector.getInstance(IImpl.class); 552 fail(); 553 } catch(ProvisionException pe) { 554 assertContains(Iterables.getOnlyElement(pe.getErrorMessages()).getMessage(), 555 "Tried proxying " + IImpl.class.getName() 556 + " to support a circular dependency, but it is not an interface."); 557 } 558 } 559 560 interface H {} 561 interface I {} 562 interface J {} 563 @SimpleSingleton 564 static class HImpl implements H { 565 @Inject HImpl(I i) {} 566 } 567 @SimpleSingleton 568 static class IImpl implements I { 569 @Inject IImpl(HImpl i, J j) {} 570 } 571 @SimpleSingleton 572 static class JImpl implements J { 573 @Inject JImpl(IImpl i) {} 574 } 575 576 @Target({ ElementType.TYPE, ElementType.METHOD }) 577 @Retention(RUNTIME) 578 @ScopeAnnotation 579 public @interface SimpleSingleton {} 580 public static class BasicSingleton implements Scope { 581 private static Map<Key, Object> cache = Maps.newHashMap(); 582 public <T> Provider<T> scope(final Key<T> key, final Provider<T> unscoped) { 583 return new Provider<T>() { 584 @SuppressWarnings("unchecked") 585 public T get() { 586 if (!cache.containsKey(key)) { 587 T t = unscoped.get(); 588 if (Scopes.isCircularProxy(t)) { 589 return t; 590 } 591 cache.put(key, t); 592 } 593 return (T)cache.get(key); 594 } 595 }; 596 } 597 } 598 } 599