Revision: 1543
Author: sberlin
Date: Sat Apr 30 08:38:05 2011
Log: fix issue 626 -- properly handle circular proxies between
providers (either failing or proxying), prevents StackOverflowErrors.
http://code.google.com/p/google-guice/source/detail?r=1543
Added:
/trunk/core/src/com/google/inject/internal/InternalFactoryToInitializableAdapter.java
/trunk/core/src/com/google/inject/internal/ProviderInternalFactory.java
Modified:
/trunk/core/src/com/google/inject/internal/BindingProcessor.java
/trunk/core/src/com/google/inject/internal/BoundProviderFactory.java
/trunk/core/src/com/google/inject/internal/DelegatingInvocationHandler.java
/trunk/core/src/com/google/inject/internal/InjectorImpl.java
/trunk/core/src/com/google/inject/internal/InternalFactoryToProviderAdapter.java
/trunk/core/src/com/google/inject/internal/Scoping.java
/trunk/core/test/com/google/inject/CircularDependencyTest.java
=======================================
--- /dev/null
+++
/trunk/core/src/com/google/inject/internal/InternalFactoryToInitializableAdapter.java
Sat Apr 30 08:38:05 2011
@@ -0,0 +1,52 @@
+/**
+ * Copyright (C) 2011 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.internal;
+
+import com.google.inject.Provider;
+import static com.google.inject.internal.util.Preconditions.checkNotNull;
+import com.google.inject.spi.Dependency;
+import com.google.inject.spi.ProviderInstanceBinding;
+
+/**
+ * Adapts {@link ProviderInstanceBinding} providers, ensuring circular
proxies
+ * fail (or proxy) properly.
+ *
+ * @author [email protected] (Sam Berlin)
+*/
+final class InternalFactoryToInitializableAdapter<T> extends
ProviderInternalFactory<T> {
+
+ private final Initializable<Provider<? extends T>> initializable;
+
+ public InternalFactoryToInitializableAdapter(
+ Initializable<Provider<? extends T>> initializable, Object source,
boolean allowProxy) {
+ super(source, allowProxy);
+ this.initializable = checkNotNull(initializable, "provider");
+ }
+
+ public T get(Errors errors, InternalContext context, Dependency<?>
dependency, boolean linked)
+ throws ErrorsException {
+ try {
+ return circularGet(initializable.get(errors), errors, context,
dependency, linked);
+ } catch(RuntimeException userException) {
+ throw
errors.withSource(source).errorInProvider(userException).toException();
+ }
+ }
+
+ @Override public String toString() {
+ return initializable.toString();
+ }
+}
=======================================
--- /dev/null
+++ /trunk/core/src/com/google/inject/internal/ProviderInternalFactory.java
Sat Apr 30 08:38:05 2011
@@ -0,0 +1,72 @@
+/**
+ * Copyright (C) 2011 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.internal;
+
+import static com.google.inject.internal.util.Preconditions.checkNotNull;
+
+import javax.inject.Provider;
+
+import com.google.inject.spi.Dependency;
+
+/**
+ * Base class for InternalFactories that are used by Providers, to handle
+ * circular dependencies.
+ *
+ * @author [email protected] (Sam Berlin)
+ */
+abstract class ProviderInternalFactory<T> implements InternalFactory<T> {
+
+ protected final Object source;
+ private final boolean allowProxy;
+
+ ProviderInternalFactory(Object source, boolean allowProxy) {
+ this.source = checkNotNull(source, "source");
+ this.allowProxy = allowProxy;
+ }
+
+ protected T circularGet(Provider<? extends T> provider, Errors errors,
+ InternalContext context, Dependency<?> dependency, boolean linked)
+ throws ErrorsException {
+ Class<?> expectedType =
dependency.getKey().getTypeLiteral().getRawType();
+ // we do not use 'this' as the key because for JIT provider bindings,
+ // 'this' is recreated on failures... the key suffices.
+ ConstructionContext<T> constructionContext =
context.getConstructionContext(dependency.getKey());
+
+ // We have a circular reference between constructors. Return a proxy.
+ if (constructionContext.isConstructing()) {
+ if (!allowProxy) {
+ throw errors.circularProxiesDisabled(expectedType).toException();
+ } else {
+ // TODO: if we can't proxy this object, can we proxy the other
object?
+ @SuppressWarnings("unchecked")
+ T proxyType = (T) constructionContext.createProxy(errors,
expectedType);
+ return proxyType;
+ }
+ }
+ // First time through...
+ constructionContext.startConstruction();
+ try {
+ T t = errors.checkForNull(provider.get(), source, dependency);
+ constructionContext.setProxyDelegates(t);
+ return t;
+ } finally {
+ constructionContext.finishConstruction();
+ }
+ }
+
+}
=======================================
--- /trunk/core/src/com/google/inject/internal/BindingProcessor.java Thu
Mar 10 19:02:39 2011
+++ /trunk/core/src/com/google/inject/internal/BindingProcessor.java Sat
Apr 30 08:38:05 2011
@@ -100,7 +100,8 @@
Set<InjectionPoint> injectionPoints = binding.getInjectionPoints();
Initializable<Provider<? extends T>> initializable = initializer
.<Provider<? extends T>>requestInjection(injector, provider,
source, injectionPoints);
- InternalFactory<T> factory = new
InternalFactoryToProviderAdapter<T>(initializable, source);
+ InternalFactory<T> factory = new
InternalFactoryToInitializableAdapter<T>(
+ initializable,
source, !injector.options.disableCircularProxies);
InternalFactory<? extends T> scopedFactory
= Scoping.scope(key, injector, factory, source, scoping);
putBinding(new ProviderInstanceBindingImpl<T>(injector, key,
source, scopedFactory, scoping,
@@ -111,8 +112,8 @@
public Boolean visit(ProviderKeyBinding<? extends T> binding) {
prepareBinding();
Key<? extends javax.inject.Provider<? extends T>> providerKey =
binding.getProviderKey();
- BoundProviderFactory<T> boundProviderFactory
- = new BoundProviderFactory<T>(injector, providerKey, source);
+ BoundProviderFactory<T> boundProviderFactory = new
BoundProviderFactory<T>(
+ injector, providerKey,
source, !injector.options.disableCircularProxies);
bindingData.addCreationListener(boundProviderFactory);
InternalFactory<? extends T> scopedFactory = Scoping.scope(
key, injector, (InternalFactory<? extends T>)
boundProviderFactory, source, scoping);
=======================================
--- /trunk/core/src/com/google/inject/internal/BoundProviderFactory.java
Thu Mar 10 19:02:39 2011
+++ /trunk/core/src/com/google/inject/internal/BoundProviderFactory.java
Sat Apr 30 08:38:05 2011
@@ -23,20 +23,20 @@
/**
* Delegates to a custom factory which is also bound in the injector.
*/
-final class BoundProviderFactory<T> implements InternalFactory<T>,
CreationListener {
+final class BoundProviderFactory<T> extends ProviderInternalFactory<T>
implements CreationListener {
private final InjectorImpl injector;
final Key<? extends javax.inject.Provider<? extends T>> providerKey;
- final Object source;
private InternalFactory<? extends javax.inject.Provider<? extends T>>
providerFactory;
BoundProviderFactory(
InjectorImpl injector,
Key<? extends javax.inject.Provider<? extends T>> providerKey,
- Object source) {
+ Object source,
+ boolean allowProxy) {
+ super(source, allowProxy);
this.injector = injector;
this.providerKey = providerKey;
- this.source = source;
}
public void notify(Errors errors) {
@@ -52,10 +52,10 @@
errors = errors.withSource(providerKey);
javax.inject.Provider<? extends T> provider =
providerFactory.get(errors, context, dependency, true);
try {
- return errors.checkForNull(provider.get(), source, dependency);
+ return circularGet(provider, errors, context, dependency, linked);
} catch(RuntimeException userException) {
throw errors.errorInProvider(userException).toException();
- }
+ }
}
@Override public String toString() {
=======================================
---
/trunk/core/src/com/google/inject/internal/DelegatingInvocationHandler.java
Sat Jul 3 08:51:31 2010
+++
/trunk/core/src/com/google/inject/internal/DelegatingInvocationHandler.java
Sat Apr 30 08:38:05 2011
@@ -34,6 +34,9 @@
}
try {
+ // TODO: method.setAccessible(true); ?
+ // this would fix visibility errors when we proxy a
+ // non-public interface.
return method.invoke(delegate, args);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
=======================================
--- /trunk/core/src/com/google/inject/internal/InjectorImpl.java Wed Mar 2
05:53:17 2011
+++ /trunk/core/src/com/google/inject/internal/InjectorImpl.java Sat Apr 30
08:38:05 2011
@@ -677,14 +677,16 @@
final BindingImpl<? extends Provider<?>> providerBinding
= getBindingOrThrow(providerKey, errors,
JitLimitation.NEW_OR_EXISTING_JIT);
- InternalFactory<T> internalFactory = new InternalFactory<T>() {
+ InternalFactory<T> internalFactory =
+ new
ProviderInternalFactory<T>(providerKey, !options.disableCircularProxies) {
public T get(Errors errors, InternalContext context, Dependency
dependency, boolean linked)
throws ErrorsException {
errors = errors.withSource(providerKey);
- Provider<?> provider = providerBinding.getInternalFactory().get(
+ Provider provider = providerBinding.getInternalFactory().get(
errors, context, dependency, true);
try {
- Object o = provider.get();
+ @SuppressWarnings("unchecked") // type is not checked within
circularGet
+ Object o = circularGet(provider, errors, context, dependency,
linked);
if (o != null && !rawType.isInstance(o)) {
throw errors.subtypeNotProvided(providerType,
rawType).toException();
}
=======================================
---
/trunk/core/src/com/google/inject/internal/InternalFactoryToProviderAdapter.java
Sat Jul 3 08:51:31 2010
+++
/trunk/core/src/com/google/inject/internal/InternalFactoryToProviderAdapter.java
Sat Apr 30 08:38:05 2011
@@ -25,25 +25,24 @@
*/
final class InternalFactoryToProviderAdapter<T> implements
InternalFactory<T> {
- private final Initializable<Provider<? extends T>> initializable;
+ private final Provider<? extends T> provider;
private final Object source;
- public InternalFactoryToProviderAdapter(
- Initializable<Provider<? extends T>> initializable, Object source) {
- this.initializable = checkNotNull(initializable, "provider");
+ public InternalFactoryToProviderAdapter(Provider<? extends T> provider,
Object source) {
+ this.provider = checkNotNull(provider, "provider");
this.source = checkNotNull(source, "source");
}
public T get(Errors errors, InternalContext context, Dependency<?>
dependency, boolean linked)
throws ErrorsException {
try {
- return errors.checkForNull(initializable.get(errors).get(), source,
dependency);
+ return errors.checkForNull(provider.get(), source, dependency);
} catch (RuntimeException userException) {
throw
errors.withSource(source).errorInProvider(userException).toException();
}
}
@Override public String toString() {
- return initializable.toString();
+ return provider.toString();
}
}
=======================================
--- /trunk/core/src/com/google/inject/internal/Scoping.java Sat Jul 3
08:51:31 2010
+++ /trunk/core/src/com/google/inject/internal/Scoping.java Sat Apr 30
08:38:05 2011
@@ -239,8 +239,7 @@
Provider<T> scoped
= scope.scope(key, new
ProviderToInternalFactoryAdapter<T>(injector, creator));
- return new InternalFactoryToProviderAdapter<T>(
- Initializables.<Provider<? extends T>>of(scoped), source);
+ return new InternalFactoryToProviderAdapter<T>(scoped, source);
}
/**
=======================================
--- /trunk/core/test/com/google/inject/CircularDependencyTest.java Sun Dec
12 18:44:36 2010
+++ /trunk/core/test/com/google/inject/CircularDependencyTest.java Sat Apr
30 08:38:05 2011
@@ -21,13 +21,14 @@
/**
* @author [email protected] (Bob Lee)
+ * @author [email protected] (Sam Berlin)
*/
public class CircularDependencyTest extends TestCase {
-
- @Override protected void setUp() throws Exception {
- super.setUp();
- Chicken.nextInstanceId = 0;
- Egg.nextInstanceId = 0;
+
+ @Override
+ protected void setUp() throws Exception {
+ AImpl.nextId = 0;
+ BImpl.nextId = 0;
}
public void testCircularlyDependentConstructors()
@@ -38,39 +39,118 @@
bind(B.class).to(BImpl.class);
}
});
-
+ assertCircularDependencies(injector);
+ }
+
+ public void testCircularlyDependentConstructorsWithProviderInstances()
+ throws CreationException {
+ Injector injector = Guice.createInjector(new AbstractModule() {
+ 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 testCircularlyDependentConstructorsWithProviderKeys()
+ throws CreationException {
+ Injector injector = Guice.createInjector(new AbstractModule() {
+ 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));
}
- interface A {
+ @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;
}
+ public int id() {
+ return id;
+ }
public B getB() {
return b;
}
}
-
- interface B {
+
+ static class AP implements Provider<A> {
+ @Inject Provider<B> bp;
+ public A get() {
+ return new AImpl(bp.get());
+ }
+ }
+
+ @Singleton
+ static class AutoAP implements Provider<A> {
+ @Inject Provider<B> bp;
+ A a;
+ 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;
}
+ public int id() {
+ return id;
+ }
public A getA() {
return a;
}
}
+
+ static class BP implements Provider<B> {
+ @Inject Provider<A> ap;
+ public B get() {
+ return new BImpl(ap.get());
+ }
+ }
public void testUnresolvableCircularDependency() {
try {
@@ -82,14 +162,144 @@
"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;
+ public C2 get() {
+ dp.get();
+ return null;
+ }
+ }
+ static class D2P implements Provider<D2> {
+ @Inject Provider<C2> cp;
+ 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(),
+ "Tried proxying " + C.class.getName() + " to support a circular
dependency, ",
+ "but circular proxies 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(),
+ "Tried proxying " + C.class.getName() + " to support a circular
dependency, ",
+ "but circular proxies 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(),
+ "Tried proxying " + C2.class.getName() + " to support a circular
dependency, ",
+ "but circular proxies 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(),
+ "Tried proxying " + C2.class.getName() + " to support a circular
dependency, ",
+ "but circular proxies are disabled.");
+ }
+ }
/**
* As reported by issue 349, we give a lousy trace when a class is
circularly
@@ -136,43 +346,12 @@
public A getA() {
return this;
}
- }
-
- static class Chicken {
- static int nextInstanceId;
- final int instanceId = nextInstanceId++;
- @Inject Egg source;
- }
-
- static class Egg {
- static int nextInstanceId;
- final int instanceId = nextInstanceId++;
- @Inject Chicken source;
- }
-
- public void testCircularlyDependentSingletonsWithProviders() {
- Injector injector = Guice.createInjector(new AbstractModule() {
- protected void configure() {
- bind(Chicken.class).in(Singleton.class);
- }
-
- @Provides @Singleton Egg provideEgg(Chicken chicken) {
- Egg egg = new Egg();
- egg.source = chicken;
- return egg;
- }
- });
-
- try {
- injector.getInstance(Egg.class);
- fail();
- } catch (ProvisionException e) {
- assertContains(e.getMessage(),
- "Provider was reentrant while creating a singleton",
- " at " + CircularDependencyTest.class.getName(), "provideEgg(",
- " while locating " + Egg.class.getName());
+
+ public int id() {
+ return 0;
}
}
+
public void testCircularDependencyProxyDelegateNeverInitialized() {
Injector injector = Guice.createInjector(new AbstractModule() {
@@ -226,5 +405,4 @@
return "G";
}
}
-
-}
+}
--
You received this message because you are subscribed to the Google Groups
"google-guice-dev" group.
To post to this group, send email to [email protected].
To unsubscribe from this group, send email to
[email protected].
For more options, visit this group at
http://groups.google.com/group/google-guice-dev?hl=en.