Post-根据前缀处理 Spring 中的 YAML 属性以从 REST 服务检索 属性
Post-processing YAML properties in Spring based on prefix to retrieve property from REST service
我有一个 Spring 启动配置 YAML,类似
spring:
application:
name: my-app
a: a literal
b: <<external due to special first and last chars>>
我想做的是添加某种解析器,它将检测 b
的值是否为 <<X>>
的形式,并将触发从外部 rest 检索该值api 在 YAML 传递给运行时保存配置的 bean 之前在内存中覆盖 YAML 中的值
我尝试使用 EnvironmentPostProcessor
但失败了,因为我无法获得实际的 属性 值,只有 属性 sources,所以我无法 post- 处理这些值。
目前对我有用的是 @Configuration
bean,它包含字段 a
和 b
,在设置器中实现一些东西来检测 spring 正在尝试设置以 <<
开始并以 >>
结束,如果是这样,用我从其余 api 检索到的版本覆盖加载到 pojo 中的内容。这并不理想,因为我最终得到了很多重复
在 Spring 5 中实现类似功能的正确方法是什么?我知道 spring 属性支持使用语法 ${a}
对其他属性的引用,因此必须有一些机制已经允许添加自定义占位符解析器
不知道正确的方法,但是从 REST 调用获取属性的一种方法是实现您自己的 PropertySource
,它获取(和缓存?)具体命名的属性的值。
这是我使用 Spring Boot 2.1.5 想出的一个 hacky 解决方案。
可能最好使用自定义 PropertyResolver
基本上它是这样的:
- 抓住我关心的
PropertySource
。对于这种情况,它是 application.properties
。应用程序可以有 N
个来源,因此如果还有其他地方可能会出现 << >>
,那么您也需要检查它们。
- 循环遍历
<< >>
的源值
- 如果匹配则动态替换该值。
我的属性是:
a=hello from a
b=<<I need special attention>>
我被黑的 ApplicationListener
是:
import org.apache.commons.lang3.StringUtils;
import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent;
import org.springframework.boot.env.OriginTrackedMapPropertySource;
import org.springframework.context.ApplicationListener;
import org.springframework.core.Ordered;
import org.springframework.core.env.MapPropertySource;
import org.springframework.core.env.PropertySource;
import org.springframework.web.client.RestTemplate;
import java.util.HashMap;
import java.util.Map;
public class EnvironmentPrepareListener implements ApplicationListener<ApplicationEnvironmentPreparedEvent>, Ordered {
private final RestTemplate restTemplate = new RestTemplate();
@Override
public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {
// Only focused main application.properties (or yml) configuration
// Loop through sources to figure out name
final String propertySourceName = "applicationConfig: [classpath:/application.properties]";
PropertySource<?> propertySource = event.getEnvironment().getPropertySources()
.get(propertySourceName);
Map<String, Object> source = ((OriginTrackedMapPropertySource) propertySource).getSource();
Map<String, Object> myUpdatedProps = new HashMap<>();
final String url = "https://jsonplaceholder.typicode.com/todos/1";
for (Map.Entry<String, Object> entry : source.entrySet()) {
if (isDynamic(entry.getValue())) {
String updatedValue = restTemplate.getForEntity(url, String.class).getBody();
myUpdatedProps.put(entry.getKey(), updatedValue);
}
}
if (!myUpdatedProps.isEmpty()) {
event.getEnvironment().getPropertySources()
.addBefore(
propertySourceName,
new MapPropertySource("myUpdatedProps", myUpdatedProps)
);
}
}
private boolean isDynamic(Object value) {
return StringUtils.startsWith(value.toString(), "<<")
&& StringUtils.endsWith(value.toString(), ">>");
}
@Override
public int getOrder() {
return Ordered.LOWEST_PRECEDENCE;
}
}
点击 /test
会得到我:
{ "userId": 1, "id": 1, "title": "delectus aut autem", "completed": false }
我最后做了一些改动以标记特殊属性。然后我创建了自己的 PropertySource
,就像@Andreas 建议的那样。全部灵感来自 org.springframework.boot.env.RandomValuePropertySource
诀窍是将特殊字符 <<
和 >>
更改为 spring 已经使用的语法:${}
,但就像使用 ${random.int
} 我做了类似 ${rest.XXX}
的事情。我之前不知道的是,通过这样做,Spring 将使用来自占位符值的新 属性 名称第二次调用所有 属性 源(rest.XXX
在我之前的例子中)。如果 属性 的名称以我的前缀 rest.
开头,那么在 属性 源中我可以处理该值
这是我的解决方案的简化版本
public class MyPropertySource extends PropertySource<RestTemplate> {
private static final String PREFIX = "rest.";
public MyPropertySource() {
super(MyPropertySource.class.getSimpleName());
}
@Override
public Object getProperty(@Nonnull String name) {
String result = null;
if (name.startsWith(PREFIX)) {
result = getValueFromRest(name.substring(PREFIX.length()));
}
return result;
}
}
最后,为了注册 属性 来源,我使用 EnvironmentPostProcessor
作为 described here。我找不到不需要维护新文件的更简单方法 META-INF/spring.factories
我有一个 Spring 启动配置 YAML,类似
spring:
application:
name: my-app
a: a literal
b: <<external due to special first and last chars>>
我想做的是添加某种解析器,它将检测 b
的值是否为 <<X>>
的形式,并将触发从外部 rest 检索该值api 在 YAML 传递给运行时保存配置的 bean 之前在内存中覆盖 YAML 中的值
我尝试使用 EnvironmentPostProcessor
但失败了,因为我无法获得实际的 属性 值,只有 属性 sources,所以我无法 post- 处理这些值。
目前对我有用的是 @Configuration
bean,它包含字段 a
和 b
,在设置器中实现一些东西来检测 spring 正在尝试设置以 <<
开始并以 >>
结束,如果是这样,用我从其余 api 检索到的版本覆盖加载到 pojo 中的内容。这并不理想,因为我最终得到了很多重复
在 Spring 5 中实现类似功能的正确方法是什么?我知道 spring 属性支持使用语法 ${a}
对其他属性的引用,因此必须有一些机制已经允许添加自定义占位符解析器
不知道正确的方法,但是从 REST 调用获取属性的一种方法是实现您自己的 PropertySource
,它获取(和缓存?)具体命名的属性的值。
这是我使用 Spring Boot 2.1.5 想出的一个 hacky 解决方案。 可能最好使用自定义 PropertyResolver
基本上它是这样的:
- 抓住我关心的
PropertySource
。对于这种情况,它是application.properties
。应用程序可以有N
个来源,因此如果还有其他地方可能会出现<< >>
,那么您也需要检查它们。 - 循环遍历
<< >>
的源值
- 如果匹配则动态替换该值。
我的属性是:
a=hello from a
b=<<I need special attention>>
我被黑的 ApplicationListener
是:
import org.apache.commons.lang3.StringUtils;
import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent;
import org.springframework.boot.env.OriginTrackedMapPropertySource;
import org.springframework.context.ApplicationListener;
import org.springframework.core.Ordered;
import org.springframework.core.env.MapPropertySource;
import org.springframework.core.env.PropertySource;
import org.springframework.web.client.RestTemplate;
import java.util.HashMap;
import java.util.Map;
public class EnvironmentPrepareListener implements ApplicationListener<ApplicationEnvironmentPreparedEvent>, Ordered {
private final RestTemplate restTemplate = new RestTemplate();
@Override
public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {
// Only focused main application.properties (or yml) configuration
// Loop through sources to figure out name
final String propertySourceName = "applicationConfig: [classpath:/application.properties]";
PropertySource<?> propertySource = event.getEnvironment().getPropertySources()
.get(propertySourceName);
Map<String, Object> source = ((OriginTrackedMapPropertySource) propertySource).getSource();
Map<String, Object> myUpdatedProps = new HashMap<>();
final String url = "https://jsonplaceholder.typicode.com/todos/1";
for (Map.Entry<String, Object> entry : source.entrySet()) {
if (isDynamic(entry.getValue())) {
String updatedValue = restTemplate.getForEntity(url, String.class).getBody();
myUpdatedProps.put(entry.getKey(), updatedValue);
}
}
if (!myUpdatedProps.isEmpty()) {
event.getEnvironment().getPropertySources()
.addBefore(
propertySourceName,
new MapPropertySource("myUpdatedProps", myUpdatedProps)
);
}
}
private boolean isDynamic(Object value) {
return StringUtils.startsWith(value.toString(), "<<")
&& StringUtils.endsWith(value.toString(), ">>");
}
@Override
public int getOrder() {
return Ordered.LOWEST_PRECEDENCE;
}
}
点击 /test
会得到我:
{ "userId": 1, "id": 1, "title": "delectus aut autem", "completed": false }
我最后做了一些改动以标记特殊属性。然后我创建了自己的 PropertySource
,就像@Andreas 建议的那样。全部灵感来自 org.springframework.boot.env.RandomValuePropertySource
诀窍是将特殊字符 <<
和 >>
更改为 spring 已经使用的语法:${}
,但就像使用 ${random.int
} 我做了类似 ${rest.XXX}
的事情。我之前不知道的是,通过这样做,Spring 将使用来自占位符值的新 属性 名称第二次调用所有 属性 源(rest.XXX
在我之前的例子中)。如果 属性 的名称以我的前缀 rest.
这是我的解决方案的简化版本
public class MyPropertySource extends PropertySource<RestTemplate> {
private static final String PREFIX = "rest.";
public MyPropertySource() {
super(MyPropertySource.class.getSimpleName());
}
@Override
public Object getProperty(@Nonnull String name) {
String result = null;
if (name.startsWith(PREFIX)) {
result = getValueFromRest(name.substring(PREFIX.length()));
}
return result;
}
}
最后,为了注册 属性 来源,我使用 EnvironmentPostProcessor
作为 described here。我找不到不需要维护新文件的更简单方法 META-INF/spring.factories