4

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();
}
AdrienW
  • 3,092
  • 6
  • 29
  • 59

2 Answers2

4

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.

Andy Wilkinson
  • 108,729
  • 24
  • 257
  • 242
  • This perfectly does the trick, thanks a lot. One more question if I may ask: is it possible to "transform" the value, say having the custom annotation using an Enum for `value` (e.g. `MyServiceImplementation.IMPL_A`) and translating that to a String (e.g. `a`) for property checking? EDIT: [This question](https://stackoverflow.com/q/39492767/5018771) seems to near the topic but I don't understand it. – AdrienW Oct 13 '22 at 18:04
  • I suspect not. Have you tried? – Andy Wilkinson Oct 14 '22 at 13:20
  • I can only achieve this by parsing the properties and annotations "per hand", implementing the `Condition` interface, using `context.getEnvironment().getProperty("myservice.implversion")` and `metadata.getAnnotations().stream().filter(a -> OnMyServiceVersion.class.equals(a.getType())).findFirst().orElseThrow().getEnum("value", MyServiceImplementation.class)`. I can then match the property value and the enum value to decide on the result of the `matches` method but I doubt this is as robust as the `@ConditionalOnProperty` implementation. – AdrienW Oct 14 '22 at 13:38
0
@Bean(name = "emailNotification")
@ConditionalOnProperty(prefix = "notification", name = "service")
public NotificationSender notificationSender() {
    return new EmailNotification();
}

for reference https://www.baeldung.com/spring-conditionalonproperty

Gun Slinger
  • 135
  • 3
  • Sadly this is not what I am looking for. I know the tutorial you mention, my goal is to "hide" the _prefix_ and _name_ attributes from the annotation user. – AdrienW Oct 13 '22 at 16:44