Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Spring profile is ignored when reading properties from application.yml

Tags:

java

spring

I have this code that scans Spring context:

public void scan() {
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();

    context.register(SomeConfig.class);
    context.refresh();
}

I need properties to be read from application.yml file, so in SomeConfig class, I have this:

@Configuration
@PropertySource(value = "classpath:application.yml", factory = YamlPropertyLoaderFactory.class)
public class SomeConfig {
  //some beans
}

(I have copied YamlPropertyLoaderFactory class from here)

application.yml is a typical Spring Boot file with some properties by profile, and a default profile:

spring:
  profiles:
    active: p1

---

spring:
   profiles: p1

file: file1.txt

---

spring:
   profiles: p2

file: file2.txt

In some bean, I'm reading file property using @Value.

When I run my application, I'm passing -Dspring.profiles.active=p1 variable, but I'm getting an error:

Could not resolve placeholder 'file' in value "${file}"

(It should work even if I don't pass any profile since application.yml has default profile set to p1)

If I remove all profiles config from application.yml, it works fine:

file: file1.txt

So, it means that context scan is not reading the profile variable.

Also, if I set active profile "programatically", it doesn't resolve the properties either:

context.getEnvironment().setActiveProfiles("p1");
like image 822
Héctor Avatar asked Nov 02 '18 11:11

Héctor


People also ask

How do I read properties from YAML file in spring boot?

Since you'r using application. yml file, you don't need to manually load the file to the context as it's the default config file for spring application. You can simply use them in a @Component decorated class like below; @Value("${app.

How do you read property on yml application?

To read from the application. yml or property file : The easiest way to read a value from the property file or yml is to use the spring @value annotation.


1 Answers

The YamlPropertyLoaderFactory you refer to has the following code:

public class YamlPropertyLoaderFactory extends DefaultPropertySourceFactory {
    @Override
    public PropertySource<?> createPropertySource(String name, EncodedResource resource) throws IOException {
        if (resource == null){
            return super.createPropertySource(name, resource);
        }

        return new YamlPropertySourceLoader().load(resource.getResource().getFilename(), resource.getResource(), null);
    }
}

The third parameter to the YamlPropertySourceLoader.load() method is actually the profile name that you want the properties for. As this example passes in null it just returns the set of properties from the yml file not for a specific profile.

i.e.

spring:
  profiles:
    active: p1

---

I don't think it's easy to pick up the active profile name in the YamlPropertyLoaderFactory, although you could try something like...

public class YamlPropertyLoaderFactory extends DefaultPropertySourceFactory {
    @Override
    public PropertySource<?> createPropertySource(String name, EncodedResource resource) throws IOException {
        if (resource == null){
            return super.createPropertySource(name, resource);
        }

        String activeProfile = System.getProperty("spring.profiles.active");
        return new YamlPropertySourceLoader().load(resource.getResource().getFilename(), resource.getResource(), activeProfile);
    }
}

Or as you have the active profile name in the yml file, you could call YamlPropertySourceLoader().load with null to get the spring.profiles.active property then call it again to load the actual part of the yml file you want.

public class YamlPropertyLoaderFactory extends DefaultPropertySourceFactory {
    @Override
    public PropertySource<?> createPropertySource(String name, EncodedResource resource) throws IOException {
        if (resource == null){
            return super.createPropertySource(name, resource);
        }
        PropertySource<?> source = new YamlPropertySourceLoader().load(resource.getResource().getFilename(), resource.getResource(), null);
        String activeProfile = source.getProperty("spring.profiles.active");
        return new YamlPropertySourceLoader().load(resource.getResource().getFilename(), resource.getResource(), activeProfile);
    }
}

YamlPropertySourceLoader was changed back in feb 2018 (YamlPropertySourceLoader blame view in Git repo). It now returns a list of propertySource and does not have the third parameter on the load method.

Provided you have the spring.profiles.active property in the yml file you'd be able to do the following with the newer version of YamlPropertySourceLoader

public class YamlPropertyLoaderFactory extends DefaultPropertySourceFactory {

    @Override
    public PropertySource<?> createPropertySource(String name, EncodedResource resource) throws IOException {
        if (resource == null){
            return super.createPropertySource(name, resource);
        }
        List<PropertySource<?>> sources = new YamlPropertySourceLoader().load(resource.getResource().getFilename(), resource.getResource());
        for (PropertySource<?> checkSource : sources) {
            if (checkSource.containsProperty("spring.profiles.active")) {
                String activeProfile = (String) checkSource.getProperty("spring.profiles.active");
                for (PropertySource<?> source : sources) {
                    if (activeProfile.trim().equals(source.getProperty("spring.profiles"))) {
                        return source; 
                    }
                }
            }
        }
        return sources.get(0);
    }

}
like image 105
pcoates Avatar answered Oct 24 '22 03:10

pcoates