Spring Boot - application.yml'den haritayı enjekte edin


99

Aşağıdakileri içeren bir Spring Boot uygulamam var application.yml- temelde buradan alınmıştır :

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

Belirli değerleri enjekte edebilirim, örneğin

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

Bununla birlikte, tüm haritayı enjekte etmek istiyorum, yani şuna benzer bir şey:

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

Bu (veya benzer bir şey) mümkün mü? Açıkçası, yaml'yi doğrudan yükleyebilirim, ancak Bahar tarafından zaten desteklenen bir şey olup olmadığını merak ediyordum.

Yanıtlar:


71

Aşağıdakileri kullanarak bir harita enjekte edebilirsiniz @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;
        }
    }
}

Bunu sorudaki yaml ile çalıştırmak şunları üretir:

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

Bir önek ayarlamak, eksik özelliklerin nasıl işlendiğini kontrol etmek vb. İçin çeşitli seçenekler vardır. Daha fazla bilgi için javadoc'a bakın .


Teşekkürler Andy - bu beklendiği gibi çalışıyor. Fazladan bir sınıf olmadan çalışmaması ilginç - yani infoharitayı MapBindingSampleherhangi bir nedenle içine koyamıyorsunuz (belki de uygulamayı SpringApplication.runarama sırasında çalıştırmak için kullanıldığı için ).
levant alaca

1
Bir alt harita enjekte etmenin bir yolu var mı? Örneğin yukarıdaki harita info.buildyerine enjekte etmek info?
levant pied

1
Evet. @ConfigurationProperties'deki öneki bilgi olarak ayarlayın ve ardından Test'i getInfo () 'yu getBuild () adlı bir yöntemle değiştirerek güncelleyin
Andy Wilkinson

Güzel, teşekkürler Andy, harika çalıştı! Bir şey daha - locations(özellikleri ymlvarsayılan yerine başka bir dosyadan almak için application.yml) açıkken @ConfigurationProperties, yer tutucuların değiştirilmesine neden olmaması dışında işe yaradı. Örneğin, bir sistem özelliği project.version=123ayarladıysanız, yanıtta verdiğiniz örnek geri dönerken version=123, ayarladıktan sonra locationsgeri dönecektir project.version=${project.version}. Burada bazı türlerde bir sınırlama olup olmadığını biliyor musunuz?
levant alaca

Bu bir sınırlama. Özel bir konum kullandığınızda yer tutucu değiştirmeyi gerçekleştirmek için bir sorun ( github.com/spring-projects/spring-boot/issues/1301 ) açtım
Andy Wilkinson

109

Aşağıdaki çözüm, ayrı bir sınıf veya @Beanaçıklamalı bir yöntem kullanmak zorunda olmaması dışında @Andy Wilkinson'ın çözümü için bir kısaltmadır .

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

}

Hem @Valueaçıklamayı hem de @ConfigurationPropertiessorun olmadan bir araya getirebiliriz . Ancak alıcılar ve ayarlayıcılar önemlidir ve işe yaraması @EnableConfigurationPropertiesgerekir @ConfigurationProperties.

Bu fikri @Szymon Stepniak tarafından sağlanan harika çözümden denedim, biri için yararlı olacağını düşündüm.


11
Teşekkürler! 1.3.1 yaylı önyükleme kullandım, benim durumumda ihtiyacım olmadığını buldum@EnableConfigurationProperties
zhuguowei

Bu yanıtı kullanırken 'geçersiz karakter sabiti' hatası alıyorum. Bu hatayı önlemek için çift tırnak kullanmak için @ConfigurationProperties (prefix = 'input') değiştirebilir misiniz?
Anton Rand

10
İyi yanıt, ancak @ Değer ek açıklamaları gerekli değildir.
Robin

3
Sahte alıcı ve ayarlayıcı yazmak yerine Lombok açıklamalarını @Setter (AccessLevel.PUBLIC) ve @Getter (AccessLevel.PUBLIC)
RiZKiT

Dahice. Yapılandırmanın iç içe de olabileceğini unutmayın: Map <String, Map <String, String >>
Máthé Endre-Botond

16

Bugün de aynı problemle karşılaşıyorum ama maalesef Andy'nin çözümü bende işe yaramadı. Spring Boot 1.2.1.RELEASE'de bu daha da kolay, ancak birkaç şeyin farkında olmalısınız.

İşte benim ilginç kısım application.yml:

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

providersharita yalnızca bir harita girişi içeriyor, amacım diğer OAuth sağlayıcıları için dinamik yapılandırma sağlamak. Bu haritayı, bu yaml dosyasında sağlanan yapılandırmaya göre hizmetleri başlatacak bir hizmete enjekte etmek istiyorum. İlk uygulamam şuydu:

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

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

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

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

Uygulamayı başlattıktan sonra, providersharita OAuth2ProvidersServicebaşlatılmadı. Andy'nin önerdiği çözümü denedim ama işe yaramadı. O uygulamada Groovy kullanıyorum , bu yüzden kaldırmaya privateve Groovy'nin alıcı ve ayarlayıcı oluşturmasına izin vermeye karar verdim . Yani kodum şuna benziyordu:

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

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

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

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

Bu küçük değişiklikten sonra her şey çalıştı.

Yine de bahsetmeye değer bir şey var. Çalışmasını sağladıktan sonra, bu alanı yapmaya privateve setter yönteminde setter'a düz argüman türü sağlamaya karar verdim . Ne yazık ki işe yaramayacak. Mesajla neden olur 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?

Spring Boot uygulamanızda Groovy kullanıyorsanız bunu aklınızda bulundurun.


15

Haritayı konfigürasyondan almak için konfigürasyon sınıfına ihtiyacınız olacaktır. @ Değer ek açıklaması maalesef işe yaramayacak.

Application.yml

entries:
  map:
     key1: value1
     key2: value2

Yapılandırma sınıfı:

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

Yukarıdaki çözümün 2.1.0 sürümüne karşı çalıştığı test edildi
Tugrul ASLAN

6

Çekmek için Çözüm Haritası kullanarak @value gelen application.yml özellik olarak kodlanmış satırlı

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 

Burada "my-map-property-name" harita özelliğimizin değeri bir dizge içinde JSON biçiminde saklanır ve satırın sonunda \ kullanarak çok satırlı elde ettik

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

}

Daha fazla açıklama

  • \ yaml'de dizeyi çok satırlıya bölmek için kullanılır

  • yaml dizesindeki (alıntı) için \ " kaçış karakteridir"

  • Yaml dilinde {anahtar: değer} JSON, @ Değer tarafından Haritaya dönüştürülecek

  • # {} SpEL ifadesidir ve @Value içinde json int Map veya Array / list Reference'ı dönüştürmek için kullanılabilir

Bir yay önyükleme projesinde test edilmiştir


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
Stack Overflow'a hoş geldiniz! Bu kod parçacığı soruyu çözebilirken, bir açıklama eklemek, yayınınızın kalitesini artırmaya gerçekten yardımcı olur. Gelecekte okuyucular için soruyu yanıtladığınızı ve bu kişilerin kod önerinizin nedenlerini bilmeyebileceklerini unutmayın.
Scott Weldon

wiki bağlantısı değerlidir. Açıklama github.com/spring-projects/spring-boot/wiki/… adresinde
dschulten

1

Ekstra yapılardan kaçınmak istiyorsanız bunu daha da basitleştirebilirsiniz.

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

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

}

Ve sonra bunu her zamanki gibi kullanın, örneğin bir yapıcıyla:

public class Foo {

  private final Map<String, String> serviceMappings;

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

}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.