Support parameterizedContainer in bean conditions

Add a `parameterizedContainer` attribute to `ConditionalOnBean` and
`ConditionalOnMissingBean` which can be used to support generic types
when checking for the presence of beans.

Closes gh-14940
pull/14950/head
Phillip Webb 6 years ago
parent 4d3d711e0e
commit 9f858e759c

@ -75,7 +75,7 @@ final class BeanTypeRegistry implements SmartInitializingSingleton {
private final DefaultListableBeanFactory beanFactory;
private final Map<String, Class<?>> beanTypes = new HashMap<>();
private final Map<String, ResolvableType> beanTypes = new HashMap<>();
private final Map<String, RootBeanDefinition> beanDefinitions = new HashMap<>();
@ -89,16 +89,20 @@ final class BeanTypeRegistry implements SmartInitializingSingleton {
* the case of {@link FactoryBean FactoryBeans}. Will include singletons but will not
* cause early bean initialization.
* @param type the class or interface to match (must not be {@code null})
* @param typeExtractor function used to extract the actual type
* @return the names of beans (or objects created by FactoryBeans) matching the given
* object type (including subclasses), or an empty set if none
*/
public Set<String> getNamesForType(Class<?> type) {
public Set<String> getNamesForType(Class<?> type, TypeExtractor typeExtractor) {
updateTypesIfNecessary();
return this.beanTypes.entrySet().stream()
.filter((entry) -> entry.getValue() != null
&& type.isAssignableFrom(entry.getValue()))
.map(Map.Entry::getKey)
.collect(Collectors.toCollection(LinkedHashSet::new));
return this.beanTypes.entrySet().stream().filter((entry) -> {
Class<?> beanType = extractType(entry.getValue(), typeExtractor);
return beanType != null && type.isAssignableFrom(beanType);
}).map(Map.Entry::getKey).collect(Collectors.toCollection(LinkedHashSet::new));
}
private Class<?> extractType(ResolvableType type, TypeExtractor extractor) {
return (type != null) ? extractor.getBeanType(type) : null;
}
/**
@ -114,7 +118,7 @@ final class BeanTypeRegistry implements SmartInitializingSingleton {
updateTypesIfNecessary();
return this.beanTypes.entrySet().stream()
.filter((entry) -> entry.getValue() != null && AnnotationUtils
.findAnnotation(entry.getValue(), annotation) != null)
.findAnnotation(entry.getValue().resolve(), annotation) != null)
.map(Map.Entry::getKey)
.collect(Collectors.toCollection(LinkedHashSet::new));
}
@ -127,19 +131,22 @@ final class BeanTypeRegistry implements SmartInitializingSingleton {
}
private void updateTypesIfNecessary() {
this.beanFactory.getBeanNamesIterator().forEachRemaining((name) -> {
this.beanFactory.getBeanNamesIterator()
.forEachRemaining(this::updateTypesIfNecessary);
}
private void updateTypesIfNecessary(String name) {
if (!this.beanTypes.containsKey(name)) {
addBeanType(name);
}
else {
updateBeanType(name);
}
});
}
private void addBeanType(String name) {
if (this.beanFactory.containsSingleton(name)) {
this.beanTypes.put(name, this.beanFactory.getType(name));
this.beanTypes.put(name, getType(name, null));
}
else if (!this.beanFactory.isAlias(name)) {
addBeanTypeForNonAliasDefinition(name);
@ -147,9 +154,9 @@ final class BeanTypeRegistry implements SmartInitializingSingleton {
}
private void addBeanTypeForNonAliasDefinition(String name) {
RootBeanDefinition beanDefinition = getBeanDefinition(name);
if (beanDefinition != null) {
addBeanTypeForNonAliasDefinition(name, beanDefinition);
RootBeanDefinition definition = getBeanDefinition(name);
if (definition != null) {
addBeanTypeForNonAliasDefinition(name, definition);
}
}
@ -157,34 +164,46 @@ final class BeanTypeRegistry implements SmartInitializingSingleton {
if (this.beanFactory.isAlias(name) || this.beanFactory.containsSingleton(name)) {
return;
}
RootBeanDefinition beanDefinition = getBeanDefinition(name);
if (beanDefinition == null) {
RootBeanDefinition definition = getBeanDefinition(name);
if (definition == null) {
return;
}
RootBeanDefinition previous = this.beanDefinitions.put(name, beanDefinition);
if (previous != null && !beanDefinition.equals(previous)) {
addBeanTypeForNonAliasDefinition(name, beanDefinition);
RootBeanDefinition previous = this.beanDefinitions.put(name, definition);
if (previous != null && !definition.equals(previous)) {
addBeanTypeForNonAliasDefinition(name, definition);
}
}
private RootBeanDefinition getBeanDefinition(String name) {
try {
return (RootBeanDefinition) this.beanFactory.getMergedBeanDefinition(name);
}
catch (BeanDefinitionStoreException ex) {
logIgnoredError("unresolvable metadata in bean definition", name, ex);
return null;
}
}
private void addBeanTypeForNonAliasDefinition(String name,
RootBeanDefinition beanDefinition) {
RootBeanDefinition definition) {
try {
if (!beanDefinition.isAbstract()
&& !requiresEagerInit(beanDefinition.getFactoryBeanName())) {
String factoryName = BeanFactory.FACTORY_BEAN_PREFIX + name;
if (this.beanFactory.isFactoryBean(factoryName)) {
Class<?> factoryBeanGeneric = getFactoryBeanGeneric(this.beanFactory,
beanDefinition);
if (!definition.isAbstract()
&& !requiresEagerInit(definition.getFactoryBeanName())) {
ResolvableType factoryMethodReturnType = getFactoryMethodReturnType(
definition);
String factoryBeanName = BeanFactory.FACTORY_BEAN_PREFIX + name;
if (this.beanFactory.isFactoryBean(factoryBeanName)) {
ResolvableType factoryBeanGeneric = getFactoryBeanGeneric(
this.beanFactory, definition, factoryMethodReturnType);
this.beanTypes.put(name, factoryBeanGeneric);
this.beanTypes.put(factoryName,
this.beanFactory.getType(factoryName));
this.beanTypes.put(factoryBeanName,
getType(factoryBeanName, factoryMethodReturnType));
}
else {
this.beanTypes.put(name, this.beanFactory.getType(name));
this.beanTypes.put(name, getType(name, factoryMethodReturnType));
}
}
this.beanDefinitions.put(name, beanDefinition);
this.beanDefinitions.put(name, definition);
}
catch (CannotLoadBeanClassException ex) {
// Probably contains a placeholder
@ -192,69 +211,24 @@ final class BeanTypeRegistry implements SmartInitializingSingleton {
}
}
private RootBeanDefinition getBeanDefinition(String name) {
try {
return (RootBeanDefinition) this.beanFactory.getMergedBeanDefinition(name);
}
catch (BeanDefinitionStoreException ex) {
logIgnoredError("unresolvable metadata in bean definition", name, ex);
return null;
}
}
private void logIgnoredError(String message, String name, Exception ex) {
if (logger.isDebugEnabled()) {
logger.debug("Ignoring " + message + " '" + name + "'", ex);
}
}
private boolean requiresEagerInit(String factoryBeanName) {
return (factoryBeanName != null && this.beanFactory.isFactoryBean(factoryBeanName)
&& !this.beanFactory.containsSingleton(factoryBeanName));
}
/**
* Attempt to guess the type that a {@link FactoryBean} will return based on the
* generics in its method signature.
* @param beanFactory the source bean factory
* @param definition the bean definition
* @return the generic type of the {@link FactoryBean} or {@code null}
*/
private Class<?> getFactoryBeanGeneric(ConfigurableListableBeanFactory beanFactory,
BeanDefinition definition) {
private ResolvableType getFactoryMethodReturnType(BeanDefinition definition) {
try {
return doGetFactoryBeanGeneric(beanFactory, definition);
}
catch (Exception ex) {
return null;
}
}
private Class<?> doGetFactoryBeanGeneric(ConfigurableListableBeanFactory beanFactory,
BeanDefinition definition)
throws Exception, ClassNotFoundException, LinkageError {
if (StringUtils.hasLength(definition.getFactoryBeanName())
&& StringUtils.hasLength(definition.getFactoryMethodName())) {
return getConfigurationClassFactoryBeanGeneric(beanFactory, definition);
Method method = getFactoryMethod(this.beanFactory, definition);
ResolvableType type = (method != null)
? ResolvableType.forMethodReturnType(method) : null;
return type;
}
if (StringUtils.hasLength(definition.getBeanClassName())) {
return getDirectFactoryBeanGeneric(beanFactory, definition);
}
return null;
}
private Class<?> getConfigurationClassFactoryBeanGeneric(
ConfigurableListableBeanFactory beanFactory, BeanDefinition definition)
throws Exception {
Method method = getFactoryMethod(beanFactory, definition);
Class<?> generic = ResolvableType.forMethodReturnType(method)
.as(FactoryBean.class).resolveGeneric();
if ((generic == null || generic.equals(Object.class))
&& definition.hasAttribute(FACTORY_BEAN_OBJECT_TYPE)) {
generic = getTypeFromAttribute(
definition.getAttribute(FACTORY_BEAN_OBJECT_TYPE));
catch (Exception ex) {
}
return generic;
return null;
}
private Method getFactoryMethod(ConfigurableListableBeanFactory beanFactory,
@ -305,14 +279,48 @@ final class BeanTypeRegistry implements SmartInitializingSingleton {
return Arrays.equals(candidate.getParameterTypes(), current.getParameterTypes());
}
private Class<?> getDirectFactoryBeanGeneric(
private void logIgnoredError(String message, String name, Exception ex) {
if (logger.isDebugEnabled()) {
logger.debug("Ignoring " + message + " '" + name + "'", ex);
}
}
/**
* Attempt to guess the type that a {@link FactoryBean} will return based on the
* generics in its method signature.
* @param beanFactory the source bean factory
* @param definition the bean definition
* @param factoryMethodReturnType the factory method return type
* @return the generic type of the {@link FactoryBean} or {@code null}
*/
private ResolvableType getFactoryBeanGeneric(
ConfigurableListableBeanFactory beanFactory, BeanDefinition definition,
ResolvableType factoryMethodReturnType) {
try {
if (factoryMethodReturnType != null) {
return getFactoryBeanType(definition, factoryMethodReturnType);
}
if (StringUtils.hasLength(definition.getBeanClassName())) {
return getDirectFactoryBeanGeneric(beanFactory, definition);
}
}
catch (Exception ex) {
}
return null;
}
private ResolvableType getDirectFactoryBeanGeneric(
ConfigurableListableBeanFactory beanFactory, BeanDefinition definition)
throws ClassNotFoundException, LinkageError {
Class<?> factoryBeanClass = ClassUtils.forName(definition.getBeanClassName(),
beanFactory.getBeanClassLoader());
Class<?> generic = ResolvableType.forClass(factoryBeanClass).as(FactoryBean.class)
.resolveGeneric();
if ((generic == null || generic.equals(Object.class))
return getFactoryBeanType(definition, ResolvableType.forClass(factoryBeanClass));
}
private ResolvableType getFactoryBeanType(BeanDefinition definition,
ResolvableType type) throws ClassNotFoundException, LinkageError {
ResolvableType generic = type.as(FactoryBean.class).getGeneric();
if ((generic == null || generic.resolve().equals(Object.class))
&& definition.hasAttribute(FACTORY_BEAN_OBJECT_TYPE)) {
generic = getTypeFromAttribute(
definition.getAttribute(FACTORY_BEAN_OBJECT_TYPE));
@ -320,17 +328,26 @@ final class BeanTypeRegistry implements SmartInitializingSingleton {
return generic;
}
private Class<?> getTypeFromAttribute(Object attribute)
private ResolvableType getTypeFromAttribute(Object attribute)
throws ClassNotFoundException, LinkageError {
if (attribute instanceof Class<?>) {
return (Class<?>) attribute;
return ResolvableType.forClass((Class<?>) attribute);
}
if (attribute instanceof String) {
return ClassUtils.forName((String) attribute, null);
return ResolvableType.forClass(ClassUtils.forName((String) attribute, null));
}
return null;
}
private ResolvableType getType(String name, ResolvableType factoryMethodReturnType) {
if (factoryMethodReturnType != null
&& !factoryMethodReturnType.resolve(Object.class).equals(Object.class)) {
return factoryMethodReturnType;
}
Class<?> type = this.beanFactory.getType(name);
return (type != null) ? ResolvableType.forClass(type) : null;
}
/**
* Factory method to get the {@link BeanTypeRegistry} for a given {@link BeanFactory}.
* @param beanFactory the source bean factory
@ -342,14 +359,25 @@ final class BeanTypeRegistry implements SmartInitializingSingleton {
Assert.isTrue(listableBeanFactory.isAllowEagerClassLoading(),
"Bean factory must allow eager class loading");
if (!listableBeanFactory.containsLocalBean(BEAN_NAME)) {
BeanDefinition bd = BeanDefinitionBuilder
BeanDefinition definition = BeanDefinitionBuilder
.genericBeanDefinition(BeanTypeRegistry.class,
() -> new BeanTypeRegistry(
(DefaultListableBeanFactory) beanFactory))
.getBeanDefinition();
listableBeanFactory.registerBeanDefinition(BEAN_NAME, bd);
listableBeanFactory.registerBeanDefinition(BEAN_NAME, definition);
}
return listableBeanFactory.getBean(BEAN_NAME, BeanTypeRegistry.class);
}
/**
* Function used to extract the actual bean type from a source {@link ResolvableType}.
* May be used to support parameterized containers for beans.
*/
@FunctionalInterface
interface TypeExtractor {
Class<?> getBeanType(ResolvableType type);
}
}

@ -97,4 +97,14 @@ public @interface ConditionalOnBean {
*/
SearchStrategy search() default SearchStrategy.ALL;
/**
* Additional classes that may contain the specified bean types within their generic
* parameters. For example, an annotation declaring {@code value=Name.class} and
* {@code parameterizedContainer=NameRegistration.class} would detect both
* {@code Name} and {@code NameRegistration<Name>}.
* @return the container types
* @since 2.1.0
*/
Class<?>[] parameterizedContainer() default {};
}

@ -113,4 +113,14 @@ public @interface ConditionalOnMissingBean {
*/
SearchStrategy search() default SearchStrategy.ALL;
/**
* Additional classes that may contain the specified bean types within their generic
* parameters. For example, an annotation declaring {@code value=Name.class} and
* {@code parameterizedContainer=NameRegistration.class} would detect both
* {@code Name} and {@code NameRegistration<Name>}.
* @return the container types
* @since 2.1.0
*/
Class<?>[] parameterizedContainer() default {};
}

@ -17,6 +17,7 @@
package org.springframework.boot.autoconfigure.condition;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
@ -35,18 +36,22 @@ import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.boot.autoconfigure.AutoConfigurationMetadata;
import org.springframework.boot.autoconfigure.condition.BeanTypeRegistry.TypeExtractor;
import org.springframework.boot.autoconfigure.condition.ConditionMessage.Style;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.context.annotation.ConfigurationCondition;
import org.springframework.core.Ordered;
import org.springframework.core.ResolvableType;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.core.annotation.Order;
import org.springframework.core.type.AnnotatedTypeMetadata;
import org.springframework.core.type.MethodMetadata;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.MultiValueMap;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;
/**
@ -57,6 +62,9 @@ import org.springframework.util.StringUtils;
* @author Jakub Kubrynski
* @author Stephane Nicoll
* @author Andy Wilkinson
* @see ConditionalOnBean
* @see ConditionalOnMissingBean
* @see ConditionalOnSingleCandidate
*/
@Order(Ordered.LOWEST_PRECEDENCE)
class OnBeanCondition extends FilteringSpringBootCondition
@ -68,6 +76,11 @@ class OnBeanCondition extends FilteringSpringBootCondition
*/
public static final String FACTORY_BEAN_OBJECT_TYPE = BeanTypeRegistry.FACTORY_BEAN_OBJECT_TYPE;
@Override
public ConfigurationPhase getConfigurationPhase() {
return ConfigurationPhase.REGISTER_BEAN;
}
@Override
protected final ConditionOutcome[] getOutcomes(String[] autoConfigurationClasses,
AutoConfigurationMetadata autoConfigurationMetadata) {
@ -102,11 +115,6 @@ class OnBeanCondition extends FilteringSpringBootCondition
return null;
}
@Override
public ConfigurationPhase getConfigurationPhase() {
return ConfigurationPhase.REGISTER_BEAN;
}
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context,
AnnotatedTypeMetadata metadata) {
@ -144,7 +152,7 @@ class OnBeanCondition extends FilteringSpringBootCondition
matchMessage = matchMessage
.andCondition(ConditionalOnSingleCandidate.class, spec)
.found("a primary bean from beans")
.items(Style.QUOTE, matchResult.namesOfAllMatches);
.items(Style.QUOTE, matchResult.getNamesOfAllMatches());
}
if (metadata.isAnnotated(ConditionalOnMissingBean.class.getName())) {
BeanSearchSpec spec = new BeanSearchSpec(context, metadata,
@ -162,61 +170,8 @@ class OnBeanCondition extends FilteringSpringBootCondition
return ConditionOutcome.match(matchMessage);
}
private String createOnBeanNoMatchReason(MatchResult matchResult) {
StringBuilder reason = new StringBuilder();
appendMessageForNoMatches(reason, matchResult.unmatchedAnnotations,
"annotated with");
appendMessageForNoMatches(reason, matchResult.unmatchedTypes, "of type");
appendMessageForNoMatches(reason, matchResult.unmatchedNames, "named");
return reason.toString();
}
private void appendMessageForNoMatches(StringBuilder reason,
Collection<String> unmatched, String description) {
if (!unmatched.isEmpty()) {
if (reason.length() > 0) {
reason.append(" and ");
}
reason.append("did not find any beans ");
reason.append(description);
reason.append(" ");
reason.append(StringUtils.collectionToDelimitedString(unmatched, ", "));
}
}
private String createOnMissingBeanNoMatchReason(MatchResult matchResult) {
StringBuilder reason = new StringBuilder();
appendMessageForMatches(reason, matchResult.matchedAnnotations, "annotated with");
appendMessageForMatches(reason, matchResult.matchedTypes, "of type");
if (!matchResult.matchedNames.isEmpty()) {
if (reason.length() > 0) {
reason.append(" and ");
}
reason.append("found beans named ");
reason.append(StringUtils
.collectionToDelimitedString(matchResult.matchedNames, ", "));
}
return reason.toString();
}
private void appendMessageForMatches(StringBuilder reason,
Map<String, Collection<String>> matches, String description) {
if (!matches.isEmpty()) {
matches.forEach((key, value) -> {
if (reason.length() > 0) {
reason.append(" and ");
}
reason.append("found beans ");
reason.append(description);
reason.append(" '");
reason.append(key);
reason.append("' ");
reason.append(StringUtils.collectionToDelimitedString(value, ", "));
});
}
}
private MatchResult getMatchingBeans(ConditionContext context, BeanSearchSpec beans) {
protected final MatchResult getMatchingBeans(ConditionContext context,
BeanSearchSpec beans) {
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
if (beans.getStrategy() == SearchStrategy.ANCESTORS) {
BeanFactory parent = beanFactory.getParentBeanFactory();
@ -226,11 +181,13 @@ class OnBeanCondition extends FilteringSpringBootCondition
}
MatchResult matchResult = new MatchResult();
boolean considerHierarchy = beans.getStrategy() != SearchStrategy.CURRENT;
TypeExtractor typeExtractor = beans.getTypeExtractor(context.getClassLoader());
List<String> beansIgnoredByType = getNamesOfBeansIgnoredByType(
beans.getIgnoredTypes(), beanFactory, context, considerHierarchy);
beans.getIgnoredTypes(), typeExtractor, beanFactory, context,
considerHierarchy);
for (String type : beans.getTypes()) {
Collection<String> typeMatches = getBeanNamesForType(beanFactory, type,
context.getClassLoader(), considerHierarchy);
typeExtractor, context.getClassLoader(), considerHierarchy);
typeMatches.removeAll(beansIgnoredByType);
if (typeMatches.isEmpty()) {
matchResult.recordUnmatchedType(type);
@ -263,12 +220,44 @@ class OnBeanCondition extends FilteringSpringBootCondition
return matchResult;
}
private List<String> getNamesOfBeansIgnoredByType(List<String> ignoredTypes,
ListableBeanFactory beanFactory, ConditionContext context,
private String[] getBeanNamesForAnnotation(
ConfigurableListableBeanFactory beanFactory, String type,
ClassLoader classLoader, boolean considerHierarchy) throws LinkageError {
Set<String> names = new HashSet<>();
try {
@SuppressWarnings("unchecked")
Class<? extends Annotation> annotationType = (Class<? extends Annotation>) ClassUtils
.forName(type, classLoader);
collectBeanNamesForAnnotation(names, beanFactory, annotationType,
considerHierarchy);
}
catch (ClassNotFoundException ex) {
// Continue
}
return StringUtils.toStringArray(names);
}
private void collectBeanNamesForAnnotation(Set<String> names,
ListableBeanFactory beanFactory, Class<? extends Annotation> annotationType,
boolean considerHierarchy) {
BeanTypeRegistry registry = BeanTypeRegistry.get(beanFactory);
names.addAll(registry.getNamesForAnnotation(annotationType));
if (considerHierarchy) {
BeanFactory parent = ((HierarchicalBeanFactory) beanFactory)
.getParentBeanFactory();
if (parent instanceof ListableBeanFactory) {
collectBeanNamesForAnnotation(names, (ListableBeanFactory) parent,
annotationType, considerHierarchy);
}
}
}
private List<String> getNamesOfBeansIgnoredByType(List<String> ignoredTypes,
TypeExtractor typeExtractor, ListableBeanFactory beanFactory,
ConditionContext context, boolean considerHierarchy) {
List<String> beanNames = new ArrayList<>();
for (String ignoredType : ignoredTypes) {
beanNames.addAll(getBeanNamesForType(beanFactory, ignoredType,
beanNames.addAll(getBeanNamesForType(beanFactory, ignoredType, typeExtractor,
context.getClassLoader(), considerHierarchy));
}
return beanNames;
@ -283,61 +272,92 @@ class OnBeanCondition extends FilteringSpringBootCondition
}
private Collection<String> getBeanNamesForType(ListableBeanFactory beanFactory,
String type, ClassLoader classLoader, boolean considerHierarchy)
throws LinkageError {
String type, TypeExtractor typeExtractor, ClassLoader classLoader,
boolean considerHierarchy) throws LinkageError {
try {
Set<String> result = new LinkedHashSet<>();
collectBeanNamesForType(result, beanFactory,
ClassUtils.forName(type, classLoader), considerHierarchy);
return result;
return getBeanNamesForType(beanFactory, considerHierarchy,
ClassUtils.forName(type, classLoader), typeExtractor);
}
catch (ClassNotFoundException | NoClassDefFoundError ex) {
return Collections.emptySet();
}
}
private Collection<String> getBeanNamesForType(ListableBeanFactory beanFactory,
boolean considerHierarchy, Class<?> type, TypeExtractor typeExtractor) {
Set<String> result = new LinkedHashSet<>();
collectBeanNamesForType(result, beanFactory, type, typeExtractor,
considerHierarchy);
return result;
}
private void collectBeanNamesForType(Set<String> result,
ListableBeanFactory beanFactory, Class<?> type, boolean considerHierarchy) {
result.addAll(BeanTypeRegistry.get(beanFactory).getNamesForType(type));
ListableBeanFactory beanFactory, Class<?> type, TypeExtractor typeExtractor,
boolean considerHierarchy) {
BeanTypeRegistry registry = BeanTypeRegistry.get(beanFactory);
result.addAll(registry.getNamesForType(type, typeExtractor));
if (considerHierarchy && beanFactory instanceof HierarchicalBeanFactory) {
BeanFactory parent = ((HierarchicalBeanFactory) beanFactory)
.getParentBeanFactory();
if (parent instanceof ListableBeanFactory) {
collectBeanNamesForType(result, (ListableBeanFactory) parent, type,
considerHierarchy);
typeExtractor, considerHierarchy);
}
}
}
private String[] getBeanNamesForAnnotation(
ConfigurableListableBeanFactory beanFactory, String type,
ClassLoader classLoader, boolean considerHierarchy) throws LinkageError {
Set<String> names = new HashSet<>();
try {
@SuppressWarnings("unchecked")
Class<? extends Annotation> annotationType = (Class<? extends Annotation>) ClassUtils
.forName(type, classLoader);
collectBeanNamesForAnnotation(names, beanFactory, annotationType,
considerHierarchy);
private String createOnBeanNoMatchReason(MatchResult matchResult) {
StringBuilder reason = new StringBuilder();
appendMessageForNoMatches(reason, matchResult.getUnmatchedAnnotations(),
"annotated with");
appendMessageForNoMatches(reason, matchResult.getUnmatchedTypes(), "of type");
appendMessageForNoMatches(reason, matchResult.getUnmatchedNames(), "named");
return reason.toString();
}
catch (ClassNotFoundException ex) {
// Continue
private void appendMessageForNoMatches(StringBuilder reason,
Collection<String> unmatched, String description) {
if (!unmatched.isEmpty()) {
if (reason.length() > 0) {
reason.append(" and ");
}
reason.append("did not find any beans ");
reason.append(description);
reason.append(" ");
reason.append(StringUtils.collectionToDelimitedString(unmatched, ", "));
}
return StringUtils.toStringArray(names);
}
private void collectBeanNamesForAnnotation(Set<String> names,
ListableBeanFactory beanFactory, Class<? extends Annotation> annotationType,
boolean considerHierarchy) {
names.addAll(
BeanTypeRegistry.get(beanFactory).getNamesForAnnotation(annotationType));
if (considerHierarchy) {
BeanFactory parent = ((HierarchicalBeanFactory) beanFactory)
.getParentBeanFactory();
if (parent instanceof ListableBeanFactory) {
collectBeanNamesForAnnotation(names, (ListableBeanFactory) parent,
annotationType, considerHierarchy);
private String createOnMissingBeanNoMatchReason(MatchResult matchResult) {
StringBuilder reason = new StringBuilder();
appendMessageForMatches(reason, matchResult.getMatchedAnnotations(),
"annotated with");
appendMessageForMatches(reason, matchResult.getMatchedTypes(), "of type");
if (!matchResult.getMatchedNames().isEmpty()) {
if (reason.length() > 0) {
reason.append(" and ");
}
reason.append("found beans named ");
reason.append(StringUtils
.collectionToDelimitedString(matchResult.getMatchedNames(), ", "));
}
return reason.toString();
}
private void appendMessageForMatches(StringBuilder reason,
Map<String, Collection<String>> matches, String description) {
if (!matches.isEmpty()) {
matches.forEach((key, value) -> {
if (reason.length() > 0) {
reason.append(" and ");
}
reason.append("found beans ");
reason.append(description);
reason.append(" '");
reason.append(key);
reason.append("' ");
reason.append(StringUtils.collectionToDelimitedString(value, ", "));
});
}
}
@ -375,7 +395,7 @@ class OnBeanCondition extends FilteringSpringBootCondition
return null;
}
private static class BeanSearchSpec {
protected static class BeanSearchSpec {
private final Class<?> annotationType;
@ -387,10 +407,17 @@ class OnBeanCondition extends FilteringSpringBootCondition
private final List<String> ignoredTypes = new ArrayList<>();
private final List<String> parameterizedContainers = new ArrayList<>();
private final SearchStrategy strategy;
BeanSearchSpec(ConditionContext context, AnnotatedTypeMetadata metadata,
public BeanSearchSpec(ConditionContext context, AnnotatedTypeMetadata metadata,
Class<?> annotationType) {
this(context, metadata, annotationType, null);
}
public BeanSearchSpec(ConditionContext context, AnnotatedTypeMetadata metadata,
Class<?> annotationType, Class<?> genericContainer) {
this.annotationType = annotationType;
MultiValueMap<String, Object> attributes = metadata
.getAllAnnotationAttributes(annotationType.getName(), true);
@ -400,6 +427,7 @@ class OnBeanCondition extends FilteringSpringBootCondition
collect(attributes, "annotation", this.annotations);
collect(attributes, "ignored", this.ignoredTypes);
collect(attributes, "ignoredType", this.ignoredTypes);
collect(attributes, "parameterizedContainer", this.parameterizedContainers);
this.strategy = (SearchStrategy) attributes.getFirst("search");
BeanTypeDeductionException deductionException = null;
try {
@ -415,7 +443,7 @@ class OnBeanCondition extends FilteringSpringBootCondition
protected void validate(BeanTypeDeductionException ex) {
if (!hasAtLeastOne(this.types, this.names, this.annotations)) {
String message = annotationName()
String message = getAnnotationName()
+ " did not specify a bean using type, name or annotation";
if (ex == null) {
throw new IllegalStateException(message);
@ -429,7 +457,7 @@ class OnBeanCondition extends FilteringSpringBootCondition
return Arrays.stream(lists).anyMatch((list) -> !list.isEmpty());
}
protected String annotationName() {
protected final String getAnnotationName() {
return "@" + ClassUtils.getShortName(this.annotationType);
}
@ -460,10 +488,7 @@ class OnBeanCondition extends FilteringSpringBootCondition
private void addDeducedBeanTypeForBeanMethod(ConditionContext context,
MethodMetadata metadata, final List<String> beanTypes) {
try {
// We should be safe to load at this point since we are in the
// REGISTER_BEAN phase
Class<?> returnType = ClassUtils.forName(metadata.getReturnTypeName(),
context.getClassLoader());
Class<?> returnType = getReturnType(context, metadata);
beanTypes.add(returnType.getName());
}
catch (Throwable ex) {
@ -472,6 +497,71 @@ class OnBeanCondition extends FilteringSpringBootCondition
}
}
private Class<?> getReturnType(ConditionContext context, MethodMetadata metadata)
throws ClassNotFoundException, LinkageError {
// We should be safe to load at this point since we are in the
// REGISTER_BEAN phase
ClassLoader classLoader = context.getClassLoader();
Class<?> returnType = ClassUtils.forName(metadata.getReturnTypeName(),
classLoader);
if (isParameterizedContainer(returnType, classLoader)) {
returnType = getReturnTypeGeneric(metadata, classLoader);
}
return returnType;
}
private Class<?> getReturnTypeGeneric(MethodMetadata metadata,
ClassLoader classLoader) throws ClassNotFoundException, LinkageError {
Class<?> declaringClass = ClassUtils.forName(metadata.getDeclaringClassName(),
classLoader);
Method beanMethod = findBeanMethod(declaringClass, metadata.getMethodName());
return ResolvableType.forMethodReturnType(beanMethod).resolveGeneric();
}
private Method findBeanMethod(Class<?> declaringClass, String methodName) {
Method method = ReflectionUtils.findMethod(declaringClass, methodName);
if (isBeanMethod(method)) {
return method;
}
return Arrays.stream(ReflectionUtils.getAllDeclaredMethods(declaringClass))
.filter((candidate) -> candidate.getName().equals(methodName))
.filter(this::isBeanMethod).findFirst()
.orElseThrow(() -> new IllegalStateException(
"Unable to find bean method " + methodName));
}
private boolean isBeanMethod(Method method) {
return method != null
&& AnnotatedElementUtils.hasAnnotation(method, Bean.class);
}
public TypeExtractor getTypeExtractor(ClassLoader classLoader) {
if (this.parameterizedContainers.isEmpty()) {
return ResolvableType::resolve;
}
return (type) -> {
Class<?> resolved = type.resolve();
if (isParameterizedContainer(resolved, classLoader)) {
return type.getGeneric().resolve();
}
return resolved;
};
}
private boolean isParameterizedContainer(Class<?> type, ClassLoader classLoader) {
for (String candidate : this.parameterizedContainers) {
try {
if (ClassUtils.forName(candidate, classLoader)
.isAssignableFrom(type)) {
return true;
}
}
catch (Exception ex) {
}
}
return false;
}
public SearchStrategy getStrategy() {
return (this.strategy != null) ? this.strategy : SearchStrategy.ALL;
}
@ -531,23 +621,13 @@ class OnBeanCondition extends FilteringSpringBootCondition
@Override
protected void validate(BeanTypeDeductionException ex) {
Assert.isTrue(getTypes().size() == 1, () -> annotationName()
Assert.isTrue(getTypes().size() == 1, () -> getAnnotationName()
+ " annotations must specify only one type (got " + getTypes() + ")");
}
}
static final class BeanTypeDeductionException extends RuntimeException {
private BeanTypeDeductionException(String className, String beanMethodName,
Throwable cause) {
super("Failed to deduce bean type for " + className + "." + beanMethodName,
cause);
}
}
static final class MatchResult {
protected static final class MatchResult {
private final Map<String, Collection<String>> matchedAnnotations = new HashMap<>();
@ -591,20 +671,54 @@ class OnBeanCondition extends FilteringSpringBootCondition
this.unmatchedTypes.add(type);
}
private boolean isAllMatched() {
public boolean isAllMatched() {
return this.unmatchedAnnotations.isEmpty() && this.unmatchedNames.isEmpty()
&& this.unmatchedTypes.isEmpty();
}
private boolean isAnyMatched() {
public boolean isAnyMatched() {
return (!this.matchedAnnotations.isEmpty()) || (!this.matchedNames.isEmpty())
|| (!this.matchedTypes.isEmpty());
}
private Set<String> getNamesOfAllMatches() {
public Map<String, Collection<String>> getMatchedAnnotations() {
return this.matchedAnnotations;
}
public List<String> getMatchedNames() {
return this.matchedNames;
}
public Map<String, Collection<String>> getMatchedTypes() {
return this.matchedTypes;
}
public List<String> getUnmatchedAnnotations() {
return this.unmatchedAnnotations;
}
public List<String> getUnmatchedNames() {
return this.unmatchedNames;
}
public List<String> getUnmatchedTypes() {
return this.unmatchedTypes;
}
public Set<String> getNamesOfAllMatches() {
return this.namesOfAllMatches;
}
}
static final class BeanTypeDeductionException extends RuntimeException {
private BeanTypeDeductionException(String className, String beanMethodName,
Throwable cause) {
super("Failed to deduce bean type for " + className + "." + beanMethodName,
cause);
}
}
}

@ -22,6 +22,7 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.Date;
import java.util.function.Consumer;
import org.junit.Test;
@ -30,6 +31,7 @@ import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.boot.test.context.assertj.AssertableApplicationContext;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
@ -38,6 +40,7 @@ import org.springframework.context.annotation.ImportResource;
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.util.StringUtils;
import static org.assertj.core.api.Assertions.assertThat;
@ -149,6 +152,89 @@ public class ConditionalOnBeanTests {
});
}
@Test
public void parameterizedContainerWhenValueIsOfMissingBeanDoesNotMatch() {
this.contextRunner
.withUserConfiguration(ParmeterizedWithoutCustomConfig.class,
ParmeterizedConditionWithValueConfig.class)
.run((context) -> assertThat(context)
.satisfies(exampleBeanRequirement("otherExampleBean")));
}
@Test
public void parameterizedContainerWhenValueIsOfExistingBeanMatches() {
this.contextRunner
.withUserConfiguration(ParmeterizedWithCustomConfig.class,
ParmeterizedConditionWithValueConfig.class)
.run((context) -> assertThat(context).satisfies(exampleBeanRequirement(
"customExampleBean", "conditionalCustomExampleBean")));
}
@Test
public void parameterizedContainerWhenValueIsOfMissingBeanRegistrationDoesNotMatch() {
this.contextRunner
.withUserConfiguration(ParmeterizedWithoutCustomContainerConfig.class,
ParmeterizedConditionWithValueConfig.class)
.run((context) -> assertThat(context)
.satisfies(exampleBeanRequirement("otherExampleBean")));
}
@Test
public void parameterizedContainerWhenValueIsOfExistingBeanRegistrationMatches() {
this.contextRunner
.withUserConfiguration(ParmeterizedWithCustomContainerConfig.class,
ParmeterizedConditionWithValueConfig.class)
.run((context) -> assertThat(context).satisfies(exampleBeanRequirement(
"customExampleBean", "conditionalCustomExampleBean")));
}
@Test
public void parameterizedContainerWhenReturnTypeIsOfExistingBeanMatches() {
this.contextRunner
.withUserConfiguration(ParmeterizedWithCustomConfig.class,
ParmeterizedConditionWithReturnTypeConfig.class)
.run((context) -> assertThat(context).satisfies(exampleBeanRequirement(
"customExampleBean", "conditionalCustomExampleBean")));
}
@Test
public void parameterizedContainerWhenReturnTypeIsOfExistingBeanRegistrationMatches() {
this.contextRunner
.withUserConfiguration(ParmeterizedWithCustomContainerConfig.class,
ParmeterizedConditionWithReturnTypeConfig.class)
.run((context) -> assertThat(context).satisfies(exampleBeanRequirement(
"customExampleBean", "conditionalCustomExampleBean")));
}
@Test
public void parameterizedContainerWhenReturnRegistrationTypeIsOfExistingBeanMatches() {
this.contextRunner
.withUserConfiguration(ParmeterizedWithCustomConfig.class,
ParmeterizedConditionWithReturnRegistrationTypeConfig.class)
.run((context) -> assertThat(context).satisfies(exampleBeanRequirement(
"customExampleBean", "conditionalCustomExampleBean")));
}
@Test
public void parameterizedContainerWhenReturnRegistrationTypeIsOfExistingBeanRegistrationMatches() {
this.contextRunner
.withUserConfiguration(ParmeterizedWithCustomContainerConfig.class,
ParmeterizedConditionWithReturnRegistrationTypeConfig.class)
.run((context) -> assertThat(context).satisfies(exampleBeanRequirement(
"customExampleBean", "conditionalCustomExampleBean")));
}
private Consumer<ConfigurableApplicationContext> exampleBeanRequirement(
String... names) {
return (context) -> {
String[] beans = context.getBeanNamesForType(ExampleBean.class);
String[] containers = context
.getBeanNamesForType(TestParameterizedContainer.class);
assertThat(StringUtils.concatenateStringArrays(beans, containers))
.containsOnly(names);
};
}
@Configuration
@ConditionalOnBean(name = "foo")
protected static class OnBeanNameConfiguration {
@ -298,57 +384,146 @@ public class ConditionalOnBeanTests {
}
@TestAnnotation
public static class ExampleBean {
@Configuration
public static class OriginalDefinition {
private String value;
@Bean
public String testBean() {
return "test";
}
public ExampleBean(String value) {
this.value = value;
}
@Override
public String toString() {
return this.value;
@Configuration
@ConditionalOnBean(String.class)
public static class OverridingDefinition {
@Bean
public Integer testBean() {
return 1;
}
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface TestAnnotation {
@Configuration
@ConditionalOnBean(String.class)
public static class ConsumingConfiguration {
ConsumingConfiguration(String testBean) {
}
}
@Configuration
public static class OriginalDefinition {
static class ParmeterizedWithCustomConfig {
@Bean
public String testBean() {
return "test";
public CustomExampleBean customExampleBean() {
return new CustomExampleBean();
}
}
@Configuration
@ConditionalOnBean(String.class)
public static class OverridingDefinition {
static class ParmeterizedWithoutCustomConfig {
@Bean
public Integer testBean() {
return 1;
public OtherExampleBean otherExampleBean() {
return new OtherExampleBean();
}
}
@Configuration
@ConditionalOnBean(String.class)
public static class ConsumingConfiguration {
static class ParmeterizedWithoutCustomContainerConfig {
ConsumingConfiguration(String testBean) {
@Bean
public TestParameterizedContainer<OtherExampleBean> otherExampleBean() {
return new TestParameterizedContainer<OtherExampleBean>();
}
}
@Configuration
static class ParmeterizedWithCustomContainerConfig {
@Bean
public TestParameterizedContainer<CustomExampleBean> customExampleBean() {
return new TestParameterizedContainer<CustomExampleBean>();
}
}
@Configuration
static class ParmeterizedConditionWithValueConfig {
@Bean
@ConditionalOnBean(value = CustomExampleBean.class, parameterizedContainer = TestParameterizedContainer.class)
public CustomExampleBean conditionalCustomExampleBean() {
return new CustomExampleBean();
}
}
@Configuration
static class ParmeterizedConditionWithReturnTypeConfig {
@Bean
@ConditionalOnBean(parameterizedContainer = TestParameterizedContainer.class)
public CustomExampleBean conditionalCustomExampleBean() {
return new CustomExampleBean();
}
}
@Configuration
static class ParmeterizedConditionWithReturnRegistrationTypeConfig {
@Bean
@ConditionalOnBean(parameterizedContainer = TestParameterizedContainer.class)
public TestParameterizedContainer<CustomExampleBean> conditionalCustomExampleBean() {
return new TestParameterizedContainer<CustomExampleBean>();
}
}
@TestAnnotation
public static class ExampleBean {
private String value;
public ExampleBean(String value) {
this.value = value;
}
@Override
public String toString() {
return this.value;
}
}
public static class CustomExampleBean extends ExampleBean {
public CustomExampleBean() {
super("custom subclass");
}
}
public static class OtherExampleBean extends ExampleBean {
public OtherExampleBean() {
super("other subclass");
}
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface TestAnnotation {
}
}

@ -22,6 +22,7 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.Date;
import java.util.function.Consumer;
import org.junit.Test;
@ -33,6 +34,7 @@ import org.springframework.boot.autoconfigure.condition.scan.ScannedFactoryBeanC
import org.springframework.boot.autoconfigure.condition.scan.ScannedFactoryBeanWithBeanMethodArgumentsConfiguration;
import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.ComponentScan.Filter;
@ -44,6 +46,7 @@ import org.springframework.context.annotation.ImportResource;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import static org.assertj.core.api.Assertions.assertThat;
@ -304,6 +307,89 @@ public class ConditionalOnMissingBeanTests {
});
}
@Test
public void parameterizedContainerWhenValueIsOfMissingBeanMatches() {
this.contextRunner
.withUserConfiguration(ParmeterizedWithoutCustomConfig.class,
ParmeterizedConditionWithValueConfig.class)
.run((context) -> assertThat(context).satisfies(exampleBeanRequirement(
"otherExampleBean", "conditionalCustomExampleBean")));
}
@Test
public void parameterizedContainerWhenValueIsOfExistingBeanDoesNotMatch() {
this.contextRunner
.withUserConfiguration(ParmeterizedWithCustomConfig.class,
ParmeterizedConditionWithValueConfig.class)
.run((context) -> assertThat(context)
.satisfies(exampleBeanRequirement("customExampleBean")));
}
@Test
public void parameterizedContainerWhenValueIsOfMissingBeanRegistrationMatches() {
this.contextRunner
.withUserConfiguration(ParmeterizedWithoutCustomContainerConfig.class,
ParmeterizedConditionWithValueConfig.class)
.run((context) -> assertThat(context).satisfies(exampleBeanRequirement(
"otherExampleBean", "conditionalCustomExampleBean")));
}
@Test
public void parameterizedContainerWhenValueIsOfExistingBeanRegistrationDoesNotMatch() {
this.contextRunner
.withUserConfiguration(ParmeterizedWithCustomContainerConfig.class,
ParmeterizedConditionWithValueConfig.class)
.run((context) -> assertThat(context)
.satisfies(exampleBeanRequirement("customExampleBean")));
}
@Test
public void parameterizedContainerWhenReturnTypeIsOfExistingBeanDoesNotMatch() {
this.contextRunner
.withUserConfiguration(ParmeterizedWithCustomConfig.class,
ParmeterizedConditionWithReturnTypeConfig.class)
.run((context) -> assertThat(context)
.satisfies(exampleBeanRequirement("customExampleBean")));
}
@Test
public void parameterizedContainerWhenReturnTypeIsOfExistingBeanRegistrationDoesNotMatch() {
this.contextRunner
.withUserConfiguration(ParmeterizedWithCustomContainerConfig.class,
ParmeterizedConditionWithReturnTypeConfig.class)
.run((context) -> assertThat(context)
.satisfies(exampleBeanRequirement("customExampleBean")));
}
@Test
public void parameterizedContainerWhenReturnRegistrationTypeIsOfExistingBeanDoesNotMatch() {
this.contextRunner
.withUserConfiguration(ParmeterizedWithCustomConfig.class,
ParmeterizedConditionWithReturnRegistrationTypeConfig.class)
.run((context) -> assertThat(context)
.satisfies(exampleBeanRequirement("customExampleBean")));
}
@Test
public void parameterizedContainerWhenReturnRegistrationTypeIsOfExistingBeanRegistrationDoesNotMatch() {
this.contextRunner
.withUserConfiguration(ParmeterizedWithCustomContainerConfig.class,
ParmeterizedConditionWithReturnRegistrationTypeConfig.class)
.run((context) -> assertThat(context)
.satisfies(exampleBeanRequirement("customExampleBean")));
}
private Consumer<ConfigurableApplicationContext> exampleBeanRequirement(
String... names) {
return (context) -> {
String[] beans = context.getBeanNamesForType(ExampleBean.class);
String[] containers = context
.getBeanNamesForType(TestParameterizedContainer.class);
assertThat(StringUtils.concatenateStringArrays(beans, containers))
.containsOnly(names);
};
}
@Configuration
protected static class OnBeanInAncestorsConfiguration {
@ -584,30 +670,6 @@ public class ConditionalOnMissingBeanTests {
}
@TestAnnotation
public static class ExampleBean {
private String value;
public ExampleBean(String value) {
this.value = value;
}
@Override
public String toString() {
return this.value;
}
}
public static class CustomExampleBean extends ExampleBean {
public CustomExampleBean() {
super("custom subclass");
}
}
public static class ExampleFactoryBean implements FactoryBean<ExampleBean> {
public ExampleFactoryBean(String value) {
@ -654,6 +716,111 @@ public class ConditionalOnMissingBeanTests {
}
@Configuration
static class ParmeterizedWithCustomConfig {
@Bean
public CustomExampleBean customExampleBean() {
return new CustomExampleBean();
}
}
@Configuration
static class ParmeterizedWithoutCustomConfig {
@Bean
public OtherExampleBean otherExampleBean() {
return new OtherExampleBean();
}
}
@Configuration
static class ParmeterizedWithoutCustomContainerConfig {
@Bean
public TestParameterizedContainer<OtherExampleBean> otherExampleBean() {
return new TestParameterizedContainer<OtherExampleBean>();
}
}
@Configuration
static class ParmeterizedWithCustomContainerConfig {
@Bean
public TestParameterizedContainer<CustomExampleBean> customExampleBean() {
return new TestParameterizedContainer<CustomExampleBean>();
}
}
@Configuration
static class ParmeterizedConditionWithValueConfig {
@Bean
@ConditionalOnMissingBean(value = CustomExampleBean.class, parameterizedContainer = TestParameterizedContainer.class)
public CustomExampleBean conditionalCustomExampleBean() {
return new CustomExampleBean();
}
}
@Configuration
static class ParmeterizedConditionWithReturnTypeConfig {
@Bean
@ConditionalOnMissingBean(parameterizedContainer = TestParameterizedContainer.class)
public CustomExampleBean conditionalCustomExampleBean() {
return new CustomExampleBean();
}
}
@Configuration
static class ParmeterizedConditionWithReturnRegistrationTypeConfig {
@Bean
@ConditionalOnMissingBean(parameterizedContainer = TestParameterizedContainer.class)
public TestParameterizedContainer<CustomExampleBean> conditionalCustomExampleBean() {
return new TestParameterizedContainer<CustomExampleBean>();
}
}
@TestAnnotation
public static class ExampleBean {
private String value;
public ExampleBean(String value) {
this.value = value;
}
@Override
public String toString() {
return this.value;
}
}
public static class CustomExampleBean extends ExampleBean {
public CustomExampleBean() {
super("custom subclass");
}
}
public static class OtherExampleBean extends ExampleBean {
public OtherExampleBean() {
super("other subclass");
}
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented

@ -0,0 +1,28 @@
/*
* Copyright 2012-2018 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
*
* 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 org.springframework.boot.autoconfigure.condition;
/**
* Simple parameterized container for testing {@link ConditionalOnBean} and
* {@link ConditionalOnMissingBean}.
*
* @param <T> The bean type
* @author Phillip Webb
*/
public class TestParameterizedContainer<T> {
}
Loading…
Cancel
Save