Home | History | Annotate | Download | only in inject
      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