Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Spring @CondiitonalOnProperty, how to match only if missing

I have two factory methods:

@Bean
@ConditionalOnProperty("some.property.text")
public Apple createAppleX() {}

and

@Bean
@ConditionalOnProperty("some.property.text", matchIfMissing=true)
public Apple createAppleY() {}

In case of not having "some.property.text" property at all - second method works fine and first one is ignored, which is desired behavior.

In case we have some string set to "some.property.text" - both methods are considered valid for producing Apple objects, which leads the application to fail with error "No qualifying bean of type".

Is it possible to avoid second method to be considered as factory method in case we have some value for the property? Especially, is it possible via annotations only?

like image 234
XZen Avatar asked Nov 29 '17 20:11

XZen


People also ask

How do you conditionally load beans?

It allows to load beans conditionally depending on a certain environment property: @Configuration @ConditionalOnProperty( value="module. enabled", havingValue = "true", matchIfMissing = true) class CrossCuttingConcernModule { ... } The CrossCuttingConcernModule is only loaded if the module.

How do you use conditional property?

The @ConditionalOnProperty annotation allows you to load beans conditionally depending on a certain environment property or configuration of a property. Use the prefix and name attributes to specify the property that should be checked. By default, any property that exists and is not equal to false is matched.

What is conditional on property?

Annotation Type ConditionalOnProperty. @Conditional that checks if the specified properties have a specific value. By default the properties must be present in the Environment and not equal to false . The havingValue() and matchIfMissing() attributes allow further customizations.

How do you use a spring conditional Bean?

Conditions based on a Bean definition are present in Spring Application context. Conditions based on a Bean object are present in Spring Application context. Conditions based on some or all Bean properties values. Conditions based on some Resources are present in current Spring Application Context or not.


3 Answers

I was facing same problem and here is my solution:

@Bean
@ConditionalOnProperty("some.property.text")
public Apple createAppleX() {}

@Bean
@ConditionalOnProperty("some.property.text", matchIfMissing=true, havingValue="value_that_never_appears")
public Apple createAppleY() {}
like image 74
SztefanWons Avatar answered Sep 21 '22 08:09

SztefanWons


You can use NoneNestedConditions to negate one or more nested conditions. Something like this:

class NoSomePropertyCondition extends NoneNestedConditions {

    NoSomePropertyCondition() {
        super(ConfigurationPhase.PARSE_CONFIGURATION);
    }

    @ConditionalOnProperty("some.property.text")
    static class SomePropertyCondition {

    }

}

You can then use this custom condition on one of your bean methods:

@Bean
@ConditionalOnProperty("some.property.text")
public Apple createAppleX() {}

@Bean
@Conditional(NoSomePropertyCondition.class)
public Apple createAppleY() {}
like image 35
Andy Wilkinson Avatar answered Sep 20 '22 08:09

Andy Wilkinson


In the spirit of one-upmanship, Here is a more reusable annotation:

@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE, ElementType.METHOD })
@Conditional(ConditionalOnMissingProperty.MissingPropertyCondition.class)
public @interface ConditionalOnMissingProperty {

    String PROPERTY_KEYS = "propertyKeys";

    @AliasFor(PROPERTY_KEYS)
    String[] value() default {};

    @AliasFor("value")
    String[] propertyKeys() default {};

    class MissingPropertyCondition extends SpringBootCondition {
        @Override
        public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
            String[] keys = (String[]) metadata.getAnnotationAttributes(ConditionalOnMissingProperty.class.getName()).get(PROPERTY_KEYS);
            if (keys.length > 0) {
                boolean allMissing = true;
                for (String key : keys) {
                    String propertyValue = context.getEnvironment().getProperty(key);
                    String propertyValueList = context.getEnvironment().getProperty(key + "[0]"); //in case of list
                    allMissing &= (StringUtils.isEmpty(propertyValue) && StringUtils.isEmpty(propertyValueList));
                }
                if (allMissing) {
                    return new ConditionOutcome(true, "The following properties were all null or empty in the environment: " + Arrays.toString(keys));
                }
                return new ConditionOutcome(false, "one or more properties were found.");
            } else {
                throw new RuntimeException("expected method annotated with " + ConditionalOnMissingProperty.class.getName() + " to include a non-empty " + PROPERTY_KEYS + " attribute");
            }
        }
    }
}

Annotated beans or configuration are activated when one or more mentioned properties all do not exist:

@ConditionalOnMissingProperty({"app.foo.bar"})
@Configuration
public class SomeConfiguration {
  //... won't run if there is an app.foo.bar property with non-empty contents.
}

Later if I add more comprehensive reporting to the false outcome, I'll add it here.

like image 31
coderatchet Avatar answered Sep 17 '22 08:09

coderatchet