1

I am trying to implement a Spring condition that will load my bean only if there is no other beans for a certain class. The desired behavior is similar to "@ConditionalOnMissingBean" but without using spring-boot.

I am using Spring versions 5.3.13.

Is that possible? thanks.

1 Answers1

1

I found a solution by digging around spring-boot source code. ConditionalOnBean will only work on beans defined inside a configuration (bean methods). This is also recommended by spring-boot java doc for @ConditionalOnBean

The condition can only match the bean definitions that have been processed by the application context so far and, as such, it is strongly recommended to use this condition on auto-configuration classes only. If a candidate bean may be created by another auto-configuration, make sure that the one using this condition runs after.

Here is the basics of the solution I came up with, this may be improved but the basics operate well.

The condition:

@Slf4j
class MissingBeanCondition implements ConfigurationCondition {
    @Override
    public ConfigurationPhase getConfigurationPhase() {
        return ConfigurationPhase.REGISTER_BEAN;
    }

    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        var targetBeanType = metadata.getAnnotations()
                .get(ConditionalOnMissingBean.class)
                .getValue("value", Class.class)
                // TODO throw a more informative error
                .orElseThrow(() -> new RuntimeException("Failed to evaluate MissingBeanCondition"));
        
        try {
            context.getBeanFactory().getBean(targetBeanType);
        } catch (NoSuchBeanDefinitionException e) {
            return true;
        }
       
        return false;
    }
}

The annotation:

@Target({ ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Conditional(MissingBeanCondition.class)
public @interface ConditionalOnMissingBean {
    Class<?> value();
}

Usage example:

@Bean
@Singleton
@ConditionalOnMissingBean(Provider.class)
public Provider myClass() {
    return new DefaultProvider();
}