Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Does Spring follow any naming convention for application configuration?

Issue : Configuration defined in application.properties is not overridden by environment variable.

I'm facing strange issue with spring configuration as configuration defined in application.properties is not overridden by environment variable when configuration is named in specific way. As mentioned in Externalized Configuration OS environment variables takes precedence over application.properties but this doesn't happen when configuration is defined as myExternal_url but it works when configuration is defined as my_external_url (in sample code below, we need to change configuration to my_external_url in ApplicationProperties.java and application.properties)

Sample Code -

@SpringBootApplication
public class ConfigApplication implements ApplicationRunner {

  @Autowired private ApplicationProperties applicationProperties;

  public static void main(String[] args) {
    SpringApplication.run(ConfigApplication.class, args);
  }

  @Override
  public void run(ApplicationArguments arg0) {
    System.out.println("External URL = " + applicationProperties.getMyExternalUrl());
  }
}

Application Bean configuration -

@Configuration
public class AppConfig {

  @Bean
  @ConfigurationProperties(prefix = "")
  public ApplicationProperties applicationProperties() {
    return new ApplicationProperties();
  }
}

ApplicationProperties class -

public class ApplicationProperties {

  @Value("${myExternal_url}")
  private String myExternalUrl;

  public String getMyExternalUrl() {
    return this.myExternalUrl;
  }

  public void setMyExternalUrl(String myExternalUrl) {
    this.myExternalUrl = myExternalUrl;
  }
}

application.properties:

myExternal_url=external_url_env_application_properties

What could be reason for this ?

EDIT - adding gradle Gradle configuration

plugins {
    id 'org.springframework.boot' version '2.4.0-M1'
    id 'io.spring.dependency-management' version '1.0.9.RELEASE'
    id 'java'
}

group = 'com.example'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '1.8'

repositories {
    mavenCentral()
    maven { url 'https://repo.spring.io/milestone' }
}

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
    compileOnly 'org.projectlombok:lombok:1.18.6'
    annotationProcessor 'org.projectlombok:lombok:1.18.6'
}

test {
    useJUnitPlatform()
}

EDIT 2

Trace log shows that myExternal_url is resolved correctly from Environment variable. Then Spring tries to resolve autowired dependencies 'applicationProperties' by calling AutowiredAnnotationBeanPostProcessor and then value is overridden by application.properties value (screen shot).

o.s.c.e.PropertySourcesPropertyResolver  : Found key 'myExternal_url' in PropertySource 'systemEnvironment' with value of type String
o.s.c.e.PropertySourcesPropertyResolver  : Found key 'myExternal_url' in PropertySource 'environmentProperties' with value of type String

Debug ApplicationProperties

like image 821
Pankaj Avatar asked Jul 20 '20 09:07

Pankaj


Video Answer


2 Answers

TL;DR @Value has correct myExternal_Url from system variables injected, but its value is later set by @ConfigurationProperties.

The trace log is correct in that the ordering from Spring will place systemEnvironment before classpath:/application.properties in the propertySource list.

The issue you are having is because of a combination of using both @Value and @ConfigurationProperties to inject/bind your properties. Say these are the values you provide:

system:
  myExternal_Url: foo
  my_external_url: bar
applications.properties:
  myExternal_url: aaa

In your ApplicationProperties:

  @Value("${myExternal_url}")
  private String myExternalUrl; // injected with foo

myExternalUrl is injected correctly with value (foo) you defined in your Environment Variables. However, @ConfigurationProperties binds the values after by using the setter methods. Since it uses relaxed bindings, it checks for different variations of myExternalUrl, it first looks for what's in your system variables and finds that myExternal_url(both camel and Underscore) isn't in one of relaxed binding forms , but then my_external_url (underscore only) is. So my_external_url's value is provided to setter:

public void setMyExternalUrl(String myExternalUrl) { // bar
  this.myExternalUrl = myExternalUrl; // myExternalUrl is reassigned from foo to bar
}

So it should be clear that your @Value would always be overridden since @ConfigurationProperties binds values after. Simply have:

public class ApplicationProperties {
    private String myExternalUrl;
    ...

then have one of binding forms - MY_EXTERNAL_URL, my-external-url, or my_external_url(Maybe there is more) - defined in your system. Then have consistent case in your application.yml in case you don't want your system variables.

my-external-url=aaa

Side Note. It is recommended that you use the form MY_EXTERNAL_URL as system environment variables.

like image 114
夢のの夢 Avatar answered Oct 24 '22 02:10

夢のの夢


  • I cannot reproduce your issue in 2.3.1.RELEASE and is working as expected and is overridden by my environment variable.

  • However I don't see the spring document where it says, @Value("${myExternal_url}") use relaxed binding as well. All I see is @ConfigurationProperties will use relaxed binding.

    • https://docs.spring.io/spring-boot/docs/current/reference/html/spring-boot-features.html#boot-features-external-config-relaxed-binding
  • So I would update your class as follows (even through it works with and without in 2.3.1.RELEASE) @Value annotation removed

    public class ApplicationProperties {

      private String myExternalUrl;

      public String getMyExternalUrl() {
        return this.myExternalUrl;
      }

      public void setMyExternalUrl(String myExternalUrl) {
        this.myExternalUrl = myExternalUrl;
      }
    }  
  • Please note, both properties will be still present in the Environment but when it is binding, it seems to decide if one overrides the other. You can see this by printing. Here I am getting the environment from applicationContext but you can autowire Environment and test this
        System.out.println(context.getEnvironment()
                             .getProperty("myExternal_url"));
        System.out.println(context.getEnvironment()
                             .getProperty("my_external_url"));
  • To confirm the above point that only @ConfigurationProperties support relaxed binding but not @Value. I created the following class
   @Component
   public class ValueInjection {
    // This prints application properties.
    @Value("${myExternal_url}")
    private String myExternal_url;

    // This prints the environment variable
    @Value("${my_external_url}")
    private String my_external_url;

// If you uncomment this the application will not start saying 
// there is no such property.
// 
//    @Value("${myExternalUrl}")
//    private String myExternalUrl;

 //   ... getters and setters
    }

like image 2
Kavithakaran Kanapathippillai Avatar answered Oct 24 '22 04:10

Kavithakaran Kanapathippillai