@ConditionalOnProperty for multi-valued properies

11,858

Solution 1

It looks like @ConditionalOnProperty haven't multivalued properies. In spring environment they are presented as

prop[0]=a
prop[1]=b

My solution is to make my own @Conditional extension, that is able to work with multivalued properies. Here is the example.

Annotation:

@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE, ElementType.METHOD })
@Documented
@Conditional(OnPropertyCondition.class)
public @interface ConditionalOnProperty2 {
    String name();
    String value();
}

Condition:

class OnPropertyCondition implements Condition {
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        String name = attribute(metadata, "name");
        String value = attribute(metadata, "value");

        String searchName = name;
        String property = property(context, searchName);
        int i = 0;
        do {
            if (value.equals(property)) return true;
            searchName = name + '[' + i++ + ']';
        } while ((property = property(context, searchName)) != null);
        return false;
    }

    private String attribute(AnnotatedTypeMetadata metadata, String name) {
        return (String) metadata.getAnnotationAttributes(ConditionalOnProperty2.class.getName()).get(name);
    }

    private String property(ConditionContext context, String name) {
        return context.getEnvironment().getProperty(name);
    }
}

Usage:

 @Bean
 @ConditionalOnProperty2(name = "prop", havingValue = "a")
 public SomeBean bean1() {
     return new SomeBean1();
 }

 @Bean
 @ConditionalOnProperty2(name = "prop", havingValue = "b")
 public SomeBean bean2() {
     return new SomeBean2();
 }

Solution 2

You can use Spring Boot @ConditionalOnExpression annotation, not just in the @bean-annotated methods, but directly on classes as well:

@ConditionalOnExpression("${my.prop}")
@Component
class SomeBean1 implements SomeBean {}

@ConditionalOnExpression("!${my.prop}")
@Component
class SomeBean2 implements SomeBean {}

Having said this I personally prefer using pure Java:

@Value("${my.prop}") String myProp;
...
@Bean SomeBean someBean() {
    if ("abc".equals(myProp)) {return new SomeBean1();} else {return new SomeBean2();}
}
Share:
11,858
Maksim Prabarshchuk
Author by

Maksim Prabarshchuk

Updated on June 04, 2022

Comments

  • Maksim Prabarshchuk
    Maksim Prabarshchuk almost 2 years

    Is there any way to use @ConditionalOnProperty annotation based on multi-valued property?

    Spring configuration:

    @Bean
    @ConditionalOnProperty(name = "prop", havingValue = "a")
    public SomeBean bean1() {
        return new SomeBean1();
    }
    
    @Bean
    @ConditionalOnProperty(name = "prop", havingValue = "b")
    public SomeBean bean2() {
        return new SomeBean2();
    }
    

    and application.yaml

    prop: 
     - a
     - b
    

    I expect that both beans: bean1 and bean2 will be registered in the spring context, but no one from them isn't registered. Is there any way to do it?

  • Maksim Prabarshchuk
    Maksim Prabarshchuk almost 8 years
    Can you please give me an example with @ConditionalOnExpression and multivalue property? I mean some thing like @ConditionalOnExpression("${my.prop}.contains('a')")?
  • Maksim Prabarshchuk
    Maksim Prabarshchuk almost 8 years
    @Value approach isn't good for me, cause I want to register in the context the collection of beans, not only one. and I don't know in advance the beans count (it depends on the property 'prop')
  • Alexander
    Alexander almost 8 years
    I think it should be @ConditionalOnExpression("'${my.prop}'.contains('a')") or for example @ConditionalOnExpression("'${my.prop}'=='abc'"). You can use any expression that SpEL supports. I understand that you want to rely on the component scan, otherwise you could just do all the logic inside you @Configuration class.
  • Alexander
    Alexander almost 8 years
    I still personally prefer to have this logic in pure Java, for example: class SomeBeanManager {@Autowired void setAllOfThem(List<SomeBean> allOfThem) {...} } and choose the ones based on your properties.
  • Maksim Prabarshchuk
    Maksim Prabarshchuk almost 8 years
    Unfurtunately @ConditionalOnExpression("${my.prop}.contains('a')") throws an exception. I'm not sure but it seems that spring conditional SpEL can't work with multivalued properties. I'll try to implement my own @Conditional extension to deal with it.
  • Maksim Prabarshchuk
    Maksim Prabarshchuk almost 8 years
    I've got your poin about pure java way and totoly agree with such approach, but I don't want to register in context beans that aren't specified by property (they have time consuming initialize methods)
  • Alexander
    Alexander almost 8 years
    Did you try to surround ${my.prop} with single quotes? " '${my.prop}'.contains('a') "
  • Alexander
    Alexander almost 8 years
    ${my.prop} is going to be replaced by a literal by PropertyXxxBeanFactoryPostProcessor (don't remember its exact name) before your SpEL expression is evaluated. So without single quotes your expression will become "abc.contains('a')" if my.prop=abc which will deservedly throw an exception. I think Spring will look for a bean named 'abc' in this case and will not find it.
  • Maksim Prabarshchuk
    Maksim Prabarshchuk almost 8 years
    Yes, I've tryed to surround ${my.prop} with single quotes, but it haven't helped me. As I've understood sping treats such expression as a string, but my property is something like array or list. Anyway thank you very much for your help! I really appreciate it :)
  • Alexander
    Alexander almost 8 years
    Welcome :). I still suggest you to take a look at Spring EL: docs.spring.io/spring/docs/current/spring-framework-referenc‌​e/… . It is very powerful, it can for example run any static method where you could split your property as you like and place a breakpoint if needed.
  • Brice
    Brice over 5 years
    If running with Spring Boot, one may want to to extends SpringBootCondition instead as it provides more logging info if something does not work properly.
  • Thiagarajan Ramanathan
    Thiagarajan Ramanathan almost 4 years
    @ConditionalOnExpression("'${vault.environment.type}'.contai‌​ns('MAP')") worked for me.