Description
Previous to 3.4.0 (tested in 3.3.5), scoped beans using ScopedProxyMode.INTERFACES or ScopedProxyMode.TARGET_CLASS were being matched by any OnBeanCondition that checked for annotations. (e.g. @ConditionOnMissingBean(annotation = SomeAnnotation.class)). With 3.4.0 adding a check for autowire candidates and default candidates, this results in these beans no longer being matched.
Example
In this example testConditionalOnMissingBean() passes in both 3.4.0 and 3.3.5, but testConditionalOnMissingBean_Scoped() fails in 3.4.0 and passes in 3.3.5.
Note: In 3.3.5 the condition @ConditionalOnMissingBean(value = Void.class, annotation = AnnotationConditionQualifier.class) was used to effectively disable type based matching.
Source
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
@Qualifier
public @interface AnnotationConditionQualifier {
}
@AutoConfiguration
public class AnnotationConditionAutoConfiguration {
@Bean
@AnnotationConditionQualifier
@ConditionalOnMissingBean(annotation = AnnotationConditionQualifier.class)
public Supplier<String> supplier() {
return () -> "auto-configuration-string";
}
}
Test
class AnnotationConditionAutoConfigurationTest {
WebApplicationContextRunner runner;
@BeforeEach
void setUp() {
runner = new WebApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(AnnotationConditionAutoConfiguration.class));
}
@Test
void testConditionalOnMissingBean_Scoped() {
runner.withUserConfiguration(ScopedConfig.class)
.run(context -> {
assertThat(context)
.getBeanNames(Supplier.class)
.containsExactlyInAnyOrder(
"customSupplier",
ScopedProxyUtils.getTargetBeanName("customSupplier")
);
});
}
@Test
void testConditionalOnMissingBean() {
runner.withUserConfiguration(Config.class)
.run(context -> {
assertThat(context)
.getBeanNames(Supplier.class)
.containsExactlyInAnyOrder(
"customSupplier"
);
});
}
@Configuration
@Import(CustomSupplier.class)
static class Config {
}
@Configuration
@Import(ScopedCustomSupplier.class)
static class ScopedConfig {
}
@Component("customSupplier")
@AnnotationConditionQualifier
static class CustomSupplier implements Supplier<String> {
@Override
public String get() {
return "custom-string";
}
}
@Component("customSupplier")
@AnnotationConditionQualifier
@Scope(scopeName = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.INTERFACES)
static class ScopedCustomSupplier implements Supplier<String> {
@Override
public String get() {
return "scoped-custom-string";
}
}
}