• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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