Spring Boot: inietta la mappa da application.yml


99

Ho un'applicazione Spring Boot con quanto segue application.yml, preso fondamentalmente da qui :

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

Posso iniettare valori particolari, ad es

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

Vorrei, tuttavia, iniettare l'intera mappa, ovvero qualcosa del genere:

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

È possibile (o qualcosa di simile)? Ovviamente posso caricare yaml direttamente, ma mi chiedevo se c'è qualcosa già supportato da Spring.

Risposte:


71

Puoi iniettare una mappa utilizzando @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;
        }
    }
}

L'esecuzione di questo con lo yaml nella domanda produce:

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

Ci sono varie opzioni per impostare un prefisso, controllare come vengono gestite le proprietà mancanti, ecc. Vedere javadoc per maggiori informazioni.


Grazie Andy, funziona come previsto. È interessante il fatto che non funzioni senza una classe aggiuntiva, ovvero non è possibile inserire la infomappa all'interno MapBindingSampleper qualche motivo (forse perché viene utilizzata per eseguire l'app in SpringApplication.runchiamata).
levant pied

1
C'è un modo per iniettare una mappa secondaria? Ad esempio iniettare info.buildinvece che infodalla mappa sopra?
levant pied

1
Sì. Imposta il prefisso su @ConfigurationProperties su info e quindi aggiorna Test sostituendo getInfo () con un metodo chiamato getBuild ()
Andy Wilkinson

Bello, grazie Andy, ha funzionato a meraviglia! Un'altra cosa: quando si impostava locations(per ottenere le proprietà da un altro ymlfile invece di quello predefinito application.yml) @ConfigurationProperties, ha funzionato, tranne per il fatto che non ha comportato la sostituzione dei segnaposto. Ad esempio, se avessi project.version=123impostato una proprietà di sistema , l'esempio fornito nella risposta tornerebbe version=123, mentre dopo l'impostazione locationstornerebbe project.version=${project.version}. Sai se c'è una limitazione di qualche tipo qui?
levant pied

Questa è una limitazione. Ho aperto un problema ( github.com/spring-projects/spring-boot/issues/1301 ) per eseguire la sostituzione del segnaposto quando utilizzi una posizione personalizzata
Andy Wilkinson

108

La soluzione di seguito è una scorciatoia per la soluzione di @Andy Wilkinson, tranne per il fatto che non deve utilizzare una classe separata o un @Beanmetodo annotato.

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

}

Possiamo club sia @Valueannotazioni che @ConfigurationProperties, senza problemi. Ma getter e setter sono importanti ed @EnableConfigurationPropertiesè necessario avere la capacità @ConfigurationPropertiesdi funzionare.

Ho provato questa idea dalla soluzione groovy fornita da @Szymon Stepniak, ho pensato che sarebbe stata utile per qualcuno.


11
Grazie! Ho usato lo Spring Boot 1.3.1, nel mio caso ho scoperto che non è necessario@EnableConfigurationProperties
zhuguowei

Ottengo un errore di "costante di carattere non valido" quando utilizzo questa risposta. Puoi modificare: @ConfigurationProperties (prefix = 'input') per utilizzare le virgolette doppie per evitare questo errore.
Anton Rand

10
Buona risposta, ma le annotazioni @Value non sono necessarie.
Robin

3
Invece di scrivere il dummy getter & setter puoi usare le annotazioni Lombok @Setter (AccessLevel.PUBLIC) e @Getter (AccessLevel.PUBLIC)
RiZKiT

Genious. Nota che la configurazione può anche essere annidata: Map <String, Map <String, String >>
Máthé Endre-Botond

16

Oggi mi imbatto nello stesso problema, ma sfortunatamente la soluzione di Andy non ha funzionato per me. In Spring Boot 1.2.1.RELEASE è ancora più semplice, ma devi essere consapevole di alcune cose.

Ecco la parte interessante del mio 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 contiene solo una voce di mappa, il mio obiettivo è fornire una configurazione dinamica per altri provider OAuth. Voglio iniettare questa mappa in un servizio che inizializzerà i servizi in base alla configurazione fornita in questo file yaml. La mia implementazione iniziale era:

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

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

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

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

Dopo aver avviato l'applicazione, la providersmappa in OAuth2ProvidersServicenon è stata inizializzata. Ho provato la soluzione suggerita da Andy, ma non ha funzionato altrettanto bene. Uso Groovy in quell'applicazione , quindi ho deciso di rimuovere privatee lasciare che Groovy generi getter e setter. Quindi il mio codice sembrava così:

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

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

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

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

Dopo quel piccolo cambiamento tutto ha funzionato.

Anche se c'è una cosa che potrebbe valere la pena menzionare. Dopo averlo fatto funzionare, ho deciso di creare questo campo privatee fornire al setter il tipo di argomento diretto nel metodo setter. Purtroppo non funzionerà. Causa org.springframework.beans.NotWritablePropertyExceptioncon il messaggio:

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?

Tienilo a mente se stai usando Groovy nella tua applicazione Spring Boot.


15

Per recuperare la mappa dalla configurazione avrai bisogno della classe di configurazione. L'annotazione @Value non funzionerà, sfortunatamente.

Application.yml

entries:
  map:
     key1: value1
     key2: value2

Classe di configurazione:

@Configuration
@ConfigurationProperties("entries")
@Getter
@Setter
 public static class MyConfig {
     private Map<String, String> map;
 }

testato la soluzione di cui sopra funziona contro la versione 2.1.0
Tugrul ASLAN

6

Soluzione per estrarre la mappa utilizzando @Value dalla proprietà application.yml codificata come multilinea

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 

Qui il valore per la nostra proprietà map "my-map-property-name" è memorizzato in formato JSON all'interno di una stringa e abbiamo ottenuto una multilinea utilizzando \ alla fine della riga

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

}

Altre spiegazioni

  • \ in yaml è usato per spezzare la stringa in più righe

  • \ " è un carattere di escape per" (virgolette) nella stringa yaml

  • {key: value} JSON in yaml che verrà convertito in Map da @Value

  • # {} è l'espressione SpEL e può essere utilizzato in @Value per convertire json int Map o Array / list Reference

Testato in un progetto di avvio di primavera


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
Benvenuto in Stack Overflow! Sebbene questo snippet di codice possa risolvere la domanda, includere una spiegazione aiuta davvero a migliorare la qualità del tuo post. Ricorda che stai rispondendo alla domanda per i lettori in futuro e quelle persone potrebbero non conoscere i motivi del tuo suggerimento sul codice.
Scott Weldon

il collegamento al wiki è comunque prezioso. La spiegazione è su github.com/spring-projects/spring-boot/wiki/…
dschulten

1

Puoi renderlo ancora più semplice, se vuoi evitare strutture extra.

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

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

}

E poi usalo come al solito, ad esempio con un costruttore:

public class Foo {

  private final Map<String, String> serviceMappings;

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

}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.