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

Чи можливо це (чи щось подібне)? Очевидно, я можу завантажити ямл безпосередньо, але мені було цікаво, чи є щось, що вже підтримується 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 на інформацію, а потім оновіть Тест замінивши 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/isissue/1301 ), щоб виконати заміну заповнювачів, коли ви використовуєте спеціальне місце розташування
Енді Вілкінсон,

108

Нижче рішення - це скорочення для рішення @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
Дякую! Я використовував весняний завантажувач 1.3.1, в моєму випадку я виявив, що не потрібен@EnableConfigurationProperties
zhuguowei

Я отримую помилку "недійсна константа символів" при використанні цієї відповіді. Чи можете ви змінити: @ConfigurationProperties (prefix = 'input'), щоб використовувати подвійні лапки, щоб запобігти цій помилці.
Антон Ранд

10
Хороша відповідь, але анотації @Value не потрібні.
Робін

3
Замість написання фіктивного геттера та сеттера ви можете використовувати анотації Lombok @Setter (AccessLevel.PUBLIC) та @Getter (AccessLevel.PUBLIC)
RiZKiT

Геніальний. Зауважте, що конфігурацію можна також вкласти: Карта <Рядок, Карта <Рядок, Рядок >>
Máthé Endre-Botond

16

Я зіткнувся з тією ж проблемою і сьогодні, але, на жаль, рішення Енді не спрацювало для мене. У весняному завантаженні 1.2.1. ЗВІТКА це ще простіше, але ви повинні знати про деякі речі.

Ось цікава частина з мого application.yml:

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

providersкарта містить лише один запис карти, моя мета - надати динамічну конфігурацію для інших постачальників 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і надати сеттеру прямий тип аргументу в методі setter. На жаль, це звичайно не працює. Це викликає 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

Рішення для витягування Map за допомогою @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...
    } }

}

Більше пояснення

  • \ in yaml використовується для розбиття рядка на багаторядкові

  • \ " є символом втечі для" (цитата) у рядку yaml

  • {key: value} JSON у ямлі, який буде перетворено на Map @Value

  • # {} це вираз SpEL і може використовуватися в @Value для перетворення json int Map або масиву / списку посилань

Випробуваний у проекті весняного завантаження


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
Ласкаво просимо до переповнення стека! Хоча цей фрагмент коду може вирішити питання, зокрема пояснення дійсно допомагає покращити якість вашої публікації. Пам'ятайте, що ви відповідаєте на запитання читачів у майбутньому, і ці люди можуть не знати причини вашої пропозиції щодо коду.
Скотт Велдон

Хоча посилання на вікі є цінним. Пояснення знаходиться на 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;
  }

}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.