由Typesafe配置支持的Spring环境



我想在我的项目中使用typesafe配置(HOCON配置文件(,这有助于轻松有序的应用程序配置。目前我使用的是普通的Java属性文件(application.properties(,这在大型项目中很难处理。

我的项目是一个SpringMVC(不是一个Spring-boot项目(。有没有一种方法可以支持我的Spring环境(我正在被注入到我的服务中(由类型安全配置支持。这不应该阻止我现有的环境使用,如@Value注释、@Autowired Environment

我怎样才能用最少的精力和对代码的更改来做到这一点。

这是我目前的解决方案:寻找其他更好的方法

@Configuration
public class PropertyLoader{
    private static Logger logger = LoggerFactory.getLogger(PropertyLoader.class);
    @Bean
    @Autowired
    public static PropertySourcesPlaceholderConfigurer properties(Environment env) {
        PropertySourcesPlaceholderConfigurer pspc = new PropertySourcesPlaceholderConfigurer();
        Config conf = ConfigFactory.load();
        conf.resolve();
        TypesafePropertySource propertySource = new TypesafePropertySource("hoconSource", conf);
        ConfigurableEnvironment environment = (StandardEnvironment)env;
        MutablePropertySources propertySources = environment.getPropertySources();
        propertySources.addLast(propertySource);
        pspc.setPropertySources(propertySources);
        return pspc;
    }
}
class TypesafePropertySource extends PropertySource<Config>{
    public TypesafePropertySource(String name, Config source) {
        super(name, source);
    }
    @Override
    public Object getProperty(String name) {
        return this.getSource().getAnyRef(name);
    }
}

我想我想出了一种比手动将PropertySource添加到属性源更惯用的方法。创建PropertySourceFactory并用@PropertySource 引用

首先,我们有一个TypesafeConfigPropertySource几乎与您的相同:

public class TypesafeConfigPropertySource extends PropertySource<Config> {
    public TypesafeConfigPropertySource(String name, Config source) {
        super(name, source);
    }
    @Override
    public Object getProperty(String path) {
        if (source.hasPath(path)) {
            return source.getAnyRef(path);
        }
        return null;
    }
}

接下来,我们创建一个PropertySource工厂,返回该属性源

public class TypesafePropertySourceFactory implements PropertySourceFactory {
    @Override
    public PropertySource<?> createPropertySource(String name, EncodedResource resource) throws IOException {
        Config config = ConfigFactory.load(resource.getResource().getFilename()).resolve();
        String safeName = name == null ? "typeSafe" : name;
        return new TypesafeConfigPropertySource(safeName, config);
    }
}

最后,在我们的配置文件中,我们可以像任何其他PropertySource一样引用属性源,而不必自己添加PropertySource:

@Configuration
@PropertySource(factory=TypesafePropertySourceFactory.class, value="someconfig.conf")
public class PropertyLoader {
    // Nothing needed here
}

您按如下方式创建PropertySource类,它与您的类似,不同之处在于您必须返回值或null,并且不让lib抛出丢失的异常

public class TypesafeConfigPropertySource extends PropertySource<Config> {
    private static final Logger LOG = getLogger(TypesafeConfigPropertySource.class);
    public TypesafeConfigPropertySource(String name, Config source) {
        super(name, source);
    }
    @Override
    public Object getProperty(String name) {
        try {
            return source.getAnyRef(name);
        } catch (ConfigException.Missing missing) {
            LOG.trace("Property requested [{}] is not set", name);
            return null;
        }
    }
}

第二步是定义如下的bean

    @Bean
    public TypesafeConfigPropertySource provideTypesafeConfigPropertySource(
        ConfigurableEnvironment env) {
        Config conf = ConfigFactory.load().resolve();
        TypesafeConfigPropertySource source = 
                          new TypesafeConfigPropertySource("typeSafe", conf);
        MutablePropertySources sources = env.getPropertySources();
        sources.addFirst(source); // Choose if you want it first or last
        return source;
    }

如果您想将属性自动连接到其他bean,则需要使用将@DependsOn注释到属性源bean,以确保它首先加载

希望它能帮助

Laplie Anderson回答了一些小的改进:

  • 如果找不到资源,则引发异常
  • 忽略包含[:字符的路径

TypesafePropertySourceFactory.java

import java.io.IOException;
import org.springframework.core.env.PropertySource;
import org.springframework.core.io.support.EncodedResource;
import org.springframework.core.io.support.PropertySourceFactory;
import com.typesafe.config.Config;
import com.typesafe.config.ConfigFactory;
import com.typesafe.config.ConfigParseOptions;
import com.typesafe.config.ConfigResolveOptions;
public class TypesafePropertySourceFactory implements PropertySourceFactory {
  @Override
  public PropertySource<?> createPropertySource(String name, EncodedResource resource)
      throws IOException {
    Config config = ConfigFactory
        .load(resource.getResource().getFilename(),
            ConfigParseOptions.defaults().setAllowMissing(false),
            ConfigResolveOptions.noSystem()).resolve();
    String safeName = name == null ? "typeSafe" : name;
    return new TypesafeConfigPropertySource(safeName, config);
  }
}

TypesafeConfigPropertySource.java

import org.springframework.core.env.PropertySource;
import com.typesafe.config.Config;
public class TypesafeConfigPropertySource extends PropertySource<Config> {
  public TypesafeConfigPropertySource(String name, Config source) {
    super(name, source);
  }
  @Override
  public Object getProperty(String path) {
    if (path.contains("["))
      return null;
    if (path.contains(":"))
      return null;
    if (source.hasPath(path)) {
      return source.getAnyRef(path);
    }
    return null;
  }
}

我尝试了以上所有操作,但都失败了。我遇到的一个特别的问题是初始化豆子的顺序。例如,我们需要flyway支持来获取一些被覆盖的属性,这些属性来自类型安全配置,其他属性也是如此。

正如m-deinum为我们提供的一条评论中所建议的那样,以下解决方案也是有效的,也依赖于其他答案的输入。通过在加载主应用程序时使用ApplicationContextInitializer,我们确保道具在应用程序开始时加载,并正确合并到"env"中:

import org.springframework.boot.SpringBootConfiguration;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.context.annotation.Import;
@SpringBootConfiguration
@Import({MyAppConfiguration.class})
public class MyApp {
    public static void main(String[] args) {
        new SpringApplicationBuilder(MyApp.class)
            .initializers(new MyAppContextInitializer())
            .run(args);
    }
}

ContextInitializer看起来像这样:

import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;
public class MyAppContextInitializer implements
    ApplicationContextInitializer<ConfigurableApplicationContext> {
    @Override
    public void initialize(ConfigurableApplicationContext ac) {    
        PropertiesLoader loader = new PropertiesLoader(ac.getEnvironment());
        loader.addConfigToEnv();
    }
} 

PropertiesLoader的工作原理是从配置中加载属性并将其填充到环境中:

import com.typesafe.config.Config;
import com.typesafe.config.ConfigFactory;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.MutablePropertySources;
class PropertiesLoader {
    private ConfigurableEnvironment env;
    public PropertiesLoader(ConfigurableEnvironment env) {
        this.env = env;
    }
    public void addConfigToEnv() {
        MutablePropertySources sources = env.getPropertySources();
        Config finalConfig = ConfigFactory.load().resolve();
        // you can also do other stuff like: ConfigFactory.parseFile(), use Config.withFallback to merge configs, etc.
        TypesafeConfigPropertySource source = new TypesafeConfigPropertySource("typeSafe", finalConfig);
        sources.addFirst(source);
    }
}

我们还需要适用于类型安全配置的TypesafeConfigPropertySource

import com.typesafe.config.Config;
import org.springframework.core.env.PropertySource;
public class TypesafeConfigPropertySource extends PropertySource<Config> {
    public TypesafeConfigPropertySource(String name, Config source) {
        super(name, source);
    }
    @Override
    public Object getProperty(String path) {
        if (path.contains("["))
            return null;
        if (path.contains(":"))
            return null;
        if (source.hasPath(path)) {
            return source.getAnyRef(path);
        }
        return null;
    }
}

最新更新