Skip to content

Commit

Permalink
Merge pull request #29500 from VladislavSevruk
Browse files Browse the repository at this point in the history
* pr/29500:
  Polish @ConditionalOnBean and @ConditionalOnMissingBean tests
  Add ConditionalOnBean support for generic @bean return types

Closes gh-29500
  • Loading branch information
philwebb committed Jan 29, 2025
2 parents fcad1fa + 3911ca7 commit b722430
Show file tree
Hide file tree
Showing 3 changed files with 555 additions and 242 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@
* @author Jakub Kubrynski
* @author Stephane Nicoll
* @author Andy Wilkinson
* @author Uladzislau Seuruk
* @see ConditionalOnBean
* @see ConditionalOnMissingBean
* @see ConditionalOnSingleCandidate
Expand Down Expand Up @@ -205,13 +206,13 @@ protected final MatchResult getMatchingBeans(Spec<?> spec) {
ConfigurableListableBeanFactory beanFactory = getSearchBeanFactory(spec);
ClassLoader classLoader = spec.getContext().getClassLoader();
boolean considerHierarchy = spec.getStrategy() != SearchStrategy.CURRENT;
Set<Class<?>> parameterizedContainers = spec.getParameterizedContainers();
Set<ResolvableType> parameterizedContainers = spec.getParameterizedContainers();
MatchResult result = new MatchResult();
Set<String> beansIgnoredByType = getNamesOfBeansIgnoredByType(classLoader, beanFactory, considerHierarchy,
Set<String> beansIgnoredByType = getNamesOfBeansIgnoredByType(beanFactory, considerHierarchy,
spec.getIgnoredTypes(), parameterizedContainers);
for (String type : spec.getTypes()) {
Map<String, BeanDefinition> typeMatchedDefinitions = getBeanDefinitionsForType(classLoader,
considerHierarchy, beanFactory, type, parameterizedContainers);
for (ResolvableType type : spec.getTypes()) {
Map<String, BeanDefinition> typeMatchedDefinitions = getBeanDefinitionsForType(beanFactory,
considerHierarchy, type, parameterizedContainers);
Set<String> typeMatchedNames = matchedNamesFrom(typeMatchedDefinitions,
(name, definition) -> !ScopedProxyUtils.isScopedTarget(name)
&& isCandidate(beanFactory, name, definition, beansIgnoredByType));
Expand Down Expand Up @@ -296,42 +297,31 @@ private boolean isDefaultCandidate(BeanDefinition definition) {
return true;
}

private Set<String> getNamesOfBeansIgnoredByType(ClassLoader classLoader, ListableBeanFactory beanFactory,
boolean considerHierarchy, Set<String> ignoredTypes, Set<Class<?>> parameterizedContainers) {
private Set<String> getNamesOfBeansIgnoredByType(ListableBeanFactory beanFactory, boolean considerHierarchy,
Set<ResolvableType> ignoredTypes, Set<ResolvableType> parameterizedContainers) {
Set<String> result = null;
for (String ignoredType : ignoredTypes) {
Collection<String> ignoredNames = getBeanDefinitionsForType(classLoader, considerHierarchy, beanFactory,
ignoredType, parameterizedContainers)
for (ResolvableType ignoredType : ignoredTypes) {
Collection<String> ignoredNames = getBeanDefinitionsForType(beanFactory, considerHierarchy, ignoredType,
parameterizedContainers)
.keySet();
result = addAll(result, ignoredNames);
}
return (result != null) ? result : Collections.emptySet();
}

private Map<String, BeanDefinition> getBeanDefinitionsForType(ClassLoader classLoader, boolean considerHierarchy,
ListableBeanFactory beanFactory, String type, Set<Class<?>> parameterizedContainers) throws LinkageError {
try {
return getBeanDefinitionsForType(beanFactory, considerHierarchy, resolve(type, classLoader),
parameterizedContainers);
}
catch (ClassNotFoundException | NoClassDefFoundError ex) {
return Collections.emptyMap();
}
}

private Map<String, BeanDefinition> getBeanDefinitionsForType(ListableBeanFactory beanFactory,
boolean considerHierarchy, Class<?> type, Set<Class<?>> parameterizedContainers) {
boolean considerHierarchy, ResolvableType type, Set<ResolvableType> parameterizedContainers) {
Map<String, BeanDefinition> result = collectBeanDefinitionsForType(beanFactory, considerHierarchy, type,
parameterizedContainers, null);
return (result != null) ? result : Collections.emptyMap();
}

private Map<String, BeanDefinition> collectBeanDefinitionsForType(ListableBeanFactory beanFactory,
boolean considerHierarchy, Class<?> type, Set<Class<?>> parameterizedContainers,
boolean considerHierarchy, ResolvableType type, Set<ResolvableType> parameterizedContainers,
Map<String, BeanDefinition> result) {
result = putAll(result, beanFactory.getBeanNamesForType(type, true, false), beanFactory);
for (Class<?> container : parameterizedContainers) {
ResolvableType generic = ResolvableType.forClassWithGenerics(container, type);
for (ResolvableType parameterizedContainer : parameterizedContainers) {
ResolvableType generic = ResolvableType.forClassWithGenerics(parameterizedContainer.resolve(), type);
result = putAll(result, beanFactory.getBeanNamesForType(generic, true, false), beanFactory);
}
if (considerHierarchy && beanFactory instanceof HierarchicalBeanFactory hierarchicalBeanFactory) {
Expand Down Expand Up @@ -550,13 +540,13 @@ private static class Spec<A extends Annotation> {

private final Set<String> names;

private final Set<String> types;
private final Set<ResolvableType> types;

private final Set<String> annotations;

private final Set<String> ignoredTypes;
private final Set<ResolvableType> ignoredTypes;

private final Set<Class<?>> parameterizedContainers;
private final Set<ResolvableType> parameterizedContainers;

private final SearchStrategy strategy;

Expand All @@ -570,10 +560,10 @@ private static class Spec<A extends Annotation> {
this.annotationType = annotationType;
this.names = extract(attributes, "name");
this.annotations = extract(attributes, "annotation");
this.ignoredTypes = extract(attributes, "ignored", "ignoredType");
this.ignoredTypes = resolveWhenPossible(extract(attributes, "ignored", "ignoredType"));
this.parameterizedContainers = resolveWhenPossible(extract(attributes, "parameterizedContainer"));
this.strategy = annotation.getValue("search", SearchStrategy.class).orElse(null);
Set<String> types = extractTypes(attributes);
Set<ResolvableType> types = resolveWhenPossible(extractTypes(attributes));
BeanTypeDeductionException deductionException = null;
if (types.isEmpty() && this.names.isEmpty() && this.annotations.isEmpty()) {
try {
Expand Down Expand Up @@ -614,17 +604,18 @@ private void merge(Set<String> result, String... additional) {
Collections.addAll(result, additional);
}

private Set<Class<?>> resolveWhenPossible(Set<String> classNames) {
private Set<ResolvableType> resolveWhenPossible(Set<String> classNames) {
if (classNames.isEmpty()) {
return Collections.emptySet();
}
Set<Class<?>> resolved = new LinkedHashSet<>(classNames.size());
Set<ResolvableType> resolved = new LinkedHashSet<>(classNames.size());
for (String className : classNames) {
try {
resolved.add(resolve(className, this.context.getClassLoader()));
Class<?> type = resolve(className, this.context.getClassLoader());
resolved.add(ResolvableType.forRawClass(type));
}
catch (ClassNotFoundException | NoClassDefFoundError ex) {
// Ignore
resolved.add(ResolvableType.NONE);
}
}
return resolved;
Expand Down Expand Up @@ -653,48 +644,44 @@ protected final String getAnnotationName() {
return "@" + ClassUtils.getShortName(this.annotationType);
}

private Set<String> deducedBeanType(ConditionContext context, AnnotatedTypeMetadata metadata) {
private Set<ResolvableType> deducedBeanType(ConditionContext context, AnnotatedTypeMetadata metadata) {
if (metadata instanceof MethodMetadata && metadata.isAnnotated(Bean.class.getName())) {
return deducedBeanTypeForBeanMethod(context, (MethodMetadata) metadata);
}
return Collections.emptySet();
}

private Set<String> deducedBeanTypeForBeanMethod(ConditionContext context, MethodMetadata metadata) {
private Set<ResolvableType> deducedBeanTypeForBeanMethod(ConditionContext context, MethodMetadata metadata) {
try {
Class<?> returnType = getReturnType(context, metadata);
return Collections.singleton(returnType.getName());
return Set.of(getReturnType(context, metadata));
}
catch (Throwable ex) {
throw new BeanTypeDeductionException(metadata.getDeclaringClassName(), metadata.getMethodName(), ex);
}
}

private Class<?> getReturnType(ConditionContext context, MethodMetadata metadata)
private ResolvableType getReturnType(ConditionContext context, MethodMetadata metadata)
throws ClassNotFoundException, LinkageError {
// Safe to load at this point since we are in the REGISTER_BEAN phase
ClassLoader classLoader = context.getClassLoader();
Class<?> returnType = resolve(metadata.getReturnTypeName(), classLoader);
if (isParameterizedContainer(returnType)) {
returnType = getReturnTypeGeneric(metadata, classLoader);
ResolvableType returnType = getMethodReturnType(metadata, classLoader);
if (isParameterizedContainer(returnType.resolve())) {
returnType = returnType.getGeneric();
}
return returnType;
}

private boolean isParameterizedContainer(Class<?> type) {
for (Class<?> parameterizedContainer : this.parameterizedContainers) {
if (parameterizedContainer.isAssignableFrom(type)) {
return true;
}
}
return false;
return (type != null) && this.parameterizedContainers.stream()
.map(ResolvableType::resolve)
.anyMatch((container) -> container != null && container.isAssignableFrom(type));
}

private Class<?> getReturnTypeGeneric(MethodMetadata metadata, ClassLoader classLoader)
private ResolvableType getMethodReturnType(MethodMetadata metadata, ClassLoader classLoader)
throws ClassNotFoundException, LinkageError {
Class<?> declaringClass = resolve(metadata.getDeclaringClassName(), classLoader);
Method beanMethod = findBeanMethod(declaringClass, metadata.getMethodName());
return ResolvableType.forMethodReturnType(beanMethod).resolveGeneric();
return ResolvableType.forMethodReturnType(beanMethod);
}

private Method findBeanMethod(Class<?> declaringClass, String methodName) {
Expand All @@ -720,6 +707,10 @@ private SearchStrategy getStrategy() {
return (this.strategy != null) ? this.strategy : SearchStrategy.ALL;
}

Set<ResolvableType> getTypes() {
return this.types;
}

private ConditionContext getContext() {
return this.context;
}
Expand All @@ -728,19 +719,15 @@ private Set<String> getNames() {
return this.names;
}

protected Set<String> getTypes() {
return this.types;
}

private Set<String> getAnnotations() {
return this.annotations;
}

private Set<String> getIgnoredTypes() {
private Set<ResolvableType> getIgnoredTypes() {
return this.ignoredTypes;
}

private Set<Class<?>> getParameterizedContainers() {
private Set<ResolvableType> getParameterizedContainers() {
return this.parameterizedContainers;
}

Expand Down Expand Up @@ -847,13 +834,13 @@ private void recordUnmatchedAnnotation(String annotation) {
this.unmatchedAnnotations.add(annotation);
}

private void recordMatchedType(String type, Collection<String> matchingNames) {
this.matchedTypes.put(type, matchingNames);
private void recordMatchedType(ResolvableType type, Collection<String> matchingNames) {
this.matchedTypes.put(type.toString(), matchingNames);
this.namesOfAllMatches.addAll(matchingNames);
}

private void recordUnmatchedType(String type) {
this.unmatchedTypes.add(type);
private void recordUnmatchedType(ResolvableType type) {
this.unmatchedTypes.add(type.toString());
}

boolean isAllMatched() {
Expand Down
Loading

0 comments on commit b722430

Please sign in to comment.