Spring Boot - Injizieren Sie die Map aus application.yml

99

Ich habe eine Spring Boot- Anwendung mit den folgenden application.yml- im Grunde genommen von hier aus :

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

Ich kann bestimmte Werte injizieren, z

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

Ich möchte jedoch die gesamte Karte einfügen, dh so etwas:

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

Ist das (oder ähnliches) möglich? Natürlich kann ich Yaml direkt laden, habe mich aber gefragt, ob Spring bereits etwas unterstützt.

Levante gescheckt
quelle

Antworten:

71

Sie können eine Karte injizieren lassen mit @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;
        }
    }
}

Wenn Sie dies mit dem Yaml in der Frage ausführen, erhalten Sie Folgendes:

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

Es gibt verschiedene Optionen zum Festlegen eines Präfixes, zum Steuern des Umgangs mit fehlenden Eigenschaften usw. Weitere Informationen finden Sie im Javadoc .

Andy Wilkinson
quelle
Danke Andy - das funktioniert wie erwartet. Interessant, dass es ohne eine zusätzliche Klasse nicht funktioniert - dh Sie können die infoKarte MapBindingSampleaus irgendeinem Grund nicht einfügen (möglicherweise, weil sie zum Ausführen der App im SpringApplication.runAufruf verwendet wird).
Levant gescheckt
1
Gibt es eine Möglichkeit, eine Subkarte einzufügen? ZB spritzen info.buildstatt infovon der obigen Karte?
Levant gescheckt
1
Ja. Setzen Sie das Präfix auf @ConfigurationProperties auf info und aktualisieren Sie dann Test, indem Sie getInfo () durch eine Methode namens getBuild () ersetzen
Andy Wilkinson
Schön, danke Andy, hat wie ein Zauber gewirkt! Eine weitere Sache - beim Aktivieren locations(um die Eigenschaften aus einer anderen ymlDatei anstelle der Standardeinstellung abzurufen application.yml) @ConfigurationPropertiesfunktionierte es, außer dass Platzhalter nicht ersetzt wurden. Wenn Sie beispielsweise eine Systemeigenschaft project.version=123festgelegt hätten, würde das in der Antwort angegebene Beispiel zurückgegeben version=123, während es nach dem Festlegen zurückgegeben locationswürde project.version=${project.version}. Wissen Sie, ob es hier eine Art Einschränkung gibt?
Levant gescheckt
Das ist eine Einschränkung. Ich habe ein Problem geöffnet ( github.com/spring-projects/spring-boot/issues/1301 ), um Platzhalter zu ersetzen, wenn Sie einen benutzerdefinierten Speicherort verwenden
Andy Wilkinson
107

Die folgende Lösung ist eine Abkürzung für die Lösung von @Andy Wilkinson, mit der Ausnahme, dass keine separate Klasse oder eine mit @BeanAnmerkungen versehene Methode verwendet werden muss.

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;
    }

}

Wir können sowohl @ValueAnmerkungen als @ConfigurationPropertiesauch keine Probleme zusammenfassen. Aber Getter und Setter sind wichtig und @EnableConfigurationPropertiesmüssen @ConfigurationPropertiesfunktionieren.

Ich habe diese Idee mit einer großartigen Lösung von @Szymon Stepniak ausprobiert und dachte, sie wäre für jemanden nützlich.

Raksja
quelle
11
Vielen Dank! Ich habe Spring Boot 1.3.1 verwendet, in meinem Fall fand ich nicht@EnableConfigurationProperties
zhuguowei
Bei Verwendung dieser Antwort wird der Fehler "Ungültige Zeichenkonstante" angezeigt. Können Sie Folgendes ändern: @ConfigurationProperties (Präfix = 'Eingabe'), um doppelte Anführungszeichen zu verwenden, um diesen Fehler zu verhindern.
Anton Rand
10
Gute Antwort, aber die @ Value-Anmerkungen sind nicht erforderlich.
Robin
3
Anstatt den Dummy Getter & Setter zu schreiben, können Sie die Lombok-Annotationen @Setter (AccessLevel.PUBLIC) und @Getter (AccessLevel.PUBLIC)
RiZKiT
Genial. Beachten Sie, dass die Konfiguration auch verschachtelt werden kann: Map <String, Map <String, String >>
Máthé Endre-Botond
16

Ich habe heute das gleiche Problem, aber leider hat Andys Lösung bei mir nicht funktioniert. In Spring Boot 1.2.1.RELEASE ist es noch einfacher, aber Sie müssen sich einiger Dinge bewusst sein.

Hier ist der interessante Teil von mir 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 enthält nur einen Karteneintrag. Mein Ziel ist es, eine dynamische Konfiguration für andere OAuth-Anbieter bereitzustellen. Ich möchte diese Zuordnung in einen Dienst einfügen, der Dienste basierend auf der in dieser yaml-Datei bereitgestellten Konfiguration initialisiert. Meine erste Implementierung war:

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

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

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

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

Nach dem Starten der Anwendung wurde die providersZuordnung OAuth2ProvidersServicenicht initialisiert. Ich habe die von Andy vorgeschlagene Lösung ausprobiert, aber sie hat nicht so gut funktioniert. Ich benutze Groovy in dieser Anwendung, also habe ich beschlossen private, Groovy zu entfernen und Getter und Setter generieren zu lassen. Mein Code sah also so aus:

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

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

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

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

Nach dieser kleinen Änderung hat alles funktioniert.

Obwohl es eine Sache gibt, die erwähnenswert sein könnte. Nachdem ich es zum Laufen gebracht hatte, entschied ich mich, dieses Feld zu privateerstellen und dem Setter einen geraden Argumenttyp in der Setter-Methode bereitzustellen. Leider wird das nicht funktionieren. Es verursacht org.springframework.beans.NotWritablePropertyExceptionmit Nachricht:

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?

Denken Sie daran, wenn Sie Groovy in Ihrer Spring Boot-Anwendung verwenden.

Szymon Stepniak
quelle
14

Um die Karte aus der Konfiguration abzurufen, benötigen Sie eine Konfigurationsklasse. @ Value Annotation reicht leider nicht aus.

Application.yml

entries:
  map:
     key1: value1
     key2: value2

Konfigurationsklasse:

@Configuration
@ConfigurationProperties("entries")
@Getter
@Setter
 public static class MyConfig {
     private Map<String, String> map;
 }
Umlaufbahn
quelle
getestet die obige Lösung funktioniert gegen Version 2.1.0
Tugrul ASLAN
6

Lösung zum Abrufen von Map mit @Value aus der Eigenschaft application.yml, die als mehrzeilig codiert ist

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 

Hier wird der Wert für unsere Karteneigenschaft "my-map-property-name" im JSON- Format in einer Zeichenfolge gespeichert, und wir haben eine Mehrzeiligkeit mit \ am Zeilenende erreicht

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...
    } }

}

Weitere Erklärung

  • \ in yaml wird verwendet, um einen String in mehrere Zeilen zu unterteilen

  • " ist Escapezeichen für" (Zitat) in Yaml-String

  • {key: value} JSON in yaml, das von @Value in Map konvertiert wird

  • # {} es ist ein SpEL-Ausdruck und kann in @Value verwendet werden, um json int Map oder Array / list Reference zu konvertieren

Getestet in einem Spring Boot Projekt

Mailand
quelle
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

Emerson Moura
quelle
7
Willkommen bei Stack Overflow! Während dieses Code-Snippet die Frage lösen kann, hilft eine Erklärung wirklich dabei, die Qualität Ihres Beitrags zu verbessern. Denken Sie daran, dass Sie die Frage in Zukunft für Leser beantworten und diese Personen möglicherweise die Gründe für Ihren Codevorschlag nicht kennen.
Scott Weldon
Der Link zum Wiki ist jedoch wertvoll. Die Erklärung ist bei github.com/spring-projects/spring-boot/wiki/…
dschulten
0

Sie können es noch einfacher machen, wenn Sie zusätzliche Strukturen vermeiden möchten.

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

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

}

Und verwenden Sie es dann wie gewohnt, zum Beispiel mit einem Konstruktor:

public class Foo {

  private final Map<String, String> serviceMappings;

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

}
Alexander Korolev
quelle