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 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 (at) google.com (Bob Lee)
     34  * @author sameb (at) google.com (Sam Berlin)
     35  */
     36 public class CircularDependencyTest extends TestCase {
     37 
     38   @Override
     39   protected void setUp() throws Exception {
     40     AImpl.nextId = 0;
     41     BImpl.nextId = 0;
     42   }
     43 
     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 
     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 
     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 
    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 
    121   public void testCircularlyDependentConstructorsWithProvidedBy() throws CreationException {
    122     Injector injector = Guice.createInjector();
    123     assertCircularDependencies(injector);
    124   }
    125 
    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 {
    139     B getB();
    140 
    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
    152     public AImpl(B b) {
    153       this.b = b;
    154     }
    155 
    156     @Override
    157     public int id() {
    158       return id;
    159     }
    160 
    161     @Override
    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
    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
    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 {
    192     A getA();
    193 
    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
    204     public BImpl(A a) {
    205       this.a = a;
    206     }
    207 
    208     @Override
    209     public int id() {
    210       return id;
    211     }
    212 
    213     @Override
    214     public A getA() {
    215       return a;
    216     }
    217   }
    218 
    219   static class BP implements Provider<B> {
    220     Provider<A> ap;
    221 
    222     @Inject
    223     BP(Provider<A> ap) {
    224       this.ap = ap;
    225     }
    226 
    227     @Override
    228     public B get() {
    229       return new BImpl(ap.get());
    230     }
    231   }
    232 
    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 
    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 
    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 
    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
    304     C(D d) {}
    305   }
    306 
    307   static class D {
    308     @Inject
    309     D(C c) {}
    310   }
    311 
    312   static class C2P implements Provider<C2> {
    313     @Inject Provider<D2> dp;
    314 
    315     @Override
    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
    326     public D2 get() {
    327       cp.get();
    328       return null;
    329     }
    330   }
    331 
    332   @ProvidedBy(C2P.class)
    333   static class C2 {
    334     @Inject
    335     C2(D2 d) {}
    336   }
    337 
    338   @ProvidedBy(D2P.class)
    339   static class D2 {
    340     @Inject
    341     D2(C2 c) {}
    342   }
    343 
    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 
    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 
    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 
    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    */
    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 
    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
    480     public E(A a, B b) {}
    481 
    482     @Override
    483     public B getB() {
    484       return this;
    485     }
    486 
    487     @Override
    488     public A getA() {
    489       return this;
    490     }
    491 
    492     @Override
    493     public int id() {
    494       return 0;
    495     }
    496   }
    497 
    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 {
    514     G g();
    515   }
    516 
    517   @Singleton
    518   public static class RealF implements F {
    519     private final G g;
    520 
    521     @Inject
    522     RealF(G g) {
    523       this.g = g;
    524     }
    525 
    526     @Override
    527     public G g() {
    528       return g;
    529     }
    530 
    531     @Override
    532     public String toString() {
    533       return "F";
    534     }
    535   }
    536 
    537   public interface G {
    538     F f();
    539   }
    540 
    541   @Singleton
    542   public static class RealG implements G {
    543     private final F f;
    544 
    545     @Inject
    546     RealG(F f) {
    547       this.f = f;
    548     }
    549 
    550     @Override
    551     public F f() {
    552       return f;
    553     }
    554 
    555     @Override
    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    */
    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 
    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    */
    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
    701     HImpl(I i) {}
    702   }
    703 
    704   @SimpleSingleton
    705   static class IImpl implements I {
    706     @Inject
    707     IImpl(HImpl i, J j) {}
    708   }
    709 
    710   @SimpleSingleton
    711   static class JImpl implements J {
    712     @Inject
    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
    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