Address duplicate binding issues
- Deduplicate LinkedKeyBinding by key before falling back to the target key - Rather than just dropping the Guice keys during deduplication, retains the original Guice key if possible. Otherwise bindings may fail to resolve, as is the case with map bindings - Avoid duplicating beans for untargetted bindings - Avoid duplicating multibindings
This commit is contained in:
@@ -16,6 +16,9 @@
|
|||||||
|
|
||||||
package org.springframework.guice.annotation;
|
package org.springframework.guice.annotation;
|
||||||
|
|
||||||
|
import java.lang.annotation.Annotation;
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
@@ -23,24 +26,30 @@ import java.util.HashMap;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Map.Entry;
|
import java.util.Map.Entry;
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
import java.util.function.Predicate;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
import com.google.inject.Binding;
|
import com.google.inject.Binding;
|
||||||
|
import com.google.inject.ConfigurationException;
|
||||||
import com.google.inject.Guice;
|
import com.google.inject.Guice;
|
||||||
import com.google.inject.Injector;
|
import com.google.inject.Injector;
|
||||||
import com.google.inject.Key;
|
import com.google.inject.Key;
|
||||||
import com.google.inject.Module;
|
import com.google.inject.Module;
|
||||||
import com.google.inject.Scopes;
|
import com.google.inject.Scopes;
|
||||||
import com.google.inject.Stage;
|
import com.google.inject.Stage;
|
||||||
|
import com.google.inject.TypeLiteral;
|
||||||
|
import com.google.inject.internal.BindingImpl;
|
||||||
import com.google.inject.name.Named;
|
import com.google.inject.name.Named;
|
||||||
import com.google.inject.spi.Element;
|
import com.google.inject.spi.Element;
|
||||||
import com.google.inject.spi.ElementSource;
|
import com.google.inject.spi.ElementSource;
|
||||||
import com.google.inject.spi.Elements;
|
import com.google.inject.spi.Elements;
|
||||||
import com.google.inject.spi.LinkedKeyBinding;
|
import com.google.inject.spi.LinkedKeyBinding;
|
||||||
|
import com.google.inject.spi.Message;
|
||||||
import com.google.inject.spi.PrivateElements;
|
import com.google.inject.spi.PrivateElements;
|
||||||
|
import com.google.inject.spi.UntargettedBinding;
|
||||||
import org.apache.commons.logging.Log;
|
import org.apache.commons.logging.Log;
|
||||||
import org.apache.commons.logging.LogFactory;
|
import org.apache.commons.logging.LogFactory;
|
||||||
|
|
||||||
@@ -86,12 +95,28 @@ class ModuleRegistryConfiguration implements BeanDefinitionRegistryPostProcessor
|
|||||||
|
|
||||||
private static final String SPRING_GUICE_STAGE_PROPERTY_NAME = "spring.guice.stage";
|
private static final String SPRING_GUICE_STAGE_PROPERTY_NAME = "spring.guice.stage";
|
||||||
|
|
||||||
|
private static final List<String> SPRING_GUICE_IGNORED_ANNOTATION_PREFIXES = Arrays.asList(
|
||||||
|
"com.google.inject.multibindings", "com.google.inject.internal.Element",
|
||||||
|
"com.google.inject.internal.UniqueAnnotations", "com.google.inject.internal.RealOptionalBinder");
|
||||||
|
|
||||||
private final Log logger = LogFactory.getLog(getClass());
|
private final Log logger = LogFactory.getLog(getClass());
|
||||||
|
|
||||||
private ApplicationContext applicationContext;
|
private ApplicationContext applicationContext;
|
||||||
|
|
||||||
private boolean enableJustInTimeBinding = true;
|
private boolean enableJustInTimeBinding = true;
|
||||||
|
|
||||||
|
protected static final Method GUICE_BINDINGIMPL_WITHKEY;
|
||||||
|
|
||||||
|
static {
|
||||||
|
try {
|
||||||
|
GUICE_BINDINGIMPL_WITHKEY = BindingImpl.class.getDeclaredMethod("withKey", Key.class);
|
||||||
|
GUICE_BINDINGIMPL_WITHKEY.setAccessible(true);
|
||||||
|
}
|
||||||
|
catch (NoSuchMethodException ex) {
|
||||||
|
throw new RuntimeException(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
|
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
|
||||||
this.applicationContext = applicationContext;
|
this.applicationContext = applicationContext;
|
||||||
@@ -106,6 +131,11 @@ class ModuleRegistryConfiguration implements BeanDefinitionRegistryPostProcessor
|
|||||||
modules.add(new SpringModule((ConfigurableListableBeanFactory) registry, this.enableJustInTimeBinding));
|
modules.add(new SpringModule((ConfigurableListableBeanFactory) registry, this.enableJustInTimeBinding));
|
||||||
Map<Key<?>, Binding<?>> bindings = new HashMap<Key<?>, Binding<?>>();
|
Map<Key<?>, Binding<?>> bindings = new HashMap<Key<?>, Binding<?>>();
|
||||||
List<Element> elements = Elements.getElements(Stage.TOOL, modules);
|
List<Element> elements = Elements.getElements(Stage.TOOL, modules);
|
||||||
|
List<Message> errors = elements.stream().filter((e) -> e instanceof Message).map((e) -> (Message) e)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
if (!errors.isEmpty()) {
|
||||||
|
throw new ConfigurationException(errors);
|
||||||
|
}
|
||||||
if (this.applicationContext.getEnvironment().getProperty(SPRING_GUICE_DEDUPE_BINDINGS_PROPERTY_NAME,
|
if (this.applicationContext.getEnvironment().getProperty(SPRING_GUICE_DEDUPE_BINDINGS_PROPERTY_NAME,
|
||||||
Boolean.class, false)) {
|
Boolean.class, false)) {
|
||||||
elements = removeDuplicates(elements);
|
elements = removeDuplicates(elements);
|
||||||
@@ -146,34 +176,53 @@ class ModuleRegistryConfiguration implements BeanDefinitionRegistryPostProcessor
|
|||||||
Stage stage = this.applicationContext.getEnvironment().getProperty(SPRING_GUICE_STAGE_PROPERTY_NAME,
|
Stage stage = this.applicationContext.getEnvironment().getProperty(SPRING_GUICE_STAGE_PROPERTY_NAME,
|
||||||
Stage.class, Stage.PRODUCTION);
|
Stage.class, Stage.PRODUCTION);
|
||||||
boolean ifLazyInit = stage.equals(Stage.DEVELOPMENT);
|
boolean ifLazyInit = stage.equals(Stage.DEVELOPMENT);
|
||||||
for (Entry<Key<?>, Binding<?>> entry : bindings.entrySet()) {
|
Map<? extends Key<?>, List<LinkedKeyBinding<?>>> linkedBindingsByKey = bindings.values().stream()
|
||||||
if (entry.getKey().getTypeLiteral().getRawType().equals(Injector.class) || SpringModule.SPRING_GUICE_SOURCE
|
.filter((e) -> e instanceof LinkedKeyBinding).map((e) -> ((LinkedKeyBinding<?>) e))
|
||||||
.equals(Optional.ofNullable(entry.getValue().getSource()).map(Object::toString).orElse(""))) {
|
.collect(Collectors.groupingBy(LinkedKeyBinding::getLinkedKey));
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (entry.getKey().getAnnotationType() != null
|
|
||||||
&& entry.getKey().getAnnotationType().getName().startsWith("com.google.inject.multibindings")) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (entry.getKey().getAnnotationType() != null && entry.getKey().getAnnotationType().getName()
|
|
||||||
.startsWith("com.google.inject.internal.UniqueAnnotations")) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
Map<? extends Key<?>, ? extends Binding<?>> guiceBindingsByKey = bindings.entrySet().stream()
|
||||||
|
.filter((entry) -> {
|
||||||
|
Binding<?> binding = entry.getValue();
|
||||||
|
Key<?> key = entry.getKey();
|
||||||
|
Object source = binding.getSource();
|
||||||
|
|
||||||
|
TypeLiteral<?> typeLiteral = key.getTypeLiteral();
|
||||||
|
Class<? extends Annotation> annotationType = key.getAnnotationType();
|
||||||
|
|
||||||
|
if (binding instanceof UntargettedBinding && linkedBindingsByKey.containsKey(binding.getKey())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (typeLiteral.getRawType().equals(Injector.class) || SpringModule.SPRING_GUICE_SOURCE
|
||||||
|
.equals(Optional.ofNullable(source).map(Object::toString).orElse(""))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (annotationType != null) {
|
||||||
|
if (SPRING_GUICE_IGNORED_ANNOTATION_PREFIXES.stream()
|
||||||
|
.anyMatch((prefix) -> annotationType.getName().startsWith(prefix))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}).collect(Collectors.toMap(Entry::getKey, Entry::getValue));
|
||||||
|
|
||||||
|
for (Entry<? extends Key<?>, ? extends Binding<?>> entry : guiceBindingsByKey.entrySet()) {
|
||||||
Binding<?> binding = entry.getValue();
|
Binding<?> binding = entry.getValue();
|
||||||
Key<?> key = entry.getKey();
|
Key<?> key = entry.getKey();
|
||||||
Object source = binding.getSource();
|
|
||||||
|
TypeLiteral<?> typeLiteral = key.getTypeLiteral();
|
||||||
|
Class<? extends Annotation> annotationType = key.getAnnotationType();
|
||||||
|
|
||||||
RootBeanDefinition bean = new RootBeanDefinition(GuiceFactoryBean.class);
|
RootBeanDefinition bean = new RootBeanDefinition(GuiceFactoryBean.class);
|
||||||
ConstructorArgumentValues args = new ConstructorArgumentValues();
|
ConstructorArgumentValues args = new ConstructorArgumentValues();
|
||||||
args.addIndexedArgumentValue(0, key.getTypeLiteral().getRawType());
|
args.addIndexedArgumentValue(0, typeLiteral.getRawType());
|
||||||
args.addIndexedArgumentValue(1, key);
|
args.addIndexedArgumentValue(1, key);
|
||||||
args.addIndexedArgumentValue(2, Scopes.isSingleton(binding));
|
args.addIndexedArgumentValue(2, Scopes.isSingleton(binding));
|
||||||
bean.setConstructorArgumentValues(args);
|
bean.setConstructorArgumentValues(args);
|
||||||
bean.setTargetType(ResolvableType.forType(key.getTypeLiteral().getType()));
|
bean.setTargetType(ResolvableType.forType(typeLiteral.getType()));
|
||||||
if (!Scopes.isSingleton(binding)) {
|
if (!Scopes.isSingleton(binding)) {
|
||||||
bean.setScope(ConfigurableBeanFactory.SCOPE_PROTOTYPE);
|
bean.setScope(ConfigurableBeanFactory.SCOPE_PROTOTYPE);
|
||||||
}
|
}
|
||||||
|
Object source = binding.getSource();
|
||||||
if (source instanceof ElementSource) {
|
if (source instanceof ElementSource) {
|
||||||
bean.setResourceDescription(((ElementSource) source).getDeclaringSource().toString());
|
bean.setResourceDescription(((ElementSource) source).getDeclaringSource().toString());
|
||||||
}
|
}
|
||||||
@@ -181,10 +230,10 @@ class ModuleRegistryConfiguration implements BeanDefinitionRegistryPostProcessor
|
|||||||
bean.setResourceDescription(SpringModule.SPRING_GUICE_SOURCE);
|
bean.setResourceDescription(SpringModule.SPRING_GUICE_SOURCE);
|
||||||
}
|
}
|
||||||
bean.setAttribute(SpringModule.SPRING_GUICE_SOURCE, true);
|
bean.setAttribute(SpringModule.SPRING_GUICE_SOURCE, true);
|
||||||
if (key.getAnnotationType() != null) {
|
if (annotationType != null) {
|
||||||
bean.addQualifier(new AutowireCandidateQualifier(Qualifier.class, getValueAttributeForNamed(key)));
|
String nameValue = getValueAttributeForNamed(key);
|
||||||
bean.addQualifier(
|
bean.addQualifier(new AutowireCandidateQualifier(Qualifier.class, nameValue));
|
||||||
new AutowireCandidateQualifier(key.getAnnotationType(), getValueAttributeForNamed(key)));
|
bean.addQualifier(new AutowireCandidateQualifier(annotationType, nameValue));
|
||||||
}
|
}
|
||||||
if (ifLazyInit) {
|
if (ifLazyInit) {
|
||||||
bean.setLazyInit(true);
|
bean.setLazyInit(true);
|
||||||
@@ -252,69 +301,62 @@ class ModuleRegistryConfiguration implements BeanDefinitionRegistryPostProcessor
|
|||||||
* @return de-duplicated list of bindings
|
* @return de-duplicated list of bindings
|
||||||
*/
|
*/
|
||||||
protected List<Element> removeDuplicates(List<Element> elements) {
|
protected List<Element> removeDuplicates(List<Element> elements) {
|
||||||
List<Element> duplicateElements = elements.stream().filter((e) -> e instanceof Binding)
|
Predicate<Element> hasSpringSource = ((element) -> element.getSource() != null
|
||||||
.map((e) -> (Binding<?>) e)
|
&& element.getSource().toString().contains(SpringModule.SPRING_GUICE_SOURCE));
|
||||||
.collect(Collectors.groupingBy(ModuleRegistryConfiguration::getLinkedKeyIfRequired)).entrySet().stream()
|
|
||||||
.filter((e) -> e.getValue().size() > 1 && e.getValue().stream()
|
|
||||||
.anyMatch((binding) -> binding.getSource() != null
|
|
||||||
&& binding.getSource().toString().contains(SpringModule.SPRING_GUICE_SOURCE))) // find
|
|
||||||
// duplicates
|
|
||||||
.flatMap((e) -> e.getValue().stream())
|
|
||||||
.filter((e) -> e.getSource() != null
|
|
||||||
&& !e.getSource().toString().contains(SpringModule.SPRING_GUICE_SOURCE))
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
|
|
||||||
@SuppressWarnings("unlikely-arg-type")
|
List<? extends Binding<?>> bindings = elements.stream().filter((e) -> e instanceof Binding)
|
||||||
List<Element> dedupedElements = elements.stream().filter((e) -> {
|
.map((e) -> (Binding<?>) e).collect(Collectors.toList());
|
||||||
|
|
||||||
|
Map<? extends Key<?>, ? extends Key<?>> injectionKeys = bindings.stream()
|
||||||
|
.collect(Collectors.groupingBy(Binding::getKey)).entrySet().stream()
|
||||||
|
.collect(Collectors.toMap(Map.Entry::getKey, (e) -> {
|
||||||
|
List<? extends Binding<?>> keyBindings = e.getValue();
|
||||||
|
if (keyBindings.size() == 1) {
|
||||||
|
// If a linked binding isn't duplicated by its key, try the linked
|
||||||
|
// injection key
|
||||||
|
Binding<?> binding = keyBindings.get(0);
|
||||||
|
if (binding instanceof LinkedKeyBinding) {
|
||||||
|
return ((LinkedKeyBinding<?>) binding).getLinkedKey();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return e.getKey();
|
||||||
|
}));
|
||||||
|
|
||||||
|
Map<? extends Key<?>, List<? extends Binding<?>>> duplicateBindings = bindings.stream()
|
||||||
|
.collect(Collectors.groupingBy((e) -> injectionKeys.get(e.getKey()))).entrySet().stream()
|
||||||
|
.filter((e) -> e.getValue().size() > 1 && e.getValue().stream().anyMatch(hasSpringSource))
|
||||||
|
.collect(Collectors.toMap(Entry::getKey, Entry::getValue));
|
||||||
|
|
||||||
|
return elements.stream().flatMap((e) -> {
|
||||||
if (e instanceof Binding) {
|
if (e instanceof Binding) {
|
||||||
return !duplicateElements.contains(new SourceComparableBinding((Binding<?>) e));
|
Binding<?> b = (Binding<?>) e;
|
||||||
}
|
Key<?> key = injectionKeys.get(b.getKey());
|
||||||
else {
|
List<? extends Binding<?>> duplicates = duplicateBindings.get(key);
|
||||||
return true;
|
if (duplicates != null) {
|
||||||
|
if (hasSpringSource.test(b)) {
|
||||||
|
return duplicates.stream().filter(hasSpringSource.negate())
|
||||||
|
.map((guiceBinding) -> withKey(b, guiceBinding.getKey()));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Remove the duplicate Guice binding
|
||||||
|
return Stream.empty();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
return Stream.of(e);
|
||||||
}).collect(Collectors.toList());
|
}).collect(Collectors.toList());
|
||||||
return dedupedElements;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Key<?> getLinkedKeyIfRequired(Binding<?> binding) {
|
/*
|
||||||
if (binding == null) {
|
* Re-key the Spring source binding with the Guice key for the built-in bindings.
|
||||||
return null;
|
*/
|
||||||
|
private Binding<?> withKey(Binding<?> binding, Key<?> key) {
|
||||||
|
try {
|
||||||
|
return (BindingImpl<?>) GUICE_BINDINGIMPL_WITHKEY.invoke(binding, key);
|
||||||
}
|
}
|
||||||
|
catch (IllegalAccessException | InvocationTargetException ex) {
|
||||||
if (binding instanceof LinkedKeyBinding) {
|
throw new RuntimeException(ex);
|
||||||
LinkedKeyBinding<?> linkedBinding = (LinkedKeyBinding<?>) binding;
|
|
||||||
return linkedBinding.getLinkedKey();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return binding.getKey();
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("checkstyle:EqualsHashCode")
|
|
||||||
private static class SourceComparableBinding {
|
|
||||||
|
|
||||||
private Binding<?> binding;
|
|
||||||
|
|
||||||
SourceComparableBinding(Binding<?> binding) {
|
|
||||||
this.binding = binding;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object obj) {
|
|
||||||
if (obj instanceof Binding) {
|
|
||||||
Binding<?> compareTo = (Binding<?>) obj;
|
|
||||||
if (compareTo.getSource() != null && this.binding != null) {
|
|
||||||
return this.binding.equals(compareTo)
|
|
||||||
&& Objects.equals(this.binding.getSource(), compareTo.getSource());
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return Objects.equals(this.binding, compareTo);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -168,9 +168,6 @@ public class SpringModule extends AbstractModule {
|
|||||||
type = clazz;
|
type = clazz;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (type == null) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
Provider<?> typeProvider = BeanFactoryProvider.typed(beanFactory, type, bindingAnnotation);
|
Provider<?> typeProvider = BeanFactoryProvider.typed(beanFactory, type, bindingAnnotation);
|
||||||
Provider<?> namedProvider = BeanFactoryProvider.named(beanFactory, name, type, bindingAnnotation);
|
Provider<?> namedProvider = BeanFactoryProvider.named(beanFactory, name, type, bindingAnnotation);
|
||||||
|
|
||||||
@@ -305,7 +302,8 @@ public class SpringModule extends AbstractModule {
|
|||||||
if (!this.matcher.matches(name, type)) {
|
if (!this.matcher.matches(name, type)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (type.getTypeName().startsWith("com.google.inject")) {
|
String typeName = type.getTypeName();
|
||||||
|
if (typeName.startsWith("com.google.inject") || typeName.startsWith("javax.inject.Provider")) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (type instanceof ParameterizedType) {
|
if (type instanceof ParameterizedType) {
|
||||||
@@ -318,13 +316,11 @@ public class SpringModule extends AbstractModule {
|
|||||||
}
|
}
|
||||||
Key<?> key = bindingAnnotation.map((a) -> (Key<Object>) Key.get(type, a)).orElse((Key<Object>) Key.get(type));
|
Key<?> key = bindingAnnotation.map((a) -> (Key<Object>) Key.get(type, a)).orElse((Key<Object>) Key.get(type));
|
||||||
StageTypeKey stageTypeKey = new StageTypeKey(binder.currentStage(), key);
|
StageTypeKey stageTypeKey = new StageTypeKey(binder.currentStage(), key);
|
||||||
if (this.bound.get(stageTypeKey) == null) {
|
// Only bind one provider for each type
|
||||||
// Only bind one provider for each type
|
if (this.bound.put(stageTypeKey, typeProvider) == null) {
|
||||||
|
|
||||||
binder.withSource(SPRING_GUICE_SOURCE).bind(key).toProvider(typeProvider);
|
binder.withSource(SPRING_GUICE_SOURCE).bind(key).toProvider(typeProvider);
|
||||||
this.bound.put(stageTypeKey, typeProvider);
|
|
||||||
}
|
}
|
||||||
// But allow binding to named beans if not already bound
|
// Allow binding to named beans if not already bound
|
||||||
if (!name.equals(getNameFromBindingAnnotation(bindingAnnotation))) {
|
if (!name.equals(getNameFromBindingAnnotation(bindingAnnotation))) {
|
||||||
binder.withSource(SPRING_GUICE_SOURCE).bind(TypeLiteral.get(type)).annotatedWith(Names.named(name))
|
binder.withSource(SPRING_GUICE_SOURCE).bind(TypeLiteral.get(type)).annotatedWith(Names.named(name))
|
||||||
.toProvider(namedProvider);
|
.toProvider(namedProvider);
|
||||||
@@ -377,6 +373,11 @@ public class SpringModule extends AbstractModule {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "StageTypeKey[key=" + this.key + ", stage=" + this.stage + "]";
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("checkstyle:FinalClass")
|
@SuppressWarnings("checkstyle:FinalClass")
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ import org.springframework.guice.annotation.EnableGuiceModulesTests;
|
|||||||
* @author Dave Syer
|
* @author Dave Syer
|
||||||
*/
|
*/
|
||||||
@Suite
|
@Suite
|
||||||
@SelectClasses({ BindingDeduplicationTests.class, EnableGuiceModulesTests.class })
|
@SelectClasses({ MapBindingDeduplicationTests.class, BindingDeduplicationTests.class, EnableGuiceModulesTests.class })
|
||||||
@Disabled
|
@Disabled
|
||||||
public class AdhocTestSuite {
|
public class AdhocTestSuite {
|
||||||
|
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import java.lang.annotation.Retention;
|
|||||||
import java.lang.annotation.RetentionPolicy;
|
import java.lang.annotation.RetentionPolicy;
|
||||||
import java.lang.annotation.Target;
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
import javax.inject.Named;
|
import javax.inject.Named;
|
||||||
import javax.inject.Qualifier;
|
import javax.inject.Qualifier;
|
||||||
|
|
||||||
@@ -35,6 +36,7 @@ import com.google.inject.throwingproviders.CheckedProvides;
|
|||||||
import com.google.inject.throwingproviders.ThrowingProviderBinder;
|
import com.google.inject.throwingproviders.ThrowingProviderBinder;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.NoUniqueBeanDefinitionException;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
|
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
@@ -42,6 +44,7 @@ import org.springframework.context.annotation.Configuration;
|
|||||||
import org.springframework.guice.annotation.EnableGuiceModules;
|
import org.springframework.guice.annotation.EnableGuiceModules;
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||||
|
|
||||||
public class BindingAnnotationTests {
|
public class BindingAnnotationTests {
|
||||||
|
|
||||||
@@ -105,6 +108,15 @@ public class BindingAnnotationTests {
|
|||||||
context.close();
|
context.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void verifyBindingAnnotationsDuplicateBeans() {
|
||||||
|
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
|
||||||
|
BindingAnnotationTestsConfig.class);
|
||||||
|
assertThatExceptionOfType(NoUniqueBeanDefinitionException.class)
|
||||||
|
.isThrownBy(() -> assertThat(context.getBean(SomeService.class)).isNotNull());
|
||||||
|
context.close();
|
||||||
|
}
|
||||||
|
|
||||||
public static class SomeDependencyWithQualifierOnProvider {
|
public static class SomeDependencyWithQualifierOnProvider {
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -145,7 +157,7 @@ public class BindingAnnotationTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@BindingAnnotation
|
@BindingAnnotation
|
||||||
@Target({ ElementType.TYPE, ElementType.METHOD, ElementType.FIELD })
|
@Target({ ElementType.TYPE, ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER })
|
||||||
@Retention(RetentionPolicy.RUNTIME)
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
@interface SomeBindingAnnotation {
|
@interface SomeBindingAnnotation {
|
||||||
|
|
||||||
@@ -182,6 +194,32 @@ public class BindingAnnotationTests {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface SomeService {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
static class BaseSomeService implements SomeService {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
static class ShadowingSomeService implements SomeService {
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
ShadowingSomeService(@SomeBindingAnnotation SomeService baseService) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class SomeProvider implements javax.inject.Provider<Object> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object get() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
@EnableGuiceModules
|
@EnableGuiceModules
|
||||||
@Configuration
|
@Configuration
|
||||||
static class BindingAnnotationTestsConfig {
|
static class BindingAnnotationTestsConfig {
|
||||||
@@ -245,6 +283,11 @@ public class BindingAnnotationTests {
|
|||||||
return new SomeStringHolder();
|
return new SomeStringHolder();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
SomeProvider someProvider() {
|
||||||
|
return new SomeProvider();
|
||||||
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
static AbstractModule module() {
|
static AbstractModule module() {
|
||||||
return new AbstractModule() {
|
return new AbstractModule() {
|
||||||
@@ -253,6 +296,9 @@ public class BindingAnnotationTests {
|
|||||||
install(ThrowingProviderBinder.forModule(this));
|
install(ThrowingProviderBinder.forModule(this));
|
||||||
bind(String.class).annotatedWith(SomeBindingAnnotation.class).toInstance("annotated");
|
bind(String.class).annotatedWith(SomeBindingAnnotation.class).toInstance("annotated");
|
||||||
bind(String.class).annotatedWith(SomeOtherBindingAnnotation.class).toInstance("other");
|
bind(String.class).annotatedWith(SomeOtherBindingAnnotation.class).toInstance("other");
|
||||||
|
|
||||||
|
bind(SomeService.class).annotatedWith(SomeBindingAnnotation.class).to(BaseSomeService.class);
|
||||||
|
bind(SomeService.class).to(ShadowingSomeService.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
@CheckedProvides(TestCheckedProvider.class)
|
@CheckedProvides(TestCheckedProvider.class)
|
||||||
|
|||||||
@@ -19,10 +19,15 @@ package org.springframework.guice;
|
|||||||
import com.google.inject.AbstractModule;
|
import com.google.inject.AbstractModule;
|
||||||
import com.google.inject.CreationException;
|
import com.google.inject.CreationException;
|
||||||
import com.google.inject.Module;
|
import com.google.inject.Module;
|
||||||
|
import com.google.inject.Scopes;
|
||||||
|
import com.google.inject.multibindings.Multibinder;
|
||||||
import com.google.inject.multibindings.OptionalBinder;
|
import com.google.inject.multibindings.OptionalBinder;
|
||||||
import org.junit.jupiter.api.AfterAll;
|
import com.google.inject.name.Names;
|
||||||
|
import org.junit.jupiter.api.AfterEach;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.NoUniqueBeanDefinitionException;
|
||||||
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
|
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
@@ -33,20 +38,63 @@ import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
|||||||
|
|
||||||
public class BindingDeduplicationTests {
|
public class BindingDeduplicationTests {
|
||||||
|
|
||||||
@AfterAll
|
@BeforeEach
|
||||||
public static void cleanUp() {
|
public void setup() {
|
||||||
|
System.setProperty("spring.guice.dedup", "true");
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterEach
|
||||||
|
public void cleanUp() {
|
||||||
System.clearProperty("spring.guice.dedup");
|
System.clearProperty("spring.guice.dedup");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void verifyNoDuplicateBindingErrorWhenDedupeEnabled() {
|
public void verifyNoDuplicateBindingErrorWhenDedupeEnabled() {
|
||||||
System.setProperty("spring.guice.dedup", "true");
|
try (AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
|
||||||
|
BindingDeduplicationTestsConfig.class)) {
|
||||||
|
Dependency dependency = context.getBean(Dependency.class);
|
||||||
|
assertThat(dependency).isNotNull();
|
||||||
|
|
||||||
|
OptionalDependency optionalDependency = context.getBean(OptionalDependency.class);
|
||||||
|
assertThat(optionalDependency).isNotNull();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void annotatedBindingDoesNotDuplicate() {
|
||||||
|
try (AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
|
||||||
|
BindingDeduplicationTestsConfig.class)) {
|
||||||
|
FirstInterface firstInterface = context.getBean(FirstInterface.class);
|
||||||
|
assertThat(firstInterface).isNotNull();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void untargettedBindingDoesNotDuplicate() {
|
||||||
|
try (AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
|
||||||
|
BindingDeduplicationTestsConfig.class)) {
|
||||||
|
UntargettedDependency untargettedDependency = context.getBean(UntargettedDependency.class);
|
||||||
|
assertThat(untargettedDependency).isNotNull();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void setBindingDoesNotDuplicate() {
|
||||||
|
try (AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
|
||||||
|
BindingDeduplicationTestsConfig.class)) {
|
||||||
|
SetProvided setProvided = context.getBean(SetProvided.class);
|
||||||
|
assertThat(setProvided).isNotNull();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void springBindingIsDuplicated() {
|
||||||
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
|
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
|
||||||
BindingDeduplicationTestsConfig.class);
|
BindingDeduplicationTestsConfig.class);
|
||||||
SomeDependency someDependency = context.getBean(SomeDependency.class);
|
|
||||||
assertThat(someDependency).isNotNull();
|
assertThatExceptionOfType(NoUniqueBeanDefinitionException.class)
|
||||||
SomeOptionalDependency someOptionalDependency = context.getBean(SomeOptionalDependency.class);
|
.isThrownBy(() -> context.getBean(String.class));
|
||||||
assertThat(someOptionalDependency).isNotNull();
|
|
||||||
context.close();
|
context.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -60,11 +108,47 @@ public class BindingDeduplicationTests {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class SomeDependency {
|
public interface Dependency {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class SomeOptionalDependency {
|
private static class PrivateDependency implements Dependency {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class SomeSingleton {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface OptionalDependency {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class SomeOptionalDependency implements OptionalDependency {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
interface FirstInterface {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SecondInterface {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
static class MultiInterfaceSingleton implements FirstInterface, SecondInterface {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
static class UntargettedDependency {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SetProvided {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class SomeSetProvided implements SetProvided {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -73,23 +157,53 @@ public class BindingDeduplicationTests {
|
|||||||
static class BindingDeduplicationTestsConfig {
|
static class BindingDeduplicationTestsConfig {
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
SomeDependency someBean() {
|
SomeSingleton someSingleton() {
|
||||||
return new SomeDependency();
|
return new SomeSingleton();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
SomeOptionalDependency someOptionalBean() {
|
PrivateDependency privateDependency() {
|
||||||
|
return new PrivateDependency();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
OptionalDependency someOptionalDependency() {
|
||||||
return new SomeOptionalDependency();
|
return new SomeOptionalDependency();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
String barString() {
|
||||||
|
return "bar";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
SomeSetProvided someSetProvided() {
|
||||||
|
return new SomeSetProvided();
|
||||||
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
static Module module() {
|
static Module module() {
|
||||||
return new AbstractModule() {
|
return new AbstractModule() {
|
||||||
@Override
|
@Override
|
||||||
protected void configure() {
|
protected void configure() {
|
||||||
bind(SomeDependency.class).asEagerSingleton();
|
bind(Dependency.class).to(PrivateDependency.class);
|
||||||
OptionalBinder.newOptionalBinder(binder(), SomeOptionalDependency.class).setDefault()
|
bind(SomeSingleton.class).asEagerSingleton();
|
||||||
|
|
||||||
|
OptionalBinder.newOptionalBinder(binder(), OptionalDependency.class).setDefault()
|
||||||
.to(SomeOptionalDependency.class);
|
.to(SomeOptionalDependency.class);
|
||||||
|
|
||||||
|
Multibinder<SetProvided> setBinder = Multibinder.newSetBinder(binder(), SetProvided.class);
|
||||||
|
setBinder.addBinding().toInstance(new SomeSetProvided());
|
||||||
|
|
||||||
|
bind(UntargettedDependency.class);
|
||||||
|
|
||||||
|
// Untargetted binding to provide a singleton for the interface
|
||||||
|
// bindings
|
||||||
|
bind(MultiInterfaceSingleton.class).in(Scopes.SINGLETON);
|
||||||
|
bind(FirstInterface.class).to(MultiInterfaceSingleton.class).in(Scopes.SINGLETON);
|
||||||
|
bind(SecondInterface.class).to(MultiInterfaceSingleton.class).in(Scopes.SINGLETON);
|
||||||
|
|
||||||
|
bind(String.class).annotatedWith(Names.named("fooString")).toInstance("foo");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,153 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2018-2022 the original author or authors.
|
||||||
|
*
|
||||||
|
* 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
|
||||||
|
*
|
||||||
|
* https://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 org.springframework.guice;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import com.google.inject.AbstractModule;
|
||||||
|
import com.google.inject.Module;
|
||||||
|
import com.google.inject.Provider;
|
||||||
|
import com.google.inject.multibindings.MapBinder;
|
||||||
|
import org.junit.jupiter.api.AfterAll;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.core.ResolvableType;
|
||||||
|
import org.springframework.guice.annotation.EnableGuiceModules;
|
||||||
|
import org.springframework.guice.module.SpringModule;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
public class MapBindingDeduplicationTests {
|
||||||
|
|
||||||
|
@AfterAll
|
||||||
|
public static void cleanUp() {
|
||||||
|
System.clearProperty("spring.guice.dedup");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void mapBindingGuiceOnly() {
|
||||||
|
System.setProperty("spring.guice.dedup", "false");
|
||||||
|
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
|
||||||
|
MapBindingGuiceOnlyTestsConfig.class);
|
||||||
|
|
||||||
|
String[] beanNamesForType = context
|
||||||
|
.getBeanNamesForType(ResolvableType.forClassWithGenerics(Map.class, String.class, Provider.class));
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
Map<String, Provider<Dependency>> dependencyProvider = (Map<String, Provider<Dependency>>) context
|
||||||
|
.getBean(beanNamesForType[0]);
|
||||||
|
|
||||||
|
assertThat(dependencyProvider.size()).isEqualTo(2);
|
||||||
|
assertThat(dependencyProvider.get("someQualifier").get()).isInstanceOf(SomeDependency.class);
|
||||||
|
|
||||||
|
context.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void mapBindingConflictingConcreteClass() {
|
||||||
|
System.setProperty("spring.guice.dedup", "true");
|
||||||
|
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
|
||||||
|
MapBindingConcreteClassTestsConfig.class);
|
||||||
|
|
||||||
|
String[] beanNamesForType = context
|
||||||
|
.getBeanNamesForType(ResolvableType.forClassWithGenerics(Map.class, String.class, Provider.class));
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
Map<String, Provider<Dependency>> dependencyProvider = (Map<String, Provider<Dependency>>) context
|
||||||
|
.getBean(beanNamesForType[0]);
|
||||||
|
|
||||||
|
assertThat(dependencyProvider.size()).isEqualTo(2);
|
||||||
|
assertThat(dependencyProvider.get("someQualifier").get()).isInstanceOf(SomeDependency.class);
|
||||||
|
|
||||||
|
SomeDependency someDependency = context.getBean(SomeDependency.class);
|
||||||
|
assertThat(someDependency.getSource()).isEqualTo(SpringModule.SPRING_GUICE_SOURCE);
|
||||||
|
|
||||||
|
context.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Dependency {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class SomeDependency implements Dependency {
|
||||||
|
|
||||||
|
private String source = "guice";
|
||||||
|
|
||||||
|
public void setSource(String source) {
|
||||||
|
this.source = source;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSource() {
|
||||||
|
return this.source;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class SomeOptionalDependency implements Dependency {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@EnableGuiceModules
|
||||||
|
@Configuration
|
||||||
|
static class MapBindingGuiceOnlyTestsConfig {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
static Module module() {
|
||||||
|
return new AbstractModule() {
|
||||||
|
@Override
|
||||||
|
protected void configure() {
|
||||||
|
MapBinder<String, Dependency> bindings = MapBinder.newMapBinder(binder(), String.class,
|
||||||
|
Dependency.class);
|
||||||
|
bindings.addBinding("someQualifier").to(SomeDependency.class);
|
||||||
|
bindings.addBinding("someOtherQualifier").to(SomeOptionalDependency.class);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@EnableGuiceModules
|
||||||
|
@Configuration
|
||||||
|
static class MapBindingConcreteClassTestsConfig {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
SomeDependency dependency() {
|
||||||
|
SomeDependency someDependency = new SomeDependency();
|
||||||
|
someDependency.setSource(SpringModule.SPRING_GUICE_SOURCE);
|
||||||
|
return someDependency;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
static Module module() {
|
||||||
|
return new AbstractModule() {
|
||||||
|
@Override
|
||||||
|
protected void configure() {
|
||||||
|
MapBinder<String, Dependency> bindings = MapBinder.newMapBinder(binder(), String.class,
|
||||||
|
Dependency.class);
|
||||||
|
bindings.addBinding("someQualifier").to(SomeDependency.class);
|
||||||
|
// Intentionally duplicate the binding to ensure that every key is
|
||||||
|
// available after deduplication
|
||||||
|
bindings.addBinding("someQualifier").to(SomeDependency.class);
|
||||||
|
bindings.addBinding("someOtherQualifier").to(SomeOptionalDependency.class);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user