Постобработка свойств YAML в Spring на основе префикса - PullRequest
0 голосов
/ 14 июня 2019

У меня есть весенняя конфигурация загрузки YAML с чем-то вроде

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

Что я пытаюсь сделать, это добавить какой-то распознаватель, который обнаружит, что значение b имеет вид<<X>> и будет инициировать извлечение этого значения из внешнего API-интерфейса rest, чтобы перезаписать в памяти значение, которое было в YAML, до того, как оно будет передано в bean-компонент, содержащий конфигурации во время выполнения

.EnvironmentPostProcessor, поскольку я не могу получить фактические значения свойств, просто свойство sources , поэтому я не могу постобработать значения.

В настоящее время у меня работает bean-компонент @Configuration, который содержит поля a и b, реализуйте что-то в установщиках, чтобы определить, начинается ли значение, которое пытается установить пружина, с << и заканчивается >>, и если это так, перезаписать то, что загружается в pojo, версией, которую я получаю из остальных API.Это не идеально, потому что я получаю много дубликатов

Как правильно реализовать что-то подобное в Spring 5?Я знаю, что свойства Spring поддерживают ссылки на другие свойства с использованием синтаксиса ${a}, поэтому должен существовать какой-то механизм, который уже позволяет добавлять пользовательские преобразователи заполнителей

Ответы [ 3 ]

0 голосов
/ 14 июня 2019

Вот хакерское решение, которое я придумал, используя Spring Boot 2.1.5.Вероятно, лучше использовать пользовательский 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 }
0 голосов
/ 15 июня 2019

Я закончил тем, что изменил немного, чтобы отметить специальные свойства.Затем я создал свой собственный PropertySource вид, подобный предложенному @Andreas.Все это было навеяно org.springframework.boot.env.RandomValuePropertySource

Хитрость заключалась в том, чтобы изменить специальные символы << и >> на синтаксис, уже использованный весной: ${}, но, как случайный преобразователь, который использует ${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 как , описанный здесь .Я не мог найти более простой способ, который не влечет за собой сохранение нового файла META-INF/spring.factories

0 голосов
/ 14 июня 2019

Не знаю о правильном способе, но один из способов получить свойства из вызова REST - реализовать свой собственный PropertySource, который получает (и кэширует?) Значения специально названных свойств.

...