我可以在没有Spring AppContext的情况下手动加载@ConfigurationProperties吗?

时间:2014-11-14 23:33:51

标签: java spring spring-boot

有没有办法直接加载标有@ConfigurationProperties的类而不使用Spring Context?基本上我想重用Spring所有的智能逻辑,但对于我在Spring生命周期之外手动实例化的bean。

我有一个在Spring(Boot)中快乐加载的bean,我可以将它注入我的其他服务bean:

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

有关详细信息,请参阅spring docco http://docs.spring.io/spring-boot/docs/current-SNAPSHOT/reference/htmlsingle/#boot-features-external-config-command-line-args

但是现在我需要从Spring(在Hibernate之外)创建的类中访问这个bean。该类是在应用程序启动过程的早期创建的,Spring Boot尚未通过经典的查找帮助器方法或自己动态的静态引用使应用程序上下文可用。

所以我反而想做类似的事情:

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

让MySettings最终加载它的所有值,从命令行,系统属性,app.properties等。在Spring中有一些类可以做这样的事情,还是与应用程序上下文交织在一起?

显然我可以自己加载Properties文件,但我真的想保留Spring Boot的逻辑,使用命令行变量(例如--my.property1 = xxx),系统变量,或者application.properties甚至是yaml文件,以及它放松的绑定和类型转换的逻辑(例如,property2是一个文件),所以它与Spring语境中使用的完全相同。

可能还是梦想?

感谢您的帮助!

5 个答案:

答案 0 :(得分:12)

我有同样的“问题”。 以下是我在SpringBoot 1.3.xxx和1.4.1版本中解决它的方法。

假设我们有以下yaml配置文件:

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

我们有以下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;
        }
    }
} 

然后,要以编程方式以编程方式执行Spring的“神奇”操作(例如,在静态方法中加载某些属性),您可以执行以下操作:

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);

    }
}

答案 1 :(得分:5)

您正在寻找的“魔术”课程是PropertiesConfigurationFactory。但是我会质疑你对它的需要 - 如果你只需要绑定一次,那么Spring应该能够为你做到这一点,如果你有生命周期问题,最好解决这些问题(万一它们会破坏别的东西)。

答案 2 :(得分:3)

这是ctranxuan对Spring Boot 2.x的回答的更新。在我们的情况下,我们避免为单元测试使用Spring上下文,而是希望测试我们的配置类(在本示例中称为AppConfig,其设置以app为前缀):< / p>

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
  }
}

答案 3 :(得分:0)

这篇文章的方向相似,但通过验证和属性占位符分辨率扩展了最后一个答案。

Spring Boot Binder API support for @Value Annotations

尽管@Value中的

ConfigurationProperty注释似乎没有正确绑定(至少如果引用的值不属于ConfigurationProperty前缀名称空间的一部分)。

答案 4 :(得分:0)

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