Skip to content

OnBeanCondition fails to match on annotations when using Scoped Proxies #43423

Closed
@hk-2keys

Description

@hk-2keys

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";
        }
    }
}

Metadata

Metadata

Assignees

Labels

type: regressionA regression from a previous release

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions