Spring Boot - вставить карту из application.yml

99

У меня есть приложение Spring Boot со следующим application.yml- взятым в основном отсюда :

info:
   build:
      artifact: ${project.artifactId}
      name: ${project.name}
      description: ${project.description}
      version: ${project.version}

Я могу ввести определенные значения, например

@Value("${info.build.artifact}") String value

Я бы хотел, однако, внедрить всю карту, то есть примерно так:

@Value("${info}") Map<String, Object> info

Возможно ли это (или что-то подобное)? Очевидно, я могу загрузить yaml напрямую, но мне было интересно, есть ли что-то уже поддерживаемое Spring.

левант пестрый
источник

Ответы:

71

Вы можете ввести карту, используя @ConfigurationProperties:

import java.util.HashMap;
import java.util.Map;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
@EnableAutoConfiguration
@EnableConfigurationProperties
public class MapBindingSample {

    public static void main(String[] args) throws Exception {
        System.out.println(SpringApplication.run(MapBindingSample.class, args)
                .getBean(Test.class).getInfo());
    }

    @Bean
    @ConfigurationProperties
    public Test test() {
        return new Test();
    }

    public static class Test {

        private Map<String, Object> info = new HashMap<String, Object>();

        public Map<String, Object> getInfo() {
            return this.info;
        }
    }
}

Выполнение этого с помощью yaml в вопросе дает:

{build={artifact=${project.artifactId}, version=${project.version}, name=${project.name}, description=${project.description}}}

Существуют различные параметры для установки префикса, управления обработкой отсутствующих свойств и т. Д. Дополнительную информацию см. В документации javadoc .

Энди Уилкинсон
источник
Спасибо, Энди - все работает как положено. Интересно, что он не работает без дополнительного класса - т.е. вы не можете поместить infoкарту внутрь MapBindingSampleпо какой-то причине (возможно, потому, что она используется для запуска приложения в SpringApplication.runвызове).
levant pied
1
Есть ли способ ввести дополнительную карту? Например, ввести info.buildвместо infoкарты выше?
levant pied
1
Да. Установите для префикса @ConfigurationProperties значение info, а затем обновите Test, заменив getInfo () методом с именем getBuild ()
Энди Уилкинсон
Приятно, спасибо Энди, сработало как шарм! Еще одна вещь - при настройке locations(чтобы получить свойства из другого ymlфайла вместо значения по умолчанию application.yml) @ConfigurationPropertiesон работал, за исключением того, что не приводил к замене заполнителей. Например, если у вас было project.version=123задано системное свойство , пример, который вы указали в ответе, вернется version=123, а после установки locationsон вернется project.version=${project.version}. Вы знаете, есть ли здесь какие-то ограничения?
levant pied
Это ограничение. Я открыл проблему ( github.com/spring-projects/spring-boot/issues/1301 ), чтобы выполнить замену заполнителя, когда вы используете настраиваемое местоположение
Энди Уилкинсон
109

Нижеприведенное решение является сокращением для решения @Andy Wilkinson, за исключением того, что оно не должно использовать отдельный класс или @Beanаннотированный метод.

application.yml:

input:
  name: raja
  age: 12
  somedata:
    abcd: 1 
    bcbd: 2
    cdbd: 3

SomeComponent.java:

@Component
@EnableConfigurationProperties
@ConfigurationProperties(prefix = "input")
class SomeComponent {

    @Value("${input.name}")
    private String name;

    @Value("${input.age}")
    private Integer age;

    private HashMap<String, Integer> somedata;

    public HashMap<String, Integer> getSomedata() {
        return somedata;
    }

    public void setSomedata(HashMap<String, Integer> somedata) {
        this.somedata = somedata;
    }

}

Мы можем объединить как @Valueаннотацию, так и @ConfigurationPropertiesбез проблем. Но геттеры и сеттеры важны, и они @EnableConfigurationPropertiesдолжны @ConfigurationPropertiesработать.

Я попробовал эту идею из отличного решения, предоставленного @Szymon Stepniak, подумал, что это будет кому-то полезно.

ракша
источник
11
Спасибо! Я использовал @EnableConfigurationProperties
Spring
При использовании этого ответа я получаю сообщение об ошибке "недопустимая символьная константа". Можете ли вы изменить: @ConfigurationProperties (prefix = 'input'), чтобы использовать двойные кавычки, чтобы предотвратить эту ошибку.
Антон Рэнд
10
Хороший ответ, но аннотации @Value не нужны.
Робин
3
Вместо того, чтобы писать фиктивный метод получения и установки, вы можете использовать аннотации Lombok @Setter (AccessLevel.PUBLIC) и @Getter (AccessLevel.PUBLIC)
RiZKiT
Гениальный. Обратите внимание, что конфигурация также может быть вложенной: Map <String, Map <String, String >>
Máthé Endre-Botond
16

Сегодня я столкнулся с той же проблемой, но, к сожалению, решение Энди у меня не сработало. В Spring Boot 1.2.1.RELEASE это еще проще, но вы должны знать несколько вещей.

Вот интересная часть из моих application.yml:

oauth:
  providers:
    google:
     api: org.scribe.builder.api.Google2Api
     key: api_key
     secret: api_secret
     callback: http://callback.your.host/oauth/google

providersmap содержит только одну запись карты, моя цель - предоставить динамическую конфигурацию для других поставщиков OAuth. Я хочу внедрить эту карту в службу, которая будет инициализировать службы на основе конфигурации, представленной в этом yaml-файле. Моя первоначальная реализация была:

@Service
@ConfigurationProperties(prefix = 'oauth')
class OAuth2ProvidersService implements InitializingBean {

    private Map<String, Map<String, String>> providers = [:]

    @Override
    void afterPropertiesSet() throws Exception {
       initialize()
    }

    private void initialize() {
       //....
    }
}

После запуска приложения providersкарта OAuth2ProvidersServiceне была инициализирована. Я попробовал решение, предложенное Энди, но оно тоже не сработало. Я использую Groovy в этом приложении, поэтому я решил удалить privateи позволить Groovy генерировать методы получения и установки. Итак, мой код выглядел так:

@Service
@ConfigurationProperties(prefix = 'oauth')
class OAuth2ProvidersService implements InitializingBean {

    Map<String, Map<String, String>> providers = [:]

    @Override
    void afterPropertiesSet() throws Exception {
       initialize()
    }

    private void initialize() {
       //....
    }
}

После этого небольшого изменения все заработало.

Хотя есть одна вещь, о которой стоит упомянуть. После того, как я заставил его работать, я решил создать это поле privateи предоставить сеттеру прямой тип аргумента в методе сеттера. К сожалению, это не сработает. Это вызывает org.springframework.beans.NotWritablePropertyExceptionсообщение:

Invalid property 'providers[google]' of bean class [com.zinvoice.user.service.OAuth2ProvidersService]: Cannot access indexed value in property referenced in indexed property path 'providers[google]'; nested exception is org.springframework.beans.NotReadablePropertyException: Invalid property 'providers[google]' of bean class [com.zinvoice.user.service.OAuth2ProvidersService]: Bean property 'providers[google]' is not readable or has an invalid getter method: Does the return type of the getter match the parameter type of the setter?

Имейте это в виду, если вы используете Groovy в своем приложении Spring Boot.

Шимон Степняк
источник
15

Чтобы получить карту из конфигурации, вам понадобится класс конфигурации. К сожалению, аннотация @Value не поможет.

Application.yml

entries:
  map:
     key1: value1
     key2: value2

Класс конфигурации:

@Configuration
@ConfigurationProperties("entries")
@Getter
@Setter
 public static class MyConfig {
     private Map<String, String> map;
 }
Орбит
источник
протестировал вышеуказанное решение, работает с версией 2.1.0
Tugrul ASLAN
6

Решение для извлечения карты с использованием @Value из свойства application.yml, закодированного как многострочный

application.yml

other-prop: just for demo 

my-map-property-name: "{\
         key1: \"ANY String Value here\", \  
         key2: \"any number of items\" , \ 
         key3: \"Note the Last item does not have comma\" \
         }"

other-prop2: just for demo 2 

Здесь значение для нашего свойства карты "my-map-property-name" хранится в формате JSON внутри строки, и мы достигли многострочного значения, используя \ в конце строки.

myJavaClass.java

import org.springframework.beans.factory.annotation.Value;

public class myJavaClass {

@Value("#{${my-map-property-name}}") 
private Map<String,String> myMap;

public void someRandomMethod (){
    if(myMap.containsKey("key1")) {
            //todo...
    } }

}

Больше объяснений

  • \ в yaml используется для разбивки строки на многострочную

  • \ " - escape-символ для" (цитата) в строке yaml

  • {key: value} JSON в yaml, который будет преобразован в карту с помощью @Value

  • # {} это выражение SpEL, которое можно использовать в @Value для преобразования json int Map или Array / list Reference

Протестировано в проекте весенней загрузки

Милан
источник
3
foo.bars.one.counter=1
foo.bars.one.active=false
foo.bars[two].id=IdOfBarWithKeyTwo

public class Foo {

  private Map<String, Bar> bars = new HashMap<>();

  public Map<String, Bar> getBars() { .... }
}

https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-Configuration-Binding

Эмерсон Моура
источник
7
Добро пожаловать в Stack Overflow! Хотя этот фрагмент кода может решить вопрос, включение объяснения действительно помогает улучшить качество вашего сообщения. Помните, что вы отвечаете на вопрос читателей в будущем, и эти люди могут не знать причин вашего предложения кода.
Скотт Велдон
Однако ссылка на вики ценна. Объяснение находится на github.com/spring-projects/spring-boot/wiki/…
dschulten
1

Вы можете сделать его еще проще, если хотите избежать лишних структур.

service:
  mappings:
    key1: value1
    key2: value2
@Configuration
@EnableConfigurationProperties
public class ServiceConfigurationProperties {

  @Bean
  @ConfigurationProperties(prefix = "service.mappings")
  public Map<String, String> serviceMappings() {
    return new HashMap<>();
  }

}

А потом использовать как обычно, например с конструктором:

public class Foo {

  private final Map<String, String> serviceMappings;

  public Foo(Map<String, String> serviceMappings) {
    this.serviceMappings = serviceMappings;
  }

}
Александр Королев
источник