使用PropertyOverrideConfigurer来将外部配置的属性值设置(覆盖)到Bean中
Spring Boot提供了一种名为“Type-safe Configuration Properties”的机制,它可以将外部配置的属性值(如属性文件、系统属性、环境变量等设置的值)设置到带有“@ConfigurationProperties”注解的Bean和通过“@ConfigurationProperties”注解生成的Bean的属性中。
@Component
@ConfigurationProperties("demo")
public class DemoProperties {
private String foo;
// ...
}
@ConfigurationProperties("demo")
@Bean
DemoProperties demoProperties() {
return new DemoProperties();
}
demo.foo=bar
在本文中,我想介绍一下如何使用Spring Framework所提供的PropertyOverrideConfigurer来实现类似的功能!
如果您正在使用Spring Boot,但是希望在无法通过Spring Boot机制访问的Bean属性中设置任意值,或者由于各种原因不使用(无法使用)Spring Boot,但是仍然想要实现一些类似”Type-safe Configuration Properties”的功能,那么考虑使用PropertyOverrideConfigurer可能是个不错的选择。个人希望”Type-safe Configuration Properties”能够在Spring Framework本身中得到支持,因为我也想使用yaml…..
验证版本
-
- Spring Boot 2.0.3.RELEASE
- Spring Framework 5.0.7.RELEASE
PropertyOverrideConfigurer是什么?
PropertyOverrideConfigurer是Spring的DI容器上的一个功能,它可以通过读取属性文件等来替换管理的Bean的属性值。例如,即使在Bean定义文件中硬编码了配置值…
@Bean
DemoProperties demoProperties() {
DemoProperties properties = new DemoProperties();
properties.setFoo("hoge");
return properties;
}
如果准备好PropertyOverrideConfigurer的Bean定义和属性文件,则可以更改配置值。
@Bean
static PropertyOverrideConfigurer propertyOverrideConfigurer(@Value("classpath*:demo-properties.properties") Resource... resources) {
PropertyOverrideConfigurer configurer = new PropertyOverrideConfigurer();
configurer.setLocations(resources);
return configurer;
}
demoProperties.foo=bar
属性键的规则 de
要使用PropertyOverrideConfigurer来覆盖属性值到Bean中,需要在指定的属性文件中使用”{Bean名称}.{Bean属性路径}”作为键来指定值。当然,它还支持嵌套对象、Map、List等数据结构。
public class DemoProperties {
private String foo;
private final Connection defaultConnection = new Connection();
private final Map<String, Connection> connections = LazyMap.lazyMap(new HashMap<>(), Connection::new);
private final List<Template> templates = LazyList.lazyList(new ArrayList<>(), Template::new);
// ...
static class Connection {
private String host;
private int port;
// ...
}
static class Template {
private Resource file;
private Charset encoding;
// ...
}
}
demoProperties.defaultConnection.host=localhost
demoProperties.defaultConnection.port=9999
demoProperties.connections[github].host=github.com
demoProperties.connections[github].port=443
demoProperties.templates[0].file=a.txt
demoProperties.templates[0].encoding=UTF-8
demoProperties.templates[1].file=b.txt
demoProperties.templates[1].encoding=Windows-31J
如果存在无效的属性键时的行为。
如果在DI容器中找不到与属性键的”Bean名”部分匹配的Bean,则被视为无效的属性键,并且默认情况下会产生错误。
# ...
# DIコンテナにtestBeanというBeanがいないとエラーになる
testBean.foo=aaa
您可以通过以下的Bean定义来修改此操作。
@Bean
static PropertyOverrideConfigurer propertyOverrideConfigurer(@Value("classpath*:demo-properties.properties") Resource... resources) {
PropertyOverrideConfigurer configurer = new PropertyOverrideConfigurer();
configurer.setLocations(resources);
configurer.setIgnoreInvalidKeys(true); // trueを設定すると無効なプロパティキーがあってもエラーにならない
return configurer;
}
如果指定的属性文件不存在,将发生什么行为。
如果指定的属性文件不存在,则默认情况下会出错。可以通过以下的Bean定义来更改这种行为。
@Bean
static PropertyOverrideConfigurer propertyOverrideConfigurer() {
PropertyOverrideConfigurer configurer = new PropertyOverrideConfigurer();
configurer.setLocations(new ClassPathResource("demo-properties.properties"));
configurer.setIgnoreInvalidKeys(true);
configurer.setIgnoreResourceNotFound(true); // trueを設定するとプロパティファイルがなくてもエラーにならない
return configurer;
}
使用属性占位符
在属性文件中,可以指定属性占位符(${…})来指定配置值。使用属性占位符可以将Spring(Spring Boot)属性管理功能中管理的属性值(环境变量、系统属性等指定的值)替换成实际的值,因此可以用于根据执行环境改变配置值的情况。
如果使用Spring Boot的应用程序,
demoProperties.defaultConnection.host=${default.host:localhost}
default.host=myhost
那么,在运行时将会解释如下,并设定为myhost。
即使占位符指定的属性键不存在,如果指定了默认值,则不会出错,并且将应用为localhost,因此最好将默认值设置为本地开发环境的配置值。
demoProperties.defaultConnection.host=myhost
注意:在PropertyOverrideConfigurer中指定的属性文件中定义的属性键不能指定为属性占位符。这是因为在PropertyOverrideConfigurer中指定的属性文件不会被Spring(Spring Boot)的属性管理功能视为要管理的属性值。
对于容器对象的操作
当处理嵌套对象、Map、List等容器(用于保存值的对象)时,会在以下情况下报错。
-
- コンテナオブジェクトがnullの場合
-
- Mapの場合はキーに対応するネストオブジェクトがnullの場合(キーが存在しない場合も同様)
- Listの場合はindex位置に存在するネストオブジェクトがnullの場合(指定したindex位置に要素が存在しない場合も同様)
要避免这个操作,您需要在容器对象中设置一个空对象。此外,如果要存储嵌套对象在Map和List中,还需要实现延迟初始化机制。值得注意的是,在本条目中,我们使用了commons-collections库的LazyMap和LazyList来支持延迟初始化机制。
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
<version>4.3</version>
</dependency>
public class DemoProperties {
// ...
private final Connection defaultConnection =
new Connection(); // 空のオブジェクトを生成しておく
private final Map<String, Connection> connections =
LazyMap.lazyMap(new HashMap<>(), Connection::new); // 遅延初期化実装のMapを生成しておく
private final List<Template> templates =
LazyList.lazyList(new ArrayList<>(), Template::new); // 遅延初期化実装のListを生成しておく
// ...
附加的附件
在同一个文件中将属性指定为占位符的方法
如果想要将在PropertyOverrideConfigurer指定的属性文件中的属性作为占位符进行指定,可以通过使用@PropertySource或PropertySourcesPlaceholderConfigurer,将该文件纳入Spring的属性管理功能的控制范围内来解决此问题。
@SpringBootApplication
@PropertySource("classpath:demo-properties.properties")
public class DemoApplication {
// ...
}
@Bean
static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer(
@Value("classpath*:*-properties.properties") Resource... resources) {
PropertySourcesPlaceholderConfigurer configurer = new PropertySourcesPlaceholderConfigurer();
configurer.setLocations(resources);
return configurer;
}
由于@PropertySource无法使用通配符进行指定,因此只能指定一个文件,而PropertySourcesPlaceholderConfigurer则可以指定多个文件。
如何修改Spring对容器对象的默认行为
在Spring的DI容器的默认行为中,如果容器对象为null或者Map的键或List的位置对应的嵌套对象为null(即对应的元素不存在),就会导致错误。在本节中,我们介绍了设置空对象或使用延迟初始化实现的集合的方法来解决这个问题,但也可以通过更改Spring的DI容器的行为来解决。然而…需要注意的是,采用这种方法可能会影响DI容器的整体运行。
如果是Spring Boot应用程序,只需要创建以下类型的ApplicationContext扩展类,并将其指定为Spring Boot要使用的上下文类就可以了。
public class MyAnnotationConfigApplicationContext extends AnnotationConfigApplicationContext {
public MyAnnotationConfigApplicationContext() {
super(new DefaultListableBeanFactory(){
@Override
protected void initBeanWrapper(BeanWrapper bw) {
super.initBeanWrapper(bw);
bw.setAutoGrowNestedPaths(true); // ネストオブジェクトが存在しない場合にオブジェクトを生成するように設定する
}
});
}
}
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
new SpringApplicationBuilder()
.sources(DemoApplication.class)
.contextClass(MyAnnotationConfigApplicationContext.class) // 拡張したApplicationContextクラスを指定
.run(args);
}
}
本篇介绍了一个在非Web环境中使用的ApplicationContext类的扩展示例,但实际上需要根据所使用的环境来扩展相应的实现类。
org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext
Reactive Weborg.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext
スタンドアロン(非Web)org.springframework.context.annotation.AnnotationConfigApplicationContext
在Spring Boot之外的更改方法我们不予以讨论,但可以通过重写使用的ApplicationContext实现类中的DefaultListableBeanFactory的initBeanWrapper方法,并将BeanWrapper的autoGrowNestedPaths设为true来实现相同的功能。
总结
我目前在工作中参与开发的应用程序(不幸的是… 不是Spring Boot应用程序),我想要做一些Type-safe Configuration Properties的事情(将指定的值从属性文件绑定到Java对象中),我想知道是否可以使用PropertyOverrideConfigurer! 所以我搜索并记录了一下结果。
最后,当在Spring中构建应用程序时,并不需要手动定义所有的Bean,而是可以使用框架提供的注解和XML命名空间来定义所需的Bean,来辅助提供的Bean定义。在这种情况下,虽然Bean本身已经提供了为了定制其操作而设置的属性,但有时无法通过注解和XML属性来改变属性的值。如果想要定制这样的Bean属性,我认为可以简单地使用PropertyOverrideConfigurer来实现。