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/104ab2afcfc8c33089f9 to your computer and use it in GitHub Desktop.
Save gissuebot/104ab2afcfc8c33089f9 to your computer and use it in GitHub Desktop.
Migrated attachment for Guice issue 49, comment 15
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,282 @@
+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());
+ }
+
+ 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 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 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,23 @@
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 LinkedBindingBuilder<T> builder = binder.bind(key);
+ if (autobinder.autobind(key, 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 +307,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 +338,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)
@@ -19,6 +19,9 @@
import com.google.inject.InjectorImpl.SingleMemberInjector;
import com.google.inject.Key.AnnotationStrategy;
import static com.google.inject.Scopes.SINGLETON;
+
+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;
@@ -34,6 +37,8 @@
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;
@@ -63,6 +68,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,8 +138,8 @@
return stage;
}
- final List<CreationListener> creationListeners
- = new ArrayList<CreationListener>();
+ final LinkedList<CreationListener> creationListeners
+ = new LinkedList<CreationListener>();
final List<CreationListener> instanceInjectors
= new ArrayList<CreationListener>();
@@ -183,7 +190,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());
@@ -243,7 +260,13 @@
Map<Key<?>, BindingImpl<?>> bindings = new HashMap<Key<?>, BindingImpl<?>>();
injector = new InjectorImpl(
- proxyFactoryBuilder.create(), bindings, scopes);
+ proxyFactoryBuilder.create(), bindings, scopes, this, autobinderAnnotations);
+
+ updateInjector();
+ return injector;
+ }
+
+ void updateInjector() {
injector.setErrorHandler(configurationErrorHandler);
createConstantBindings();
@@ -260,7 +283,8 @@
stopwatch.resetAndLog(logger, "Binding indexing");
- for (CreationListener creationListener : creationListeners) {
+ while (!creationListeners.isEmpty()) {
+ final CreationListener creationListener = creationListeners.removeFirst();
creationListener.notify(injector);
}
@@ -298,8 +322,6 @@
runPreloaders(injector, preloaders);
stopwatch.resetAndLog(logger, "Preloading");
-
- return injector;
}
private void runPreloaders(InjectorImpl injector,
@@ -315,9 +337,11 @@
}
private void createBindings(List<ContextualCallable<Void>> preloaders) {
- for (BindingBuilderImpl<?> builder : bindingBuilders) {
+ for (Iterator<BindingBuilderImpl<?>> it = bindingBuilders.iterator(); it.hasNext();) {
+ final BindingBuilderImpl<?> builder = (BindingBuilderImpl<?>) it.next();
createBinding(builder, preloaders);
}
+ bindingBuilders.clear();
}
private <T> void createBinding(BindingBuilderImpl<T> builder,
@@ -348,6 +372,7 @@
for (ConstantBindingBuilderImpl builder : constantBindingBuilders) {
createConstantBinding(builder);
}
+ constantBindingBuilders.clear();
}
private void createConstantBinding(ConstantBindingBuilderImpl builder) {
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment