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) {
ConfigurableListableBeanFactory beanFactory = getSearchBeanFactory(spec);
ClassLoader classLoader = spec.getContext().getClassLoader();
ConfigurableListableBeanFactory beanFactory = spec.getContext().getBeanFactory();
boolean considerHierarchy = spec.getStrategy() != SearchStrategy.CURRENT;
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();
Set<String> beansIgnoredByType = getNamesOfBeansIgnoredByType(classLoader, beanFactory, considerHierarchy,
spec.getIgnoredTypes(), parameterizedContainers);
@ -219,8 +213,8 @@ class OnBeanCondition extends FilteringSpringBootCondition implements Configurat
Map<String, BeanDefinition> typeMatchedDefinitions = getBeanDefinitionsForType(classLoader,
considerHierarchy, beanFactory, type, parameterizedContainers);
Set<String> typeMatchedNames = matchedNamesFrom(typeMatchedDefinitions,
(name, definition) -> isCandidate(name, definition, beansIgnoredByType)
&& !ScopedProxyUtils.isScopedTarget(name));
(name, definition) -> !ScopedProxyUtils.isScopedTarget(name)
&& isCandidate(beanFactory, name, definition, beansIgnoredByType));
if (typeMatchedNames.isEmpty()) {
result.recordUnmatchedType(type);
}
@ -232,7 +226,7 @@ class OnBeanCondition extends FilteringSpringBootCondition implements Configurat
Map<String, BeanDefinition> annotationMatchedDefinitions = getBeanDefinitionsForAnnotation(classLoader,
beanFactory, annotation, considerHierarchy);
Set<String> annotationMatchedNames = matchedNamesFrom(annotationMatchedDefinitions,
(name, definition) -> isCandidate(name, definition, beansIgnoredByType));
(name, definition) -> isCandidate(beanFactory, name, definition, beansIgnoredByType));
if (annotationMatchedNames.isEmpty()) {
result.recordUnmatchedAnnotation(annotation);
}
@ -252,6 +246,17 @@ class OnBeanCondition extends FilteringSpringBootCondition implements Configurat
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,
BiPredicate<String, BeanDefinition> filter) {
Set<String> matchedNames = new LinkedHashSet<>(namedDefinitions.size());
@ -263,9 +268,25 @@ class OnBeanCondition extends FilteringSpringBootCondition implements Configurat
return matchedNames;
}
private boolean isCandidate(String name, BeanDefinition definition, Set<String> ignoredBeans) {
return (!ignoredBeans.contains(name))
&& (definition == null || (definition.isAutowireCandidate() && isDefaultCandidate(definition)));
private boolean isCandidate(ConfigurableListableBeanFactory beanFactory, String name, BeanDefinition definition,
Set<String> ignoredBeans) {
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) {

View File

@ -45,6 +45,9 @@ import org.springframework.context.annotation.FilterType;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
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.util.Assert;
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
void testAnnotationOnMissingBeanConditionWithEagerFactoryBean() {
// 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)
static class OnBeanInAncestorsConfiguration {
@ -665,6 +682,17 @@ class ConditionalOnMissingBeanTests {
}
@Configuration(proxyBeanMethods = false)
@TestAnnotation
static class ScopedFooConfiguration {
@Bean
String foo() {
return "foo";
}
}
@Configuration(proxyBeanMethods = false)
static class NotAutowireCandidateConfig {
@ -717,6 +745,12 @@ class ConditionalOnMissingBeanTests {
}
@Configuration(proxyBeanMethods = false)
@Import(ScopedExampleBean.class)
static class ScopedExampleBeanConfiguration {
}
@Configuration(proxyBeanMethods = false)
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 {
CustomExampleBean() {
@ -931,4 +974,8 @@ class ConditionalOnMissingBeanTests {
}
static class TestScope extends SimpleThreadScope {
}
}