0

I have a custom starter that other projects depends on, and that starter applies some configurations including a PropertySource

@Configuration
@PropertySource(value = "classpath:application-geoip.yml", factory = YamlPropertySourceFactory.class)
public class CustomStarterAutoConfiguration
{...}

application-geoip.yml contains properties specific to it's business and an enablement value

...
geoip2:
  enabled: true
...

The starter provides the geoip beans with condition to above enabled parameter.

@Configuration
@ConfigurationProperties(prefix = "geoip2")
@ConditionalOnProperty(prefix = "geoip2", name = "enabled", havingValue = "true")
public class GeoIP2ConfigurationProperties {...}

@Configuration
@ConditionalOnProperty(prefix = "geoip2", name = "enabled", havingValue = "true")
public class GeoIPConfig {
      // define required beans here with dependency to config from above @Configuration bean
}

I ship my starter with this and then create a project depending on this starter.

In my project, when I check above beans (even the property already set to true) in the context I can not see the beans initiated.

Debugged a bit and see that; While annotation ConditionalOnProperty is being processed, the context does not have the geoip2.enabled set. But If I wait until the app start and listen the ApplicationStartedEvent event. I can see the property is there.

event.getApplicationContext().getEnvironment().getProperty("geoip2.enabled") returns true.

so If I am assuming it correct, the PropertySource annotation seems to processed after ConditionalOnProperty annotation. Not always, but mostly. Depends on who wins the race.

Why I am trying this, I would like to carry the property from core with a default value. then using projects can override it in their own application.yaml files. I treid to add the property on simple project's application.yaml file and this time the property picked up and beans initiated as expected.

Olgun Kaya
  • 2,519
  • 4
  • 32
  • 46

1 Answers1

2

Rather than using @PropertySource, your custom starter should provide an EnvironmentPostProcessor implementation that's registered in META-INF/spring.factories. This post-processor is called once the Environment has been created but before the application context is refreshed and any beans are created. It should add a PropertySource to the environment that contains the geoip2 properties. If you position your PropertySource appropriately, these properties could then be overridden by those in the user's application.yaml file.

You can learn more in the reference documentation.

Andy Wilkinson
  • 108,729
  • 24
  • 257
  • 242
  • Thanks for the reply. I think I am hitting the “caution” section in the ref doc. And is the “race condition outcome” true in my experiment ? What do you mean, if I put resource properly to be able to override the value ? – Olgun Kaya Aug 11 '22 at 21:38
  • One last question, would it be the same if I have a dependency to Environtment in my @Bean annotated method. So that, spring will be waiting for environment with all property sources etc populated. I know it's a workaround, but I just wanted to learn :) – Olgun Kaya Aug 11 '22 at 22:30
  • 1
    It's not really a race condition as everything is happening serially. It's an ordering problem. For your original attempt to work, things have to happen in a certain order and that order isn't guaranteed. Injecting the environment into a `@Bean` method will have the same problem. – Andy Wilkinson Aug 12 '22 at 07:09
  • thanks a lot for your patience and guidance. – Olgun Kaya Aug 12 '22 at 07:49