Skip to content

Instantly share code, notes, and snippets.

@gissuebot
Created July 7, 2014 17:54
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save gissuebot/18ae84667aa174448097 to your computer and use it in GitHub Desktop.
Save gissuebot/18ae84667aa174448097 to your computer and use it in GitHub Desktop.
Migrated attachment for Guice issue 49, comment 18
Index: test/com/google/inject/AutobinderTest.java
===================================================================
--- test/com/google/inject/AutobinderTest.java (revision 0)
+++ test/com/google/inject/AutobinderTest.java (revision 0)
@@ -0,0 +1,358 @@
+package com.google.inject;
+
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import java.lang.annotation.Annotation;
+import java.lang.annotation.Retention;
+import static org.easymock.EasyMock.*;
+import com.google.inject.binder.Autobinder;
+import com.google.inject.binder.LinkedBindingBuilder;
+import com.google.inject.name.Named;
+import com.google.inject.util.Objects;
+
+import junit.framework.TestCase;
+
+/**
+ * Test the autobinder functionality.
+ */
+public class AutobinderTest extends TestCase {
+ private static final Module AUTO_BIND_MODULE = new Module() {
+ public void configure(final Binder binder) {
+ binder.autobind(Service.class).to(MyAutobinder.class);
+ }
+ };
+
+ /**
+ * Test that the autobinder is avaliable to be injected.
+ */
+ public void testAutobinderBound() {
+ final Injector injector = Guice.createInjector(AUTO_BIND_MODULE);
+
+ final Autobinder autobinder = injector.getInstance(Key.get(
+ Autobinder.class, Service.class));
+ assertNotNull("Autobinder should not be null", autobinder);
+ assertTrue("Autobinder should be instance of MyAutobinder",
+ autobinder instanceof MyAutobinder);
+ }
+
+ /**
+ * Test that when we explicitly request an autobound implementation we can
+ * retrieve it.
+ */
+ public void testRequestAutobound() {
+ final Injector injector = Guice.createInjector(AUTO_BIND_MODULE);
+
+ final A a = injector.getInstance(Key.get(A.class, Service.class));
+ assertEquals("A", a.get());
+ }
+
+ /**
+ * Test that when we explicitly request an autobound implementation with an
+ * annotation implementation we can retrieve it.
+ */
+ public void testRequestAutoboundAnnotationImpl() {
+ final Injector injector = Guice.createInjector(new Module() {
+ public void configure(final Binder binder) {
+ binder.autobind(new ServiceImpl("test")).to(MyAutobinder.class);
+ }
+ });
+
+ final A a = injector.getInstance(Key.get(A.class, new ServiceImpl("test")));
+ assertEquals("A", a.get());
+
+ try {
+ injector.getInstance(Key.get(A.class, Service.class));
+ fail("Expected Configuration Exception");
+ } catch (ConfigurationException e) {
+ // Expected
+ }
+ }
+
+ /**
+ * Test that when we explicitly request an autobound implementation that the
+ * autobinder can't fill the behaviour is correct.
+ */
+ public void testRequestunfilledAutobound() {
+ final Injector injector = Guice.createInjector(AUTO_BIND_MODULE);
+
+ try {
+ injector.getInstance(Key.get(D.class, Service.class));
+ fail("Expected Configuration Exception");
+ } catch (ConfigurationException e) {
+ // Expected
+ }
+ }
+
+ /**
+ * Test that when we explicitly request an autobound implementation with a
+ * missing dependency the correct exception is thrown.
+ */
+ public void testRequestAutoboundMissingDependency() {
+ final Injector injector = Guice.createInjector(AUTO_BIND_MODULE);
+
+ try {
+ injector.getInstance(Key.get(B.class, Service.class));
+ fail("Excepted ConfigurationException as D is not bound");
+ } catch (ConfigurationException e) {
+ // expected
+ }
+ }
+
+ /**
+ * Test that when an autobound implementation is injected it's injected
+ * correctly.
+ */
+ public void testInjectAutobound() {
+ final Injector injector = Guice.createInjector(AUTO_BIND_MODULE,
+ new Module() {
+ public void configure(Binder binder) {
+ binder.bind(B.class).to(BImpl.class);
+ binder.bind(D.class).to(D2.class);
+ }
+ });
+
+ final B b = injector.getInstance(B.class);
+ assertEquals("B(A,D)", b.get());
+ }
+
+ /**
+ * Test that when autobound implementations are themselves injected with other
+ * implementations.
+ */
+ public void testInjectNestedAutobound() {
+ final Injector injector = Guice.createInjector(AUTO_BIND_MODULE,
+ new Module() {
+ public void configure(Binder binder) {
+ binder.bind(C.class).to(CImpl.class);
+ binder.bind(D.class).to(D2.class);
+ }
+ });
+
+ final C c = injector.getInstance(C.class);
+ assertEquals("C(B(A,D))", c.get());
+ }
+
+ /**
+ * Test that when autobound implementations are themselves injected but some
+ * dependencies aren't bound, the behaviour is correct.
+ */
+ public void testInjectNestedAutoboundMissingDependency() {
+ try {
+ Guice.createInjector(AUTO_BIND_MODULE, new Module() {
+ public void configure(Binder binder) {
+ binder.bind(C.class).to(CImpl.class);
+ }
+ });
+ fail("Excepted ConfigurationException as D is not bound");
+ } catch (ConfigurationException e) {
+ // expected
+ }
+ }
+
+ /**
+ * Test that explicit bindings are taken in preference to autobindings.
+ */
+ public void testExplicitBindingOverridesAuto() {
+ final Injector injector = Guice.createInjector(AUTO_BIND_MODULE,
+ new Module() {
+ public void configure(Binder binder) {
+ binder.bind(A.class).annotatedWith(new ServiceImpl("")).to(A2.class);
+ binder.bind(B.class).to(BImpl.class);
+ binder.bind(D.class).to(D2.class);
+ }
+ });
+
+ final B b = injector.getInstance(B.class);
+ assertEquals("B(A2,D)", b.get());
+ }
+
+ /**
+ * Test that failed bindings don't hang around.
+ */
+ public void testBindAfterFailedAutobind() {
+ final Injector injector = Guice.createInjector(AUTO_BIND_MODULE);
+
+ try {
+ injector.getInstance(Key.get(D.class, Service.class));
+ fail("Excepted ConfigurationException as D is not bound");
+ } catch (ConfigurationException e) {
+ // expected
+ }
+
+ injector.getInstance(Key.get(A.class, Service.class));
+
+ assertNull(injector.getBinding(Key.get(D.class, Service.class)));
+ }
+
+ public void testCantGetIncomplete() throws Exception {
+ final BlockingAutobinder blockingAutobinder = new BlockingAutobinder();
+
+ final Injector injector = Guice.createInjector(new Module() {
+ public void configure(final Binder binder) {
+ binder.bind(D.class).to(D2.class);
+ binder.autobind(Service.class).toInstance(blockingAutobinder);
+ }
+ });
+
+ new Thread() {
+ public void run() {
+ injector.getInstance(Key.get(B.class, Service.class));
+ }
+ }.start();
+
+ synchronized (blockingAutobinder) {
+ while (!blockingAutobinder.blocking) {
+ blockingAutobinder.wait();
+ }
+ }
+
+ final BImpl b = (BImpl) injector.getInstance(Key.get(B.class, Service.class));
+ assertNotNull(b.d);
+ assertNotNull(b.a);
+ }
+
+ static class BlockingAutobinder implements Autobinder {
+ boolean blocking = false;
+ public <T> boolean autobind(final Key<T> key, final LinkedBindingBuilder<T> bindThisKey) {
+ if (A.class.equals(key.getTypeLiteral().getType())) {
+ synchronized (this) {
+ try {
+ blocking = true;
+ notifyAll();
+ wait(1000);
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+ return new MyAutobinder().autobind(key, bindThisKey);
+ }
+ }
+
+ static class MyAutobinder implements Autobinder {
+ public <T> boolean autobind(Key<T> key, LinkedBindingBuilder<T> bindThisKey) {
+ final Class<T> ifceClass = (Class<T>) key.getTypeLiteral().getType();
+ String implClassName = ifceClass.getName() + "Impl";
+ try {
+ final Class<T> implClass = (Class<T>) Class.forName(implClassName);
+ bindThisKey.to(implClass);
+ return true;
+ } catch (ClassNotFoundException e) {
+ return false;
+ }
+ }
+ };
+
+ @Retention(RUNTIME)
+ @BindingAnnotation
+ static @interface Service {
+ String value() default "";
+ }
+
+ static interface A {
+ String get();
+ }
+
+ static interface B {
+ String get();
+ }
+
+ static interface C {
+ String get();
+ }
+
+ static interface D {
+ String get();
+ }
+
+ static interface E {
+ String get();
+ }
+
+ static class AImpl implements A {
+ public String get() {
+ return "A";
+ }
+ }
+
+ static class A2 implements A {
+ public String get() {
+ return "A2";
+ }
+ }
+
+ static class BImpl implements B {
+ @Inject
+ @Service
+ private A a;
+
+ @Inject
+ private D d;
+
+ public String get() {
+ return String.format("B(%s,%s)", a.get(), d.get());
+ }
+ }
+
+ static class EImpl implements E {
+ @Inject
+ private D d;
+
+ public String get() {
+ return String.format("E(%s)", d.get());
+ }
+ }
+
+ static class CImpl implements C {
+ @Inject
+ @Service
+ private B b;
+
+ public String get() {
+ return String.format("C(%s)", b.get());
+ }
+ }
+
+ static class D2 implements D {
+
+ public String get() {
+ return "D";
+ }
+
+ }
+
+ static class ServiceImpl implements Service {
+
+ final String value;
+
+ public ServiceImpl(String value) {
+ this.value = Objects.nonNull(value, "name");
+ }
+
+ public String value() {
+ return this.value;
+ }
+
+ public int hashCode() {
+ // This is specified in java.lang.Annotation.
+ return 127 * "value".hashCode() ^ value.hashCode();
+ }
+
+ public boolean equals(Object o) {
+ if (!(o instanceof Service)) {
+ return false;
+ }
+
+ Service other = (Service) o;
+ return value.equals(other.value());
+ }
+
+ public String toString() {
+ return "@" + Service.class.getName() + "(value=" + value + ")";
+ }
+
+ public Class<? extends Annotation> annotationType() {
+ return Service.class;
+ }
+ }
+}
Index: src/com/google/inject/Binder.java
===================================================================
--- src/com/google/inject/Binder.java (revision 376)
+++ src/com/google/inject/Binder.java (working copy)
@@ -17,6 +17,7 @@
package com.google.inject;
import com.google.inject.binder.AnnotatedBindingBuilder;
+import com.google.inject.binder.Autobinder;
import com.google.inject.binder.ConstantBindingBuilder;
import com.google.inject.binder.LinkedBindingBuilder;
import com.google.inject.binder.AnnotatedConstantBindingBuilder;
@@ -97,6 +98,16 @@
* Binds a constant value to an annotation.
*/
AnnotatedConstantBindingBuilder bindConstant();
+
+ /**
+ * Binds the supplied binding annotation to an autobinder.
+ */
+ LinkedBindingBuilder<Autobinder> autobind(Class<? extends Annotation> annotationType);
+
+ /**
+ * Binds the supplied binding annotation to an autobinder.
+ */
+ LinkedBindingBuilder<Autobinder> autobind(Annotation annotation);
/**
* Upon successful creation, the {@link Injector} will inject static fields
Index: src/com/google/inject/binder/Autobinder.java
===================================================================
--- src/com/google/inject/binder/Autobinder.java (revision 0)
+++ src/com/google/inject/binder/Autobinder.java (revision 0)
@@ -0,0 +1,51 @@
+/**
+ * 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.binder;
+
+import com.google.inject.Key;
+
+/**
+ * Simply a factory for producing additional bindings when a key is encounterd.
+ * Autobinders are generally used to integrate with some external service
+ * registry where it's not possible to list the full list of services.
+ *
+ * An autobinder is always associated with an annotation by
+ * {@link com.google.inject.Binder#autobind(java.lang.annotation.Annotation)} or
+ * {@link com.google.inject.Binder#autobind(Class))}. The intention is that
+ * when a binding is related to an external service registry, there is an
+ * explicit annotation to indicate this.
+ *
+ * The primary difference between an Autobinder and a
+ * {@link com.google.inject.Provider} is that an autobiner is passed the key
+ * that's being looked up. This allows an autobinder to provide implementations
+ * for more than one binding. A provider on the other hand recieves no context
+ * information so cannot return a different result based on the context.
+ *
+ * Generally any explicit binding will take precedence over an autobinding.
+ */
+public interface Autobinder {
+ /**
+ * Provides a binding for the supplied key. The binding is supplied by making
+ * calles on the supplied {@link LinkedBindingBuilder} in the same was as is
+ * done within a {@link com.google.inject.Module}.
+ *
+ * If the autobinder is unable to find a binding for the supplied key it
+ * should return false which will allow the injector to proceede to its next
+ * method of finding a binding.
+ */
+ <T> boolean autobind(Key<T> key, LinkedBindingBuilder<T> bindThisKey);
+}
Index: src/com/google/inject/InjectorImpl.java
===================================================================
--- src/com/google/inject/InjectorImpl.java (revision 376)
+++ src/com/google/inject/InjectorImpl.java (working copy)
@@ -16,6 +16,9 @@
package com.google.inject;
+import com.google.inject.BinderImpl.CreationListener;
+import com.google.inject.binder.Autobinder;
+import com.google.inject.binder.LinkedBindingBuilder;
import com.google.inject.spi.SourceProviders;
import com.google.inject.util.GuiceFastClass;
import com.google.inject.util.Objects;
@@ -41,6 +44,8 @@
import java.util.Iterator;
import java.util.List;
import java.util.Map;
+import java.util.Set;
+
import net.sf.cglib.reflect.FastClass;
import net.sf.cglib.reflect.FastMethod;
@@ -87,16 +92,22 @@
final Map<Key<?>, BindingImpl<?>> bindings;
final BindingsMultimap bindingsMultimap = new BindingsMultimap();
final Map<Class<? extends Annotation>, Scope> scopes;
+ final Set<Class<? extends Annotation>> autobinderAnnotations;
ErrorHandler errorHandler = new InvalidErrorHandler();
Object defaultSource = SourceProviders.UNKNOWN_SOURCE;
+
+ final BinderImpl binder;
InjectorImpl(ConstructionProxyFactory constructionProxyFactory,
Map<Key<?>, BindingImpl<?>> bindings,
- Map<Class<? extends Annotation>, Scope> scopes) {
+ Map<Class<? extends Annotation>, Scope> scopes,
+ BinderImpl binder, Set<Class<? extends Annotation>> autobinderAnnotations) {
this.constructionProxyFactory = constructionProxyFactory;
this.bindings = bindings;
this.scopes = scopes;
+ this.binder = binder;
+ this.autobinderAnnotations = autobinderAnnotations;
}
/**
@@ -272,6 +283,24 @@
return null;
}
+
+ if (hasAutobinder(key) && !key.getTypeLiteral().getType().equals(Autobinder.class)) {
+ final Key<Autobinder> autobinderKey;
+ if (key.getAnnotation() == null) {
+ autobinderKey = Key.get(Autobinder.class, key.getAnnotationType());
+ } else {
+ autobinderKey = Key.get(Autobinder.class, key.getAnnotation());
+ }
+ final Autobinder autobinder = getInstance(autobinderKey);
+ final BindingBuilderImpl<T> builder = binder.createBindingBuilder(key);
+ if (autobinder.autobind(key, builder)) {
+ binder.addBindingBuilder(builder);
+ binder.updateInjector();
+
+ binding = (BindingImpl<T>) bindings.get(key);
+ return binding.getInternalFactory();
+ }
+ }
// We don't want to implicitly inject a member if we have a binding
// annotation.
@@ -279,10 +308,11 @@
// Look for a factory bound to a key without annotation attributes if
// necessary.
if (key.hasAttributes()) {
- return getInternalFactory(member, key.withoutAttributes());
+ final InternalFactory<? extends T> internalFactory = getInternalFactory(member, key.withoutAttributes());
+ if (internalFactory != null) {
+ return internalFactory;
+ }
}
-
- return null;
}
// Last resort: inject the type itself.
@@ -309,6 +339,13 @@
null);
}
+ private <T> boolean hasAutobinder(Key<T> key) {
+ if (!key.hasAnnotationType()) {
+ return false;
+ }
+ return autobinderAnnotations.contains(key.getAnnotationType());
+ }
+
private <T> InternalFactory<T> handleConstantConversionError(
Member member, Binding<String> stringBinding, Class<?> rawType,
Exception e) {
Index: src/com/google/inject/BinderImpl.java
===================================================================
--- src/com/google/inject/BinderImpl.java (revision 376)
+++ src/com/google/inject/BinderImpl.java (working copy)
@@ -16,16 +16,9 @@
package com.google.inject;
-import com.google.inject.InjectorImpl.SingleMemberInjector;
-import com.google.inject.Key.AnnotationStrategy;
import static com.google.inject.Scopes.SINGLETON;
-import com.google.inject.matcher.Matcher;
-import com.google.inject.spi.Message;
-import com.google.inject.spi.SourceProviders;
-import com.google.inject.util.Annotations;
import static com.google.inject.util.Objects.nonNull;
-import com.google.inject.util.StackTraceElements;
-import com.google.inject.util.Stopwatch;
+
import java.lang.annotation.Annotation;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
@@ -34,13 +27,27 @@
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Level;
import java.util.logging.Logger;
+
import org.aopalliance.intercept.MethodInterceptor;
+import com.google.inject.InjectorImpl.SingleMemberInjector;
+import com.google.inject.binder.Autobinder;
+import com.google.inject.binder.LinkedBindingBuilder;
+import com.google.inject.matcher.Matcher;
+import com.google.inject.spi.Message;
+import com.google.inject.spi.SourceProviders;
+import com.google.inject.util.Annotations;
+import com.google.inject.util.StackTraceElements;
+import com.google.inject.util.Stopwatch;
+
/**
* Builds a dependency injection {@link Injector}. Binds {@link Key}s to
* implementations.
@@ -63,6 +70,8 @@
= new ArrayList<ConstantBindingBuilderImpl>();
final Map<Class<? extends Annotation>, Scope> scopes =
new HashMap<Class<? extends Annotation>, Scope>();
+ final Set<Class<? extends Annotation>> autobinderAnnotations =
+ new HashSet<Class<? extends Annotation>>();
final List<StaticInjection> staticInjections
= new ArrayList<StaticInjection>();
@@ -131,10 +140,10 @@
return stage;
}
- final List<CreationListener> creationListeners
- = new ArrayList<CreationListener>();
- final List<CreationListener> instanceInjectors
- = new ArrayList<CreationListener>();
+ final LinkedList<CreationListener> creationListeners
+ = new LinkedList<CreationListener>();
+ final LinkedList<CreationListener> instanceInjectors
+ = new LinkedList<CreationListener>();
interface CreationListener {
void notify(InjectorImpl injector);
@@ -170,12 +179,26 @@
}
public <T> BindingBuilderImpl<T> bind(Key<T> key) {
+ BindingBuilderImpl<T> builder = createBindingBuilder(key);
+ addBindingBuilder(builder);
+ return builder;
+ }
+
+ /*
+ * These two methods are exposed to the package
+ * to allow autobinders to only add the builder when
+ * there actually is a binding.
+ */
+ <T> BindingBuilderImpl<T> createBindingBuilder(Key<T> key) {
BindingBuilderImpl<T> builder =
- new BindingBuilderImpl<T>(this, key, source());
- bindingBuilders.add(builder);
+ new BindingBuilderImpl<T>(this, key, source());
return builder;
}
+ <T> void addBindingBuilder(BindingBuilderImpl<T> builder) {
+ bindingBuilders.add(builder);
+ }
+
public <T> BindingBuilderImpl<T> bind(TypeLiteral<T> typeLiteral) {
return bind(Key.get(typeLiteral));
}
@@ -183,7 +206,17 @@
public <T> BindingBuilderImpl<T> bind(Class<T> clazz) {
return bind(Key.get(clazz));
}
+
+ public LinkedBindingBuilder<Autobinder> autobind(final Annotation annotation) {
+ autobinderAnnotations.add(annotation.annotationType());
+ return bind(Autobinder.class).annotatedWith(annotation);
+ }
+ public LinkedBindingBuilder<Autobinder> autobind(final Class<? extends Annotation> annotationType) {
+ autobinderAnnotations.add(annotationType);
+ return bind(Autobinder.class).annotatedWith(annotationType);
+ }
+
public ConstantBindingBuilderImpl bindConstant() {
ConstantBindingBuilderImpl constantBuilder
= new ConstantBindingBuilderImpl(this, source());
@@ -241,9 +274,15 @@
Injector createInjector() throws CreationException {
stopwatch.resetAndLog(logger, "Configuration");
- Map<Key<?>, BindingImpl<?>> bindings = new HashMap<Key<?>, BindingImpl<?>>();
+ Map<Key<?>, BindingImpl<?>> bindings = new ConcurrentHashMap<Key<?>, BindingImpl<?>>();
injector = new InjectorImpl(
- proxyFactoryBuilder.create(), bindings, scopes);
+ proxyFactoryBuilder.create(), bindings, scopes, this, autobinderAnnotations);
+
+ updateInjector();
+ return injector;
+ }
+
+ void updateInjector() {
injector.setErrorHandler(configurationErrorHandler);
createConstantBindings();
@@ -252,7 +291,7 @@
final List<ContextualCallable<Void>> preloaders
= new ArrayList<ContextualCallable<Void>>();
- createBindings(preloaders);
+ final List<BindingImpl<?>> bindings = createBindings(preloaders);
stopwatch.resetAndLog(logger, "Binding creation");
@@ -260,7 +299,8 @@
stopwatch.resetAndLog(logger, "Binding indexing");
- for (CreationListener creationListener : creationListeners) {
+ while (!creationListeners.isEmpty()) {
+ final CreationListener creationListener = creationListeners.removeFirst();
creationListener.notify(injector);
}
@@ -288,7 +328,8 @@
stopwatch.resetAndLog(logger, "Static member injection");
// Inject pre-existing instances.
- for (CreationListener instanceInjector : instanceInjectors) {
+ while (!instanceInjectors.isEmpty()) {
+ final CreationListener instanceInjector = instanceInjectors.removeFirst();
instanceInjector.notify(injector);
}
@@ -298,8 +339,16 @@
runPreloaders(injector, preloaders);
stopwatch.resetAndLog(logger, "Preloading");
+
+ // Mark all the bindings complete so other threads can use them.
+ // Until this call is made all other threads are blocked if they
+ // try to use the binding since it may not have all its dependencies
+ // injected yet.
+ for (final BindingImpl<?> bindingImpl : bindings) {
+ bindingImpl.bindingCompleted();
+ }
- return injector;
+ stopwatch.resetAndLog(logger, "Completing bindings");
}
private void runPreloaders(InjectorImpl injector,
@@ -314,13 +363,17 @@
});
}
- private void createBindings(List<ContextualCallable<Void>> preloaders) {
+ private List<BindingImpl<?>> createBindings(List<ContextualCallable<Void>> preloaders) {
+ final List<BindingImpl<?>> bindings = new ArrayList<BindingImpl<?>>(bindingBuilders.size());
for (BindingBuilderImpl<?> builder : bindingBuilders) {
- createBinding(builder, preloaders);
+ final BindingImpl<?> binding = createBinding(builder, preloaders);
+ bindings.add(binding);
}
+ bindingBuilders.clear();
+ return bindings;
}
- private <T> void createBinding(BindingBuilderImpl<T> builder,
+ private <T> BindingImpl<T> createBinding(BindingBuilderImpl<T> builder,
List<ContextualCallable<Void>> preloaders) {
final Key<T> key = builder.getKey();
final InternalFactory<? extends T> factory
@@ -342,17 +395,23 @@
addError(builder.getSource(), ErrorMessages.PRELOAD_NOT_ALLOWED);
}
}
+ return (BindingImpl<T>) binding;
}
private void createConstantBindings() {
for (ConstantBindingBuilderImpl builder : constantBindingBuilders) {
createConstantBinding(builder);
}
+ constantBindingBuilders.clear();
}
private void createConstantBinding(ConstantBindingBuilderImpl builder) {
if (builder.hasValue()) {
- putBinding(builder.createBinding(injector));
+ BindingImpl<?> binding = builder.createBinding(injector);
+ // Constant bindings have no dependencies so mark them complete
+ // immediatly.
+ binding.bindingCompleted();
+ putBinding(binding);
}
else {
addError(builder.getSource(), ErrorMessages.MISSING_CONSTANT_VALUE);
@@ -383,22 +442,25 @@
void putBinding(BindingImpl<?> binding) {
Key<?> key = binding.getKey();
Map<Key<?>, BindingImpl<?>> bindings = injector.internalBindings();
- Binding<?> original = bindings.get(key);
-
- Class<?> rawType = key.getRawType();
- if (FORBIDDEN_TYPES.contains(rawType)) {
- addError(binding.getSource(), ErrorMessages.CANNOT_BIND_TO_GUICE_TYPE,
- rawType.getSimpleName());
- return;
+ // Synchronized to prevent another binding with the same key being added
+ synchronized (bindings) {
+ Binding<?> original = bindings.get(key);
+
+ Class<?> rawType = key.getRawType();
+ if (FORBIDDEN_TYPES.contains(rawType)) {
+ addError(binding.getSource(), ErrorMessages.CANNOT_BIND_TO_GUICE_TYPE,
+ rawType.getSimpleName());
+ return;
+ }
+
+ if (bindings.containsKey(key)) {
+ addError(binding.getSource(), ErrorMessages.BINDING_ALREADY_SET, key,
+ original.getSource());
+ }
+ else {
+ bindings.put(key, binding);
+ }
}
-
- if (bindings.containsKey(key)) {
- addError(binding.getSource(), ErrorMessages.BINDING_ALREADY_SET, key,
- original.getSource());
- }
- else {
- bindings.put(key, binding);
- }
}
/**
Index: src/com/google/inject/BindingImpl.java
===================================================================
--- src/com/google/inject/BindingImpl.java (revision 376)
+++ src/com/google/inject/BindingImpl.java (working copy)
@@ -27,6 +27,7 @@
final Key<T> key;
final Object source;
final InternalFactory<? extends T> internalFactory;
+ Thread creationThread;
BindingImpl(InjectorImpl injector, Key<T> key, Object source,
InternalFactory<? extends T> internalFactory) {
@@ -34,6 +35,7 @@
this.key = key;
this.source = source;
this.internalFactory = internalFactory;
+ creationThread = Thread.currentThread();
}
public Key<T> getKey() {
@@ -47,6 +49,7 @@
volatile Provider<T> provider;
public Provider<T> getProvider() {
+ waitForCompletion();
if (provider == null) {
provider = injector.getProvider(key);
}
@@ -54,6 +57,7 @@
}
InternalFactory<? extends T> getInternalFactory() {
+ waitForCompletion();
return internalFactory;
}
@@ -68,7 +72,36 @@
boolean isConstant() {
return internalFactory instanceof ConstantFactory<?>;
}
+
+ /**
+ * Indicate that the binding is complete and therefore can be used.
+ */
+ synchronized void bindingCompleted() {
+ assert Thread.currentThread().equals(creationThread);
+ creationThread = null;
+ notifyAll();
+ }
+ /**
+ * If necessary block until the binding is complete.
+ * This will never block the thread that created the binding
+ * As it may need to get the factory etc before it's complete
+ * to inject into other objects.
+ *
+ * However once that thread leaves the update injector block all
+ * objects will be completed, so it's safe.
+ */
+ private synchronized void waitForCompletion() {
+ while (creationThread != null && !creationThread.equals(Thread.currentThread())) {
+ try {
+ wait();
+ } catch (InterruptedException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ }
+ }
+
public String toString() {
return new ToStringBuilder(BindingImpl.class)
.add("key", key)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment