Fix annotation matching when using scoped proxies

Update `OnBeanCondition` to check `isAutowireCandidate` on the original
bean of scoped proxy targets.

Fixes gh-43423
This commit is contained in:
Phillip Webb 2024-12-05 14:44:38 -08:00
parent 8ca8ab14f6
commit 86b0c768e7
2 changed files with 81 additions and 13 deletions

View File

@ -202,16 +202,10 @@ class OnBeanCondition extends FilteringSpringBootCondition implements Configurat
} }
protected final MatchResult getMatchingBeans(Spec<?> spec) { protected final MatchResult getMatchingBeans(Spec<?> spec) {
ConfigurableListableBeanFactory beanFactory = getSearchBeanFactory(spec);
ClassLoader classLoader = spec.getContext().getClassLoader(); ClassLoader classLoader = spec.getContext().getClassLoader();
ConfigurableListableBeanFactory beanFactory = spec.getContext().getBeanFactory();
boolean considerHierarchy = spec.getStrategy() != SearchStrategy.CURRENT; boolean considerHierarchy = spec.getStrategy() != SearchStrategy.CURRENT;
Set<Class<?>> parameterizedContainers = spec.getParameterizedContainers(); Set<Class<?>> parameterizedContainers = spec.getParameterizedContainers();
if (spec.getStrategy() == SearchStrategy.ANCESTORS) {
BeanFactory parent = beanFactory.getParentBeanFactory();
Assert.isInstanceOf(ConfigurableListableBeanFactory.class, parent,
"Unable to use SearchStrategy.ANCESTORS");
beanFactory = (ConfigurableListableBeanFactory) parent;
}
MatchResult result = new MatchResult(); MatchResult result = new MatchResult();
Set<String> beansIgnoredByType = getNamesOfBeansIgnoredByType(classLoader, beanFactory, considerHierarchy, Set<String> beansIgnoredByType = getNamesOfBeansIgnoredByType(classLoader, beanFactory, considerHierarchy,
spec.getIgnoredTypes(), parameterizedContainers); spec.getIgnoredTypes(), parameterizedContainers);
@ -219,8 +213,8 @@ class OnBeanCondition extends FilteringSpringBootCondition implements Configurat
Map<String, BeanDefinition> typeMatchedDefinitions = getBeanDefinitionsForType(classLoader, Map<String, BeanDefinition> typeMatchedDefinitions = getBeanDefinitionsForType(classLoader,
considerHierarchy, beanFactory, type, parameterizedContainers); considerHierarchy, beanFactory, type, parameterizedContainers);
Set<String> typeMatchedNames = matchedNamesFrom(typeMatchedDefinitions, Set<String> typeMatchedNames = matchedNamesFrom(typeMatchedDefinitions,
(name, definition) -> isCandidate(name, definition, beansIgnoredByType) (name, definition) -> !ScopedProxyUtils.isScopedTarget(name)
&& !ScopedProxyUtils.isScopedTarget(name)); && isCandidate(beanFactory, name, definition, beansIgnoredByType));
if (typeMatchedNames.isEmpty()) { if (typeMatchedNames.isEmpty()) {
result.recordUnmatchedType(type); result.recordUnmatchedType(type);
} }
@ -232,7 +226,7 @@ class OnBeanCondition extends FilteringSpringBootCondition implements Configurat
Map<String, BeanDefinition> annotationMatchedDefinitions = getBeanDefinitionsForAnnotation(classLoader, Map<String, BeanDefinition> annotationMatchedDefinitions = getBeanDefinitionsForAnnotation(classLoader,
beanFactory, annotation, considerHierarchy); beanFactory, annotation, considerHierarchy);
Set<String> annotationMatchedNames = matchedNamesFrom(annotationMatchedDefinitions, Set<String> annotationMatchedNames = matchedNamesFrom(annotationMatchedDefinitions,
(name, definition) -> isCandidate(name, definition, beansIgnoredByType)); (name, definition) -> isCandidate(beanFactory, name, definition, beansIgnoredByType));
if (annotationMatchedNames.isEmpty()) { if (annotationMatchedNames.isEmpty()) {
result.recordUnmatchedAnnotation(annotation); result.recordUnmatchedAnnotation(annotation);
} }
@ -252,6 +246,17 @@ class OnBeanCondition extends FilteringSpringBootCondition implements Configurat
return result; return result;
} }
private ConfigurableListableBeanFactory getSearchBeanFactory(Spec<?> spec) {
ConfigurableListableBeanFactory beanFactory = spec.getContext().getBeanFactory();
if (spec.getStrategy() == SearchStrategy.ANCESTORS) {
BeanFactory parent = beanFactory.getParentBeanFactory();
Assert.isInstanceOf(ConfigurableListableBeanFactory.class, parent,
"Unable to use SearchStrategy.ANCESTORS");
beanFactory = (ConfigurableListableBeanFactory) parent;
}
return beanFactory;
}
private Set<String> matchedNamesFrom(Map<String, BeanDefinition> namedDefinitions, private Set<String> matchedNamesFrom(Map<String, BeanDefinition> namedDefinitions,
BiPredicate<String, BeanDefinition> filter) { BiPredicate<String, BeanDefinition> filter) {
Set<String> matchedNames = new LinkedHashSet<>(namedDefinitions.size()); Set<String> matchedNames = new LinkedHashSet<>(namedDefinitions.size());
@ -263,9 +268,25 @@ class OnBeanCondition extends FilteringSpringBootCondition implements Configurat
return matchedNames; return matchedNames;
} }
private boolean isCandidate(String name, BeanDefinition definition, Set<String> ignoredBeans) { private boolean isCandidate(ConfigurableListableBeanFactory beanFactory, String name, BeanDefinition definition,
return (!ignoredBeans.contains(name)) Set<String> ignoredBeans) {
&& (definition == null || (definition.isAutowireCandidate() && isDefaultCandidate(definition))); return (!ignoredBeans.contains(name)) && (definition == null
|| isAutowireCandidate(beanFactory, name, definition) && isDefaultCandidate(definition));
}
private boolean isAutowireCandidate(ConfigurableListableBeanFactory beanFactory, String name,
BeanDefinition definition) {
return definition.isAutowireCandidate() || isScopeTargetAutowireCandidate(beanFactory, name);
}
private boolean isScopeTargetAutowireCandidate(ConfigurableListableBeanFactory beanFactory, String name) {
try {
return ScopedProxyUtils.isScopedTarget(name)
&& beanFactory.getBeanDefinition(ScopedProxyUtils.getOriginalBeanName(name)).isAutowireCandidate();
}
catch (NoSuchBeanDefinitionException ex) {
return false;
}
} }
private boolean isDefaultCandidate(BeanDefinition definition) { private boolean isDefaultCandidate(BeanDefinition definition) {

View File

@ -45,6 +45,9 @@ import org.springframework.context.annotation.FilterType;
import org.springframework.context.annotation.Import; import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar; import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.context.annotation.ImportResource; import org.springframework.context.annotation.ImportResource;
import org.springframework.context.annotation.Scope;
import org.springframework.context.annotation.ScopedProxyMode;
import org.springframework.context.support.SimpleThreadScope;
import org.springframework.core.type.AnnotationMetadata; import org.springframework.core.type.AnnotationMetadata;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
@ -124,6 +127,16 @@ class ConditionalOnMissingBeanTests {
}); });
} }
@Test
void testAnnotationOnMissingBeanConditionWithScopedProxy() {
this.contextRunner.withInitializer(this::registerScope)
.withUserConfiguration(ScopedExampleBeanConfiguration.class, OnAnnotationConfiguration.class)
.run((context) -> {
assertThat(context).doesNotHaveBean("bar");
assertThat(context.getBean(ScopedExampleBean.class)).hasToString("test");
});
}
@Test @Test
void testAnnotationOnMissingBeanConditionWithEagerFactoryBean() { void testAnnotationOnMissingBeanConditionWithEagerFactoryBean() {
// Rigorous test for SPR-11069 // Rigorous test for SPR-11069
@ -407,6 +420,10 @@ class ConditionalOnMissingBeanTests {
}; };
} }
private void registerScope(ConfigurableApplicationContext applicationContext) {
applicationContext.getBeanFactory().registerScope("test", new TestScope());
}
@Configuration(proxyBeanMethods = false) @Configuration(proxyBeanMethods = false)
static class OnBeanInAncestorsConfiguration { static class OnBeanInAncestorsConfiguration {
@ -665,6 +682,17 @@ class ConditionalOnMissingBeanTests {
} }
@Configuration(proxyBeanMethods = false)
@TestAnnotation
static class ScopedFooConfiguration {
@Bean
String foo() {
return "foo";
}
}
@Configuration(proxyBeanMethods = false) @Configuration(proxyBeanMethods = false)
static class NotAutowireCandidateConfig { static class NotAutowireCandidateConfig {
@ -717,6 +745,12 @@ class ConditionalOnMissingBeanTests {
} }
@Configuration(proxyBeanMethods = false)
@Import(ScopedExampleBean.class)
static class ScopedExampleBeanConfiguration {
}
@Configuration(proxyBeanMethods = false) @Configuration(proxyBeanMethods = false)
static class UnrelatedExampleBeanConfiguration { static class UnrelatedExampleBeanConfiguration {
@ -893,6 +927,15 @@ class ConditionalOnMissingBeanTests {
} }
@Scope(scopeName = "test", proxyMode = ScopedProxyMode.TARGET_CLASS)
static class ScopedExampleBean extends ExampleBean {
ScopedExampleBean() {
super("test");
}
}
static class CustomExampleBean extends ExampleBean { static class CustomExampleBean extends ExampleBean {
CustomExampleBean() { CustomExampleBean() {
@ -931,4 +974,8 @@ class ConditionalOnMissingBeanTests {
} }
static class TestScope extends SimpleThreadScope {
}
} }