Search code examples
javaspringspring-bootspring-annotations

How to create custom @ConditionalOnProperty for simpler usage?


In a Spring-Boot project, I use @ConditionalOnProperty to choose whether some Beans get loaded or not. It looks like the following:

@ConditionalOnProperty(
    prefix = "myservice",
    name = "implversion",
    havingValue = "a"
)
@Service
public class MyServiceImplA implements MyService {
   // ...
}

This allows me to choose with specific profiles which Bean should be loaded, for example different implementations of an interface, depending on the value of myservice.implversion being a or b or whatever other value.

I'd like to achieve the same effect with a user-friendlier annotation like such:

@OnMyServiceVersion(value = "a")
@Service
public class MyServiceImplA implements MyService {
   // ...
}

How can one do this?


I've tried annotating my custom annotation with @Conditional and implementing the Condition interface but I don't understand how to check properties that way. The Spring-Boot OnPropertyCondition extends SpringBootCondition is not public so I cannot start from there, and extending annotations isn't allowed, so I'm kind of stuck.

I've also tried the following with no success:

// INVALID CODE, DO NOT USE
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@ConditionalOnProperty(
    prefix = "myservice",
    name = "implversion",
    havingValue = OnMyServiceVersion.value()
)
public @interface OnMyServiceVersion {
    String value();
}

Solution

  • You can annotate your @OnMyServiceVersion annotation with @ConditionalOnProperty and alias the value of your annotation to the havingValue attribute of @ConditionalOnProperty:

    @Documented
    @Retention(RetentionPolicy.RUNTIME)
    @Target({ ElementType.TYPE, ElementType.METHOD })
    @ConditionalOnProperty(prefix = "myservice", name = "implversion")
    public @interface OnMyServiceVersion {
    
        @AliasFor(annotation = ConditionalOnProperty.class, attribute = "havingValue")
        String value() default "";
    
    }
    

    Here's a complete example that shows this in action:

    package com.example.demo;
    
    import java.lang.annotation.Documented;
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
    import org.springframework.core.annotation.AliasFor;
    import org.springframework.stereotype.Service;
    
    @SpringBootApplication
    public class CustomPropertyConditionApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(CustomPropertyConditionApplication.class, "--myservice.implversion=b");
        }
        
        @Service
        @OnMyServiceVersion("a")
        static class ServiceA {
            
            ServiceA() {
                System.out.println("Service A");
            }
            
        }
        
        @Service
        @OnMyServiceVersion("b")
        static class ServiceB {
            
            ServiceB() {
                System.out.println("Service B");
            }
            
        }
        
        @Documented
        @Retention(RetentionPolicy.RUNTIME)
        @Target({ ElementType.TYPE, ElementType.METHOD })
        @ConditionalOnProperty(prefix = "myservice", name = "implversion")
        static @interface OnMyServiceVersion {
            
            @AliasFor(annotation = ConditionalOnProperty.class, attribute = "havingValue")
            String value() default "";
            
        }
    
    }
    

    This will output Service B when run. If you change the arguments in the main method to --myservice.implversion=a it will output Service A. If you remove the argument, it won't output either.