Spring 类型安全配置支持的环境
Spring Environment backed by Typesafe Config
我想在我的项目中使用类型安全配置(HOCON 配置文件),这有助于简单而有组织的应用程序配置。目前我正在使用普通的 Java 属性文件(application.properties),这在大项目上很难处理。
我的项目是 Spring MVC(不是 spring 引导项目)。有没有办法支持我的 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 class 如下,它与您的类似,不同之处在于您必须 return 值或 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,您需要使用
对 propertysource bean 的注释 @DependsOn
以确保它首先被加载
希望对您有所帮助
我想我想出了一个比手动将 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 factory that returns that 属性 source
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
}
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;
}
}
以上方法我都试过了,都失败了。我遇到的一个特殊问题是 bean 的初始化顺序。例如,我们需要 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;
}
}
我想在我的项目中使用类型安全配置(HOCON 配置文件),这有助于简单而有组织的应用程序配置。目前我正在使用普通的 Java 属性文件(application.properties),这在大项目上很难处理。
我的项目是 Spring MVC(不是 spring 引导项目)。有没有办法支持我的 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 class 如下,它与您的类似,不同之处在于您必须 return 值或 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,您需要使用
对 propertysource bean 的注释 @DependsOn
以确保它首先被加载
希望对您有所帮助
我想我想出了一个比手动将 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 factory that returns that 属性 source
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
}
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;
}
}
以上方法我都试过了,都失败了。我遇到的一个特殊问题是 bean 的初始化顺序。例如,我们需要 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;
}
}