2014-07-23 19 views
46

Ho un'applicazione Spring Boot con la seguente application.yml - presa essenzialmente da here:Spring avvio - iniettare mappa da application.yml

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

posso iniettare valori particolari, ad esempio

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

Vorrei, tuttavia, per iniettare l'intera mappa, vale a dire qualcosa di simile:

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

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

risposta

34

Si può avere una mappa iniettato 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 il yaml nella questione produce:

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

Ci sono varie opzioni per impostare un prefisso, che controlla le proprietà come mancanti vengono gestiti, ecc. Vedere javadoc per ulteriori informazioni.

+0

Grazie Andy - questo funziona come previsto. È interessante notare che non funziona senza una classe extra, ovvero non è possibile inserire la mappa 'info' in' MapBindingSample' per qualche motivo (forse perché viene utilizzata per eseguire l'app nella chiamata 'SpringApplication.run'). –

+0

C'è un modo per iniettare una sottomappa? Per esempio. inserire 'info.build' invece di' info' dalla mappa sopra? –

+0

Sì. Imposta il prefisso su @ConfigurationProperties su info e poi aggiorna Test sostituendo getInfo() con un metodo chiamato getBuild() –

12

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 dal 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 

providers mappa contiene una sola voce della mappa, il mio obiettivo è quello di fornire la configurazione dinamica per altri fornitori di 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, providers mappa in OAuth2ProvidersService non è stata inizializzata. Ho provato la soluzione suggerita da Andy, ma non ha funzionato altrettanto bene. Io uso Groovy in quella applicazione, quindi ho deciso di rimuovere private e lasciare che Groovy generi getter e setter. Quindi il mio codice assomigliava a questo:

@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 funzionava.

Anche se c'è una cosa che vale la pena menzionare. Dopo averlo fatto funzionare, ho deciso di creare questo campo private e di fornire setter con tipo di argomento diretto nel metodo setter. Sfortunatamente non funzionerà. Essa provoca org.springframework.beans.NotWritablePropertyException con 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? 

tenere a mente se si sta utilizzando Groovy nell'applicazione Stivale Primavera.

53

Sotto soluzione è una scorciatoia per la soluzione di @Andy Wilkinson, tranne per il fatto che non deve utilizzare una classe separata o un metodo annotato @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; 
    } 

} 

Possiamo squadra sia @Value annotazione e @ConfigurationProperties, nessun problema. Ma getter e setter sono importanti e @EnableConfigurationProperties è necessario per far funzionare il @ConfigurationProperties.

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

+6

grazie! Ho usato spring boot 1.3.1, nel mio caso ho trovato che non è necessario '@ EnableConfigurationProperties' – zhuguowei

+1

Molto utile. Questa è la migliore risposta :) – sustainablepace

+0

Viene visualizzato un errore di 'costante carattere non valido' quando si utilizza questa risposta. È possibile modificare: @ConfigurationProperties (prefisso = 'input') per utilizzare le virgolette per evitare questo errore. –

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

+3

Benvenuti in Stack Overflow! Anche se questo snippet di codice può risolvere la domanda, [inclusa una spiegazione] (// meta.stackexchange.com/questions/114762/explaining-entely-code-based-answers) aiuta davvero a migliorare la qualità del tuo post.Ricorda che stai rispondendo alla domanda per i lettori in futuro, e queste persone potrebbero non conoscere le ragioni del tuo suggerimento sul codice. –

Problemi correlati