• 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 
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@google.com (Bob Lee)
36  * @author sameb@google.com (Sam Berlin)
37  */
38 public class CircularDependencyTest extends TestCase {
39 
40   @Override
setUp()41   protected void setUp() throws Exception {
42     AImpl.nextId = 0;
43     BImpl.nextId = 0;
44   }
45 
testCircularlyDependentConstructors()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 
testCircularlyDependentConstructorsWithProviderMethods()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 
testCircularlyDependentConstructorsWithProviderInstances()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 
testCircularlyDependentConstructorsWithProviderKeys()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 
testCircularlyDependentConstructorsWithProvidedBy()100   public void testCircularlyDependentConstructorsWithProvidedBy()
101       throws CreationException {
102     Injector injector = Guice.createInjector();
103     assertCircularDependencies(injector);
104   }
105 
assertCircularDependencies(Injector injector)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 {
getB()119     B getB();
id()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;
AImpl(B b)129     @Inject public AImpl(B b) {
130       this.b = b;
131     }
id()132     public int id() {
133       return id;
134     }
getB()135     public B getB() {
136       return b;
137     }
138   }
139 
140   static class AP implements Provider<A> {
141     @Inject Provider<B> bp;
get()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 
get()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 {
getA()162     A getA();
id()163     int id();
164   }
165 
166   static class BImpl implements B {
167     static int nextId;
168     int id = nextId++;
169 
170     final A a;
BImpl(A a)171     @Inject public BImpl(A a) {
172       this.a = a;
173     }
id()174     public int id() {
175       return id;
176     }
getA()177     public A getA() {
178       return a;
179     }
180   }
181 
182   static class BP implements Provider<B> {
183     Provider<A> ap;
BP(Provider<A> ap)184     @Inject BP(Provider<A> ap) {
185       this.ap = ap;
186     }
get()187     public B get() {
188       return new BImpl(ap.get());
189     }
190   }
191 
testUnresolvableCircularDependency()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 
testUnresolvableCircularDependenciesWithProviderInstances()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 
testUnresolvableCircularDependenciesWithProviderKeys()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 
testUnresolvableCircularDependenciesWithProvidedBy()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 {
C(D d)246     @Inject C(D d) {}
247   }
248   static class D {
D(C c)249     @Inject D(C c) {}
250   }
251 
252   static class C2P implements Provider<C2> {
253     @Inject Provider<D2> dp;
get()254     public C2 get() {
255       dp.get();
256       return null;
257     }
258   }
259   static class D2P implements Provider<D2> {
260     @Inject Provider<C2> cp;
get()261     public D2 get() {
262       cp.get();
263       return null;
264     }
265   }
266   @ProvidedBy(C2P.class)
267   static class C2 {
C2(D2 d)268     @Inject C2(D2 d) {}
269   }
270   @ProvidedBy(D2P.class)
271   static class D2 {
D2(C2 c)272     @Inject D2(C2 c) {}
273   }
274 
testDisabledCircularDependency()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 
testDisabledCircularDependenciesWithProviderInstances()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 
testDisabledCircularDependenciesWithProviderKeys()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 
testDisabledCircularDependenciesWithProvidedBy()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    */
testCircularlyDependentMultipleWays()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 
testDisablingCircularProxies()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
E(A a, B b)377     public E(A a, B b) {}
378 
getB()379     public B getB() {
380       return this;
381     }
382 
getA()383     public A getA() {
384       return this;
385     }
386 
id()387     public int id() {
388       return 0;
389     }
390   }
391 
392 
testCircularDependencyProxyDelegateNeverInitialized()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 {
g()407     G g();
408   }
409 
410   @Singleton
411   public static class RealF implements F {
412     private final G g;
RealF(G g)413     @Inject RealF(G g) {
414       this.g = g;
415     }
416 
g()417     public G g() {
418       return g;
419     }
420 
toString()421     @Override public String toString() {
422       return "F";
423     }
424   }
425 
426   public interface G {
f()427     F f();
428   }
429 
430   @Singleton
431   public static class RealG implements G {
432     private final F f;
RealG(F f)433     @Inject RealG(F f) {
434       this.f = f;
435     }
436 
f()437     public F f() {
438       return f;
439     }
440 
toString()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    */
testCircularDependenciesDetectedEarlyWhenDependenciesHaveDifferentTypes()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 
testPrivateModulesDontTriggerCircularErrorsInProviders()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    */
testCustomScopeCircularProxies()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 {
HImpl(I i)565      @Inject HImpl(I i) {}
566   }
567   @SimpleSingleton
568   static class IImpl implements I {
IImpl(HImpl i, J j)569      @Inject IImpl(HImpl i, J j) {}
570   }
571   @SimpleSingleton
572   static class JImpl implements J {
JImpl(IImpl i)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();
scope(final Key<T> key, final Provider<T> unscoped)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