1

我有一个 Spring Boot 配置 YAML,其中包含类似

spring:
  application:
    name: my-app
a: a literal
b: <<external due to special first and last chars>>

我想要做的是添加某种解析器来检测b表单的值<<X>>,并将触发从外部休息 api 检索该值以在内存中覆盖 YAML 中的值之前它得到传递给在运行时保存配置的 bean

我尝试使用但失败了,EnvironmentPostProcessor因为我无法获得实际的属性值,只有属性,所以我无法对这些值进行后处理。

目前对我@Configuration有用的是包含字段的bean,a并且b在setter中实现一些东西来检测spring试图设置的值是否以开头<<和结尾>>如果是这样,用版本覆盖加载到pojo中的内容我从其余的 api 中检索。这并不理想,因为我最终有很多重复

在 Spring 5 中实现这样的事情的正确方法是什么?我知道 spring 属性支持使用语法对其他属性的引用,${a}因此必须有一些机制已经允许添加自定义占位符解析器

4

3 回答 3

1

我最终改变了一些东西来标记特殊属性。然后我创建了我自己的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,如here所述。我找不到不需要维护新文件的更简单方法META-INF/spring.factories

于 2019-06-14T22:52:59.367 回答
0

这是我使用 Spring Boot 2.1.5 提出的一个 hacky 解决方案。使用自定义PropertyResolver可能更好

本质上它是这样的:

  1. 抓住PropertySource我在乎的。对于这种情况,它是application.properties. 应用程序可以有N多个来源,因此如果还有其他<< >>可能出现的地方,那么您也要检查它们。
  2. 循环遍历源的值<< >>
  3. 如果匹配,则动态替换该值。

我的属性是:

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 }
于 2019-06-14T03:43:47.367 回答
0

不知道正确的方法,但是从 REST 调用中获取属性的一种方法是实现你自己的PropertySource,它获取(并缓存?)特定命名属性的值。

于 2019-06-14T00:31:49.420 回答