Created
July 7, 2014 17:54
-
-
Save gissuebot/18ae84667aa174448097 to your computer and use it in GitHub Desktop.
Migrated attachment for Guice issue 49, comment 18
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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