[英]Post-processing YAML properties in Spring based on prefix
我有一個類似的Spring啟動配置YAML
spring:
application:
name: my-app
a: this is literal
b: <<this is external due to special first and last chars>>
我想要做的是添加某種解析器,它將檢測b
的值是否為<<X>>
形式,並將觸發從外部rest api中檢索該值以在內存中覆蓋該值。在YAML傳遞給在運行時保存配置的bean之前
我嘗試使用EnvironmentPostProcessor
失敗了,因為我無法獲得實際屬性值,只有屬性源 ,因此我無法對值進行后期處理。
目前適用於我的是@Configuration
bean,它包含字段a
和b
,在setter中實現一些東西來檢測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.
開頭,我可以處理該值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
。 我找不到一種更簡單的方法,不需要維護一個新文件META-INF/spring.factories
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.