Spring Boot - injecter la carte depuis application.yml

99

J'ai une application Spring Boot avec les éléments suivants application.yml- tirés essentiellement d' ici :

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

Je peux injecter des valeurs particulières, par exemple

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

Je voudrais cependant injecter toute la carte, c'est-à-dire quelque chose comme ceci:

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

Est-ce possible (ou quelque chose de similaire)? Évidemment, je peux charger yaml directement, mais je me demandais s'il y avait quelque chose déjà pris en charge par Spring.

pied levant
la source

Réponses:

71

Vous pouvez avoir une carte injectée en utilisant @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'exécuter avec le yaml dans la question produit:

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

Il existe différentes options pour définir un préfixe, contrôler la manière dont les propriétés manquantes sont gérées, etc. Voir la javadoc pour plus d'informations.

Andy Wilkinson
la source
Merci Andy - cela fonctionne comme prévu. Intéressant que cela ne fonctionne pas sans une classe supplémentaire - c'est-à-dire que vous ne pouvez pas mettre la infocarte à l'intérieur MapBindingSamplepour une raison quelconque (peut-être parce qu'elle est utilisée pour exécuter l'application en SpringApplication.runappel).
levant pied
1
Existe-t-il un moyen d'injecter une sous-carte? Par exemple, injecter info.buildau lieu de infola carte ci-dessus?
levant pied
1
Oui. Définissez le préfixe sur @ConfigurationProperties sur info, puis mettez à jour Test en remplaçant getInfo () par une méthode nommée getBuild ()
Andy Wilkinson
Bien, merci Andy, a travaillé comme un charme! Une dernière chose - lors de la configuration locations(pour obtenir les propriétés d'un autre ymlfichier au lieu de la valeur par défaut application.yml) @ConfigurationProperties, cela a fonctionné, sauf que cela n'a pas entraîné le remplacement des espaces réservés. Par exemple, si vous aviez un project.version=123ensemble de propriétés système , l'exemple que vous avez donné dans la réponse reviendrait version=123, tandis qu'après la définition, locationsil reviendrait project.version=${project.version}. Savez-vous s'il y a une limitation de quelque sorte ici?
levant pied
C'est une limitation. J'ai ouvert un problème ( github.com/spring-projects/spring-boot/issues/1301 ) pour effectuer un remplacement d'espace réservé lorsque vous utilisez un emplacement personnalisé
Andy Wilkinson
109

La solution ci-dessous est un raccourci pour la solution de @Andy Wilkinson, sauf qu'elle n'a pas besoin d'utiliser une classe distincte ou une @Beanméthode annotée.

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

}

Nous pouvons club à la fois l' @Valueannotation et @ConfigurationProperties, aucun problème. Mais les getters et les setters sont importants et @EnableConfigurationPropertiessont indispensables pour @ConfigurationPropertiespouvoir travailler.

J'ai essayé cette idée de la solution groovy fournie par @Szymon Stepniak, j'ai pensé qu'elle serait utile pour quelqu'un.

raksja
la source
11
Merci! J'ai utilisé la botte de printemps 1.3.1, dans mon cas, j'ai trouvé que je n'ai pas besoin@EnableConfigurationProperties
zhuguowei
J'obtiens une erreur «constante de caractère invalide» lorsque j'utilise cette réponse. Pouvez-vous changer: @ConfigurationProperties (prefix = 'input') pour utiliser des guillemets doubles pour éviter cette erreur.
Anton Rand
10
Bonne réponse, mais les annotations @Value ne sont pas nécessaires.
Robin
3
Au lieu d'écrire le getter et le setter factices, vous pouvez utiliser les annotations Lombok @Setter (AccessLevel.PUBLIC) et @Getter (AccessLevel.PUBLIC)
RiZKiT
Génieux. Notez que la configuration peut également être imbriquée: Map <String, Map <String, String >>
Máthé Endre-Botond
16

Je rencontre le même problème aujourd'hui, mais malheureusement, la solution d'Andy n'a pas fonctionné pour moi. Dans Spring Boot 1.2.1.RELEASE, c'est encore plus facile, mais vous devez être conscient de certaines choses.

Voici la partie intéressante de mon 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 ne contient qu'une seule entrée de carte, mon objectif est de fournir une configuration dynamique pour d'autres fournisseurs OAuth. Je souhaite injecter cette carte dans un service qui initialisera les services en fonction de la configuration fournie dans ce fichier yaml. Ma mise en œuvre initiale était:

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

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

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

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

Après le démarrage de l'application, la providerscarte dans OAuth2ProvidersServicen'a pas été initialisée. J'ai essayé la solution suggérée par Andy, mais elle n'a pas fonctionné aussi bien. J'utilise Groovy dans cette application, j'ai donc décidé de supprimer privateet de laisser Groovy générer getter et setter. Donc mon code ressemblait à ceci:

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

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

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

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

Après ce petit changement, tout a fonctionné.

Bien qu'il y ait une chose qui mérite d'être mentionnée. Après l'avoir fait fonctionner, j'ai décidé de créer ce champ privateet de fournir au setter un type d'argument simple dans la méthode setter. Malheureusement, cela ne fonctionnera pas. Cela provoque org.springframework.beans.NotWritablePropertyExceptionavec message:

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?

Gardez cela à l'esprit si vous utilisez Groovy dans votre application Spring Boot.

Szymon Stepniak
la source
15

Pour récupérer la carte de la configuration, vous aurez besoin de la classe de configuration. L'annotation @Value ne fera pas l'affaire, malheureusement.

Application.yml

entries:
  map:
     key1: value1
     key2: value2

Classe de configuration:

@Configuration
@ConfigurationProperties("entries")
@Getter
@Setter
 public static class MyConfig {
     private Map<String, String> map;
 }
Orbite
la source
testé la solution ci-dessus fonctionne avec la version 2.1.0
Tugrul ASLAN
6

Solution pour extraire Map en utilisant @Value à partir de la propriété application.yml codée comme multiligne

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 

Ici, la valeur de notre propriété de carte "my-map-property-name" est stockée au format JSON dans une chaîne et nous avons obtenu une multiligne en utilisant \ à la fin de la ligne

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

}

Plus d'explications

  • \ en yaml, il est utilisé pour diviser la chaîne en multiligne

  • \ " est le caractère d'échappement pour" (guillemet) dans la chaîne yaml

  • {key: value} JSON en yaml qui sera converti en Map par @Value

  • # {} il s'agit d'une expression SpEL et peut être utilisé dans @Value pour convertir json int Map ou Array / list Reference

Testé dans un projet Spring Boot

Milan
la source
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
la source
7
Bienvenue dans Stack Overflow! Bien que cet extrait de code puisse résoudre la question, inclure une explication contribue vraiment à améliorer la qualité de votre message. N'oubliez pas que vous répondez à la question aux lecteurs à l'avenir, et que ces personnes pourraient ne pas connaître les raisons de votre suggestion de code.
Scott Weldon
le lien vers le wiki est cependant précieux. L'explication est sur github.com/spring-projects/spring-boot/wiki
...
1

Vous pouvez le rendre encore plus simple si vous souhaitez éviter les structures supplémentaires.

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

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

}

Et puis utilisez-le comme d'habitude, par exemple avec un constructeur:

public class Foo {

  private final Map<String, String> serviceMappings;

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

}
Alexandre Korolev
la source