zonski
zonski

Reputation: 355

Can I manually load @ConfigurationProperties without the Spring AppContext?

Is there any way to load a class marked with @ConfigurationProperties without using a Spring Context directly? Basically I want to reuse all the smart logic that Spring does but for a bean I manually instantiate outside of the Spring lifecycle.

I have a bean that loads happily in Spring (Boot) and I can inject this into my other Service beans:

@ConfigurationProperties(prefix="my")
public class MySettings {
    String property1;
    File property2;
} 

See the spring docco for more info http://docs.spring.io/spring-boot/docs/current-SNAPSHOT/reference/htmlsingle/#boot-features-external-config-command-line-args

But now I need to access this bean from a class that is created outside of Spring (by Hibernate). The class is created so early in the app startup process that Spring Boot has not yet made the application context available through the classic lookup helper methods or roll-my-own static references.

So I instead want to do something like:

MySettings mySettings = new MySettings(); 
SpringPropertyLoadingMagicClass loader = new SpringPropertyLoadingMagicClass();
loader.populatePropertyValues(mySettings);

And have MySettings end up with all its values loaded, from the command line, system properties, app.properties, etc. Is there some class in Spring that does something like this or is it all too interwoven with the application context?

Obviously I could just load the Properties file myself, but I really want to keep Spring Boot's logic around using command line variables (e.g. --my.property1=xxx), or system variables, or application.properties or even a yaml file, as well as its logic around relaxed binding and type conversion (e.g. property2 is a File) so that it all works exactly the same as when used in the Spring context.

Possible or pipe dream?

Thanks for your help!

Upvotes: 20

Views: 21500

Answers (5)

lemonzone2010
lemonzone2010

Reputation: 121

import org.springframework.boot.context.properties.bind.Binder
val binder = Binder.get(environment)
binder.bind(prefix, MySettings.class).get

Upvotes: 2

FloW
FloW

Reputation: 199

This post is going into similar direction but extends the last answer with also validation and property placeholder resolutions.

Spring Boot Binder API support for @Value Annotations

@Value annotations in ConfigurationPropertys don't seem to bind properly though (at least if the referenced values are not part of the ConfigurationProperty's prefix namespace).

Upvotes: 0

edgraaff
edgraaff

Reputation: 561

Here's an update to ctranxuan's answer for Spring Boot 2.x. In our situation, we avoid spinning up a Spring context for unit tests, but do like to test our configuration classes (which is called AppConfig in this example, and its settings are prefixed by app):

public class AppConfigTest {
  private static AppConfig config;

  @BeforeClass
  public static void init() {
    YamlPropertiesFactoryBean factoryBean = new YamlPropertiesFactoryBean();
    factoryBean.setResources(new ClassPathResource("application.yaml"));

    Properties properties = factoryBean.getObject();

    ConfigurationPropertySource propertySource = new MapConfigurationPropertySource(properties);
    Binder binder = new Binder(propertySource);

    config = binder.bind("app", AppConfig.class).get(); // same prefix as @ConfigurationProperties
  }
}

Upvotes: 20

ctranxuan
ctranxuan

Reputation: 815

I had the same "issue". Here is how I solved it in SpringBoot version 1.3.xxx and 1.4.1.

Let's say we have the following yaml configuration file:

foo:
  apis:
      -
       name: Happy Api
       path: /happyApi.json?v=bar
      -
       name: Grumpy Api
       path: /grumpyApi.json?v=grrr

and we have the following ConfigurationProperties:

@ConfigurationProperties(prefix = "foo")
public class ApisProperties {
    private List<ApiPath> apis = Lists.newArrayList();

    public ApisProperties() {
    }

    public List<ApiPath> getApis() {
        return apis;
    }

    public static class ApiPath {
        private String name;
        private String path;

        public String getName() {
            return name;
        }

        public void setName(final String aName) {
            name = aName;
        }

        public String getPath() {
            return path;
        }

        public void setPath(final String aPath) {
            path = aPath;
        }
    }
} 

Then, to do the "magic" things of Spring Boot programmatically (e.g. loading some properties in a static method), you can do:

private static ApisProperties apiProperties() {
    try {
        ClassPathResource resource;
        resource = new ClassPathResource("/config/application.yml");

        YamlPropertiesFactoryBean factoryBean;
        factoryBean = new YamlPropertiesFactoryBean();
        factoryBean.setSingleton(true); // optional depends on your use-case
        factoryBean.setResources(resource);

        Properties properties;
        properties = factoryBean.getObject();

        MutablePropertySources propertySources;
        propertySources = new MutablePropertySources();
        propertySources.addLast(new PropertiesPropertySource("apis", properties));

        ApisProperties apisProperties;
        apisProperties = new ApisProperties();

        PropertiesConfigurationFactory<ApisProperties> configurationFactory;
        configurationFactory = new PropertiesConfigurationFactory<>(apisProperties);
        configurationFactory.setPropertySources(propertySources);
        configurationFactory.setTargetName("foo"); // it's the same prefix as the one defined in the @ConfigurationProperties

        configurationFactory.bindPropertiesToTarget();
        return apisProperties; // apiProperties are fed with the values defined in the application.yaml

    } catch (BindException e) {
        throw new IllegalArgumentException(e);

    }
}

Upvotes: 18

Dave Syer
Dave Syer

Reputation: 58124

The "magic" class you are looking for is PropertiesConfigurationFactory. But I would question your need for it - if you only need to bind once, then Spring should be able to do it for you, and if you have lifecycle issues it would be better to address those (in case they break something else).

Upvotes: 6

Related Questions