5

Can I use @Autowired Spring 4.x @Components with a @ConditionalOnProperty to choose implementations of a Feature based on a featuretoggles.properties file?

public class Controller {
  @Autowired
  private Feature feature;
}

@Component
@ConditionalOnProperty(name = "b", havingValue = "off")
public class A implements Feature {
}

@Component
@ConditionalOnProperty(name = "b", havingValue = "on")
public class B implements Feature {
}

@Configuration
@PropertySource("classpath:featuretoggles.properties")
public class SomeRandomConfig {
}

With a src/main/resources/featuretoggles.properties file:

b = on

(That the name of the toggle "b" and the name of the class "B" match is coincidence; it's not my aim to have these equal, the toggle could have any name.)

This fails to auto-wire feature in the Controller with an UnsatisfiedDependencyException, saying "No qualifying bean of type 'Feature' available: expected at least 1 bean that qualifies as autowire candidate".

I know I can realize this with a @Configuration class that chooses a @Bean depending on the property. But when I do that I have to add a new Configuration class each time I add a feature toggle, and those Configuration classes will be highly similar:

@Configuration
@PropertySource("classpath:featuretoggles.properties")
public class FeatureConfig {

    @Bean
    @ConditionalOnProperty(name = "b", havingValue = "on")
    public Feature useB() {
        return new B();
    }

    @Bean
    @ConditionalOnProperty(name = "b", havingValue = "off")
    public Feature useA() {
        return new A();
    }

}
Florian
  • 4,821
  • 2
  • 19
  • 44
  • a `@PropertySource` is only useful on a `@Configuration` file. Now it will never be loaded and your conditionals will never match. That said I still would opt for a configuration class as that is less magic and in your face then all those annotated components. – M. Deinum May 19 '17 at 18:30
  • You're perfectly right. Tried that and updated the example with what I changed. Adding a Configuration class unfortunately doesn't improve things. The sample above still fails to wire. What I couldn't find out so far was whether `@Component` doesn't work with `@ConditionalOnProperty` at all, or whether I simply still have a bug that fails to load the property file. Sadly, Spring doesn't really give meaningful log output for this. – Florian May 19 '17 at 19:01

1 Answers1

3

I did what you're trying to do by following this guide. First step was to write a Condition...

public class OnEnvironmentPropertyCondition implements Condition
{
  @Override
  public boolean matches(ConditionContext ctx, AnnotatedTypeMetadata meta)
  {
    Environment env = ctx.getEnvironment();
    Map<String, Object> attr = meta.getAnnotationAttributes(
                                 ConditionalOnEnvProperty.class.getName());

    boolean shouldPropExist = (Boolean)attr.get("exists");
    String prop = (String)attr.get("value");

    boolean doesPropExist = env.getProperty(prop) != null;

    // doesPropExist    shouldPropExist    result
    //    true             true             true
    //    true             false            false
    //    false            true             false
    //    true             false            true
    return doesPropExist == shouldPropExist;
  }
}

...then an annotation using that condition.

/*
 * Condition returns true if myprop exists:
 * @ConditionalOnEnvProperty("myprop")
 *
 * Condition returns true if myprop does not exist
 * @ConditionalOnEnvProperty(value="myprop", exists=false)
 */
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Conditional(OnEnvironmentPropertyCondition.class)
public @interface ConditionalOnEnvProperty
{
  public String value();
  public boolean exists() default true;
}

You can add featuretoggles.properties to the environment with the @PropertySource annotation.

Paul
  • 19,704
  • 14
  • 78
  • 96
  • 1
    At first thought we had misunderstood each other. But gave it another try and this actually solves the issue. Thanks! – Florian May 19 '17 at 22:46