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
This commit is contained in:
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) -> {
|
||||
if (!this.beanTypes.containsKey(name)) {
|
||||
addBeanType(name);
|
||||
}
|
||||
else {
|
||||
updateBeanType(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,38 +164,13 @@ 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);
|
||||
}
|
||||
}
|
||||
|
||||
private void addBeanTypeForNonAliasDefinition(String name,
|
||||
RootBeanDefinition beanDefinition) {
|
||||
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);
|
||||
this.beanTypes.put(name, factoryBeanGeneric);
|
||||
this.beanTypes.put(factoryName,
|
||||
this.beanFactory.getType(factoryName));
|
||||
}
|
||||
else {
|
||||
this.beanTypes.put(name, this.beanFactory.getType(name));
|
||||
}
|
||||
}
|
||||
this.beanDefinitions.put(name, beanDefinition);
|
||||
}
|
||||
catch (CannotLoadBeanClassException ex) {
|
||||
// Probably contains a placeholder
|
||||
logIgnoredError("bean class loading failure for bean", name, ex);
|
||||
RootBeanDefinition previous = this.beanDefinitions.put(name, definition);
|
||||
if (previous != null && !definition.equals(previous)) {
|
||||
addBeanTypeForNonAliasDefinition(name, definition);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -202,9 +184,30 @@ final class BeanTypeRegistry implements SmartInitializingSingleton {
|
|||
}
|
||||
}
|
||||
|
||||
private void logIgnoredError(String message, String name, Exception ex) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Ignoring " + message + " '" + name + "'", ex);
|
||||
private void addBeanTypeForNonAliasDefinition(String name,
|
||||
RootBeanDefinition definition) {
|
||||
try {
|
||||
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(factoryBeanName,
|
||||
getType(factoryBeanName, factoryMethodReturnType));
|
||||
}
|
||||
else {
|
||||
this.beanTypes.put(name, getType(name, factoryMethodReturnType));
|
||||
}
|
||||
}
|
||||
this.beanDefinitions.put(name, definition);
|
||||
}
|
||||
catch (CannotLoadBeanClassException ex) {
|
||||
// Probably contains a placeholder
|
||||
logIgnoredError("bean class loading failure for bean", name, ex);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -213,50 +216,21 @@ final class BeanTypeRegistry implements SmartInitializingSingleton {
|
|||
&& !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);
|
||||
if (StringUtils.hasLength(definition.getFactoryBeanName())
|
||||
&& StringUtils.hasLength(definition.getFactoryMethodName())) {
|
||||
Method method = getFactoryMethod(this.beanFactory, definition);
|
||||
ResolvableType type = (method != null)
|
||||
? ResolvableType.forMethodReturnType(method) : null;
|
||||
return type;
|
||||
}
|
||||
}
|
||||
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);
|
||||
}
|
||||
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));
|
||||
}
|
||||
return generic;
|
||||
}
|
||||
|
||||
private Method getFactoryMethod(ConfigurableListableBeanFactory beanFactory,
|
||||
BeanDefinition definition) throws Exception {
|
||||
if (definition instanceof AnnotatedBeanDefinition) {
|
||||
|
@ -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,52 +220,6 @@ class OnBeanCondition extends FilteringSpringBootCondition
|
|||
return matchResult;
|
||||
}
|
||||
|
||||
private List<String> getNamesOfBeansIgnoredByType(List<String> ignoredTypes,
|
||||
ListableBeanFactory beanFactory, ConditionContext context,
|
||||
boolean considerHierarchy) {
|
||||
List<String> beanNames = new ArrayList<>();
|
||||
for (String ignoredType : ignoredTypes) {
|
||||
beanNames.addAll(getBeanNamesForType(beanFactory, ignoredType,
|
||||
context.getClassLoader(), considerHierarchy));
|
||||
}
|
||||
return beanNames;
|
||||
}
|
||||
|
||||
private boolean containsBean(ConfigurableListableBeanFactory beanFactory,
|
||||
String beanName, boolean considerHierarchy) {
|
||||
if (considerHierarchy) {
|
||||
return beanFactory.containsBean(beanName);
|
||||
}
|
||||
return beanFactory.containsLocalBean(beanName);
|
||||
}
|
||||
|
||||
private Collection<String> getBeanNamesForType(ListableBeanFactory beanFactory,
|
||||
String type, ClassLoader classLoader, boolean considerHierarchy)
|
||||
throws LinkageError {
|
||||
try {
|
||||
Set<String> result = new LinkedHashSet<>();
|
||||
collectBeanNamesForType(result, beanFactory,
|
||||
ClassUtils.forName(type, classLoader), considerHierarchy);
|
||||
return result;
|
||||
}
|
||||
catch (ClassNotFoundException | NoClassDefFoundError ex) {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
}
|
||||
|
||||
private void collectBeanNamesForType(Set<String> result,
|
||||
ListableBeanFactory beanFactory, Class<?> type, boolean considerHierarchy) {
|
||||
result.addAll(BeanTypeRegistry.get(beanFactory).getNamesForType(type));
|
||||
if (considerHierarchy && beanFactory instanceof HierarchicalBeanFactory) {
|
||||
BeanFactory parent = ((HierarchicalBeanFactory) beanFactory)
|
||||
.getParentBeanFactory();
|
||||
if (parent instanceof ListableBeanFactory) {
|
||||
collectBeanNamesForType(result, (ListableBeanFactory) parent, type,
|
||||
considerHierarchy);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private String[] getBeanNamesForAnnotation(
|
||||
ConfigurableListableBeanFactory beanFactory, String type,
|
||||
ClassLoader classLoader, boolean considerHierarchy) throws LinkageError {
|
||||
|
@ -329,8 +240,8 @@ class OnBeanCondition extends FilteringSpringBootCondition
|
|||
private void collectBeanNamesForAnnotation(Set<String> names,
|
||||
ListableBeanFactory beanFactory, Class<? extends Annotation> annotationType,
|
||||
boolean considerHierarchy) {
|
||||
names.addAll(
|
||||
BeanTypeRegistry.get(beanFactory).getNamesForAnnotation(annotationType));
|
||||
BeanTypeRegistry registry = BeanTypeRegistry.get(beanFactory);
|
||||
names.addAll(registry.getNamesForAnnotation(annotationType));
|
||||
if (considerHierarchy) {
|
||||
BeanFactory parent = ((HierarchicalBeanFactory) beanFactory)
|
||||
.getParentBeanFactory();
|
||||
|
@ -341,6 +252,115 @@ class OnBeanCondition extends FilteringSpringBootCondition
|
|||
}
|
||||
}
|
||||
|
||||
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, typeExtractor,
|
||||
context.getClassLoader(), considerHierarchy));
|
||||
}
|
||||
return beanNames;
|
||||
}
|
||||
|
||||
private boolean containsBean(ConfigurableListableBeanFactory beanFactory,
|
||||
String beanName, boolean considerHierarchy) {
|
||||
if (considerHierarchy) {
|
||||
return beanFactory.containsBean(beanName);
|
||||
}
|
||||
return beanFactory.containsLocalBean(beanName);
|
||||
}
|
||||
|
||||
private Collection<String> getBeanNamesForType(ListableBeanFactory beanFactory,
|
||||
String type, TypeExtractor typeExtractor, ClassLoader classLoader,
|
||||
boolean considerHierarchy) throws LinkageError {
|
||||
try {
|
||||
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, 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,
|
||||
typeExtractor, 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();
|
||||
}
|
||||
|
||||
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.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, ", "));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private boolean hasSingleAutowireCandidate(
|
||||
ConfigurableListableBeanFactory beanFactory, Set<String> beanNames,
|
||||
boolean considerHierarchy) {
|
||||
|
@ -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,29 +384,6 @@ public class ConditionalOnBeanTests {
|
|||
|
||||
}
|
||||
|
||||
@TestAnnotation
|
||||
public static class ExampleBean {
|
||||
|
||||
private String value;
|
||||
|
||||
public ExampleBean(String value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.value;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Target(ElementType.TYPE)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
public @interface TestAnnotation {
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
public static class OriginalDefinition {
|
||||
|
||||
|
@ -351,4 +414,116 @@ public class ConditionalOnBeanTests {
|
|||
|
||||
}
|
||||
|
||||
@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
|
||||
@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…
Reference in New Issue