/core/test/com/google/inject/CircularDependencyTest.java
Java | 652 lines | 533 code | 65 blank | 54 comment | 4 complexity | c39affc8eced9154da4861d2d79ba845 MD5 | raw file
- /**
- * Copyright (C) 2006 Google Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- package com.google.inject;
- import static com.google.inject.Asserts.assertContains;
- import static java.lang.annotation.RetentionPolicy.RUNTIME;
- import com.google.common.collect.Iterables;
- import com.google.common.collect.Maps;
- import junit.framework.TestCase;
- import java.lang.annotation.ElementType;
- import java.lang.annotation.Retention;
- import java.lang.annotation.Target;
- import java.util.ArrayList;
- import java.util.List;
- import java.util.Map;
- /**
- * @author crazybob@google.com (Bob Lee)
- * @author sameb@google.com (Sam Berlin)
- */
- public class CircularDependencyTest extends TestCase {
- @Override
- protected void setUp() throws Exception {
- AImpl.nextId = 0;
- BImpl.nextId = 0;
- }
- public void testCircularlyDependentConstructors()
- throws CreationException {
- Injector injector = Guice.createInjector(new AbstractModule() {
- @Override
- protected void configure() {
- bind(A.class).to(AImpl.class);
- bind(B.class).to(BImpl.class);
- }
- });
- assertCircularDependencies(injector);
- }
- public void testCircularlyDependentConstructorsWithProviderMethods()
- throws CreationException {
- Injector injector = Guice.createInjector(new AbstractModule() {
- @Override
- protected void configure() {}
- @Provides @Singleton A a(B b) { return new AImpl(b); }
- @Provides B b(A a) { return new BImpl(a); }
- });
- assertCircularDependencies(injector);
- }
- public void testCircularlyDependentConstructorsWithProviderInstances()
- throws CreationException {
- Injector injector = Guice.createInjector(new AbstractModule() {
- @Override
- protected void configure() {
- bind(A.class).toProvider(new Provider<A>() {
- @Inject Provider<B> bp;
- @Override
- public A get() {
- return new AImpl(bp.get());
- }
- }).in(Singleton.class);
- bind(B.class).toProvider(new Provider<B>() {
- @Inject Provider<A> ap;
- @Override
- public B get() {
- return new BImpl(ap.get());
- }
- });
- }
- });
- assertCircularDependencies(injector);
- }
- public void testCircularlyDependentConstructorsWithProviderKeys()
- throws CreationException {
- Injector injector = Guice.createInjector(new AbstractModule() {
- @Override
- protected void configure() {
- bind(A.class).toProvider(AP.class).in(Singleton.class);
- bind(B.class).toProvider(BP.class);
- }
- });
- assertCircularDependencies(injector);
- }
- public void testCircularlyDependentConstructorsWithProvidedBy()
- throws CreationException {
- Injector injector = Guice.createInjector();
- assertCircularDependencies(injector);
- }
- private void assertCircularDependencies(Injector injector) {
- A a = injector.getInstance(A.class);
- assertNotNull(a.getB().getA());
- assertEquals(0, a.id());
- assertEquals(a.id(), a.getB().getA().id());
- assertEquals(0, a.getB().id());
- assertEquals(1, AImpl.nextId);
- assertEquals(1, BImpl.nextId);
- assertSame(a, injector.getInstance(A.class));
- }
- @ProvidedBy(AutoAP.class)
- public interface A {
- B getB();
- int id();
- }
- @Singleton
- static class AImpl implements A {
- static int nextId;
- int id = nextId++;
- final B b;
- @Inject public AImpl(B b) {
- this.b = b;
- }
- @Override
- public int id() {
- return id;
- }
- @Override
- public B getB() {
- return b;
- }
- }
- static class AP implements Provider<A> {
- @Inject Provider<B> bp;
- @Override
- public A get() {
- return new AImpl(bp.get());
- }
- }
- @Singleton
- static class AutoAP implements Provider<A> {
- @Inject Provider<B> bp;
- A a;
- @Override
- public A get() {
- if (a == null) {
- a = new AImpl(bp.get());
- }
- return a;
- }
- }
- @ProvidedBy(BP.class)
- public interface B {
- A getA();
- int id();
- }
- static class BImpl implements B {
- static int nextId;
- int id = nextId++;
- final A a;
- @Inject public BImpl(A a) {
- this.a = a;
- }
- @Override
- public int id() {
- return id;
- }
- @Override
- public A getA() {
- return a;
- }
- }
- static class BP implements Provider<B> {
- Provider<A> ap;
- @Inject BP(Provider<A> ap) {
- this.ap = ap;
- }
- @Override
- public B get() {
- return new BImpl(ap.get());
- }
- }
- public void testUnresolvableCircularDependency() {
- try {
- Guice.createInjector().getInstance(C.class);
- fail();
- } catch (ProvisionException expected) {
- assertContains(expected.getMessage(),
- "Tried proxying " + C.class.getName() + " to support a circular dependency, ",
- "but it is not an interface.");
- }
- }
- public void testUnresolvableCircularDependenciesWithProviderInstances() {
- try {
- Guice.createInjector(new AbstractModule() {
- @Override protected void configure() {}
- @Provides C c(D d) { return null; }
- @Provides D d(C c) { return null; }
- }).getInstance(C.class);
- fail();
- } catch (ProvisionException expected) {
- assertContains(expected.getMessage(),
- "Tried proxying " + C.class.getName() + " to support a circular dependency, ",
- "but it is not an interface.");
- }
- }
- public void testUnresolvableCircularDependenciesWithProviderKeys() {
- try {
- Guice.createInjector(new AbstractModule() {
- @Override protected void configure() {
- bind(C2.class).toProvider(C2P.class);
- bind(D2.class).toProvider(D2P.class);
- }
- }).getInstance(C2.class);
- fail();
- } catch (ProvisionException expected) {
- assertContains(expected.getMessage(),
- "Tried proxying " + C2.class.getName() + " to support a circular dependency, ",
- "but it is not an interface.");
- }
- }
- public void testUnresolvableCircularDependenciesWithProvidedBy() {
- try {
- Guice.createInjector().getInstance(C2.class);
- fail();
- } catch (ProvisionException expected) {
- assertContains(expected.getMessage(),
- "Tried proxying " + C2.class.getName() + " to support a circular dependency, ",
- "but it is not an interface.");
- }
- }
- static class C {
- @Inject C(D d) {}
- }
- static class D {
- @Inject D(C c) {}
- }
- static class C2P implements Provider<C2> {
- @Inject Provider<D2> dp;
- @Override
- public C2 get() {
- dp.get();
- return null;
- }
- }
- static class D2P implements Provider<D2> {
- @Inject Provider<C2> cp;
- @Override
- public D2 get() {
- cp.get();
- return null;
- }
- }
- @ProvidedBy(C2P.class)
- static class C2 {
- @Inject C2(D2 d) {}
- }
- @ProvidedBy(D2P.class)
- static class D2 {
- @Inject D2(C2 c) {}
- }
- public void testDisabledCircularDependency() {
- try {
- Guice.createInjector(new AbstractModule() {
- @Override
- protected void configure() {
- binder().disableCircularProxies();
- }
- }).getInstance(C.class);
- fail();
- } catch (ProvisionException expected) {
- assertContains(expected.getMessage(),
- "Found a circular dependency involving " + C.class.getName() + ", and circular dependencies are disabled.");
- }
- }
- public void testDisabledCircularDependenciesWithProviderInstances() {
- try {
- Guice.createInjector(new AbstractModule() {
- @Override protected void configure() {
- binder().disableCircularProxies();
- }
- @Provides C c(D d) { return null; }
- @Provides D d(C c) { return null; }
- }).getInstance(C.class);
- fail();
- } catch (ProvisionException expected) {
- assertContains(expected.getMessage(),
- "Found a circular dependency involving " + C.class.getName() + ", and circular dependencies are disabled.");
- }
- }
- public void testDisabledCircularDependenciesWithProviderKeys() {
- try {
- Guice.createInjector(new AbstractModule() {
- @Override protected void configure() {
- binder().disableCircularProxies();
- bind(C2.class).toProvider(C2P.class);
- bind(D2.class).toProvider(D2P.class);
- }
- }).getInstance(C2.class);
- fail();
- } catch (ProvisionException expected) {
- assertContains(expected.getMessage(),
- "Found a circular dependency involving " + C2.class.getName() + ", and circular dependencies are disabled.");
- }
- }
- public void testDisabledCircularDependenciesWithProvidedBy() {
- try {
- Guice.createInjector(new AbstractModule() {
- @Override
- protected void configure() {
- binder().disableCircularProxies();
- }
- }).getInstance(C2.class);
- fail();
- } catch (ProvisionException expected) {
- assertContains(expected.getMessage(),
- "Found a circular dependency involving " + C2.class.getName() + ", and circular dependencies are disabled.");
- }
- }
- /**
- * As reported by issue 349, we give a lousy trace when a class is circularly
- * dependent on itself in multiple ways.
- */
- public void testCircularlyDependentMultipleWays() {
- Injector injector = Guice.createInjector(new AbstractModule() {
- @Override
- protected void configure() {
- binder.bind(A.class).to(E.class);
- binder.bind(B.class).to(E.class);
- }
- });
- injector.getInstance(A.class);
- }
- public void testDisablingCircularDependencies() {
- Injector injector = Guice.createInjector(new AbstractModule() {
- @Override
- protected void configure() {
- binder().disableCircularProxies();
- binder.bind(A.class).to(E.class);
- binder.bind(B.class).to(E.class);
- }
- });
- try {
- injector.getInstance(A.class);
- fail("expected exception");
- } catch(ProvisionException expected) {
- assertContains(expected.getMessage(),
- "Found a circular dependency involving " + A.class.getName() + ", and circular dependencies are disabled.",
- "Found a circular dependency involving " + B.class.getName() + ", and circular dependencies are disabled.");
- }
- }
- @Singleton
- static class E implements A, B {
- @Inject
- public E(A a, B b) {}
- @Override
- public B getB() {
- return this;
- }
- @Override
- public A getA() {
- return this;
- }
- @Override
- public int id() {
- return 0;
- }
- }
- public void testCircularDependencyProxyDelegateNeverInitialized() {
- Injector injector = Guice.createInjector(new AbstractModule() {
- @Override
- protected void configure() {
- bind(F.class).to(RealF.class);
- bind(G.class).to(RealG.class);
- }
- });
- F f = injector.getInstance(F.class);
- assertEquals("F", f.g().f().toString());
- assertEquals("G", f.g().f().g().toString());
- }
- public interface F {
- G g();
- }
- @Singleton
- public static class RealF implements F {
- private final G g;
- @Inject RealF(G g) {
- this.g = g;
- }
- @Override
- public G g() {
- return g;
- }
- @Override public String toString() {
- return "F";
- }
- }
- public interface G {
- F f();
- }
- @Singleton
- public static class RealG implements G {
- private final F f;
- @Inject RealG(F f) {
- this.f = f;
- }
- @Override
- public F f() {
- return f;
- }
- @Override public String toString() {
- return "G";
- }
- }
- /**
- * Tests that ProviderInternalFactory can detect circular dependencies
- * before it gets to Scopes.SINGLETON. This is especially important
- * because the failure in Scopes.SINGLETON doesn't have enough context to
- * provide a decent error message.
- */
- public void testCircularDependenciesDetectedEarlyWhenDependenciesHaveDifferentTypes() {
- Injector injector = Guice.createInjector(new AbstractModule() {
- @Override
- protected void configure() {
- bind(Number.class).to(Integer.class);
- }
- @Provides @Singleton Integer provideInteger(List list) {
- return new Integer(2);
- }
- @Provides List provideList(Integer integer) {
- return new ArrayList();
- }
- });
- try {
- injector.getInstance(Number.class);
- fail();
- } catch(ProvisionException expected) {
- assertContains(expected.getMessage(),
- "Tried proxying " + Integer.class.getName() + " to support a circular dependency, ",
- "but it is not an interface.");
- }
- }
- public void testPrivateModulesDontTriggerCircularErrorsInProviders() {
- Injector injector = Guice.createInjector(new AbstractModule() {
- @Override
- protected void configure() {
- install(new PrivateModule() {
- @Override
- protected void configure() {
- bind(Foo.class);
- expose(Foo.class);
- }
- @Provides String provideString(Bar bar) {
- return new String("private 1, " + bar.string);
- }
- });
- install(new PrivateModule() {
- @Override
- protected void configure() {
- bind(Bar.class);
- expose(Bar.class);
- }
- @Provides String provideString() {
- return new String("private 2");
- }
- });
- }
- });
- Foo foo = injector.getInstance(Foo.class);
- assertEquals("private 1, private 2", foo.string);
- }
- static class Foo {
- @Inject String string;
- }
- static class Bar {
- @Inject String string;
- }
- /**
- * When Scope Providers call their unscoped Provider's get() methods are
- * called, it's possible that the result is a circular proxy designed for one
- * specific parameter (not for all possible parameters). But custom scopes
- * typically cache the results without checking to see if the result is a
- * proxy. This leads to caching a result that is unsuitable for reuse for
- * other parameters.
- *
- * This means that custom proxies have to do an
- * {@code if(Scopes.isCircularProxy(..))}
- * in order to avoid exceptions.
- */
- public void testCustomScopeCircularProxies() {
- Injector injector = Guice.createInjector(new AbstractModule() {
- @Override
- protected void configure() {
- bindScope(SimpleSingleton.class, new BasicSingleton());
- bind(H.class).to(HImpl.class);
- bind(I.class).to(IImpl.class);
- bind(J.class).to(JImpl.class);
- }
- });
- // The reason this happens is because the Scope gets these requests, in order:
- // entry: Key<IImpl> (1 - from getInstance call)
- // entry: Key<HImpl>
- // entry: Key<IImpl> (2 - circular dependency from HImpl)
- // result of 2nd Key<IImpl> - a com.google.inject.$Proxy, because it's a circular proxy
- // result of Key<HImpl> - an HImpl
- // entry: Key<JImpl>
- // entry: Key<IImpl> (3 - another circular dependency, this time from JImpl)
- // At this point, if the first Key<Impl> result was cached, our cache would have
- // Key<IImpl> caching to an instanceof of I, but not an an instanceof of IImpl.
- // If returned this, it would result in cglib giving a ClassCastException or
- // java reflection giving an IllegalArgumentException when filling in parameters
- // for the constructor, because JImpl wants an IImpl, not an I.
- try {
- injector.getInstance(IImpl.class);
- fail();
- } catch(ProvisionException pe) {
- assertContains(Iterables.getOnlyElement(pe.getErrorMessages()).getMessage(),
- "Tried proxying " + IImpl.class.getName()
- + " to support a circular dependency, but it is not an interface.");
- }
- }
- interface H {}
- interface I {}
- interface J {}
- @SimpleSingleton
- static class HImpl implements H {
- @Inject HImpl(I i) {}
- }
- @SimpleSingleton
- static class IImpl implements I {
- @Inject IImpl(HImpl i, J j) {}
- }
- @SimpleSingleton
- static class JImpl implements J {
- @Inject JImpl(IImpl i) {}
- }
- @Target({ ElementType.TYPE, ElementType.METHOD })
- @Retention(RUNTIME)
- @ScopeAnnotation
- public @interface SimpleSingleton {}
- public static class BasicSingleton implements Scope {
- private static Map<Key<?>, Object> cache = Maps.newHashMap();
- @Override
- public <T> Provider<T> scope(final Key<T> key, final Provider<T> unscoped) {
- return new Provider<T>() {
- @Override
- @SuppressWarnings("unchecked")
- public T get() {
- if (!cache.containsKey(key)) {
- T t = unscoped.get();
- if (Scopes.isCircularProxy(t)) {
- return t;
- }
- cache.put(key, t);
- }
- return (T)cache.get(key);
- }
- };
- }
- }
- public void testDisabledNonConstructorCircularDependencies() {
- Injector injector = Guice.createInjector(new AbstractModule() {
- @Override
- protected void configure() {
- binder().disableCircularProxies();
- }
- });
- try {
- injector.getInstance(K.class);
- fail("expected exception");
- } catch(ProvisionException expected) {
- assertContains(expected.getMessage(),
- "Found a circular dependency involving " + K.class.getName() + ", and circular dependencies are disabled.");
- }
- try {
- injector.getInstance(L.class);
- fail("expected exception");
- } catch(ProvisionException expected) {
- assertContains(expected.getMessage(),
- "Found a circular dependency involving " + L.class.getName() + ", and circular dependencies are disabled.");
- }
- }
- static class K {
- @Inject L l;
- }
- static class L {
- @Inject void inject(K k) {
- }
- }
- }