Spring Boot - masukkan peta dari application.yml

99

Saya memiliki aplikasi Spring Boot dengan yang berikut application.yml- pada dasarnya diambil dari sini :

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

Saya dapat memasukkan nilai-nilai tertentu, misalnya

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

Namun, saya ingin memasukkan seluruh peta, yaitu sesuatu seperti ini:

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

Apakah itu (atau yang serupa) mungkin? Jelas, saya dapat memuat yaml secara langsung, tetapi saya bertanya-tanya apakah ada sesuatu yang sudah didukung oleh Spring.

levant pied
sumber

Jawaban:

71

Anda dapat memasukkan peta menggunakan @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;
        }
    }
}

Menjalankan ini dengan yaml di pertanyaan menghasilkan:

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

Ada berbagai opsi untuk menyetel awalan, mengontrol bagaimana properti yang hilang ditangani, dll. Lihat javadoc untuk informasi lebih lanjut.

Andy Wilkinson
sumber
Terima kasih Andy - ini berfungsi seperti yang diharapkan. Menarik bahwa itu tidak berfungsi tanpa kelas tambahan - yaitu Anda tidak dapat meletakkan infopeta di dalamnya MapBindingSamplekarena beberapa alasan (mungkin karena itu digunakan untuk menjalankan aplikasi dalam SpringApplication.runpanggilan).
levant pied
1
Apakah ada cara untuk memasukkan sub-peta? Misalnya menyuntikkan info.buildbukan dari infopeta di atas?
levant pied
1
Iya. Setel awalan pada @ConfigurationProperties ke info dan kemudian perbarui Uji menggantikan getInfo () dengan metode bernama getBuild ()
Andy Wilkinson
Bagus, terima kasih Andy, bekerja dengan sangat baik! Satu hal lagi - saat menyetel locations(untuk mendapatkan properti dari ymlfile lain alih-alih default application.yml) aktif @ConfigurationProperties, itu berfungsi, kecuali itu tidak mengakibatkan placeholder diganti. Misalnya jika Anda memiliki project.version=123set properti sistem , contoh yang Anda berikan pada jawaban akan kembali version=123, sedangkan setelah menyetelnya locationsakan kembali project.version=${project.version}. Tahukah Anda jika ada semacam batasan di sini?
levant pied
Itu batasan. Saya telah membuka masalah ( github.com/spring-projects/spring-boot/issues/1301 ) untuk melakukan penggantian placeholder saat Anda menggunakan lokasi khusus
Andy Wilkinson
108

Solusi di bawah ini adalah singkatan dari solusi @Andy Wilkinson, kecuali bahwa solusi tersebut tidak harus menggunakan kelas terpisah atau @Beanmetode beranotasi.

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

}

Kami dapat menggabungkan @Valueanotasi dan @ConfigurationProperties, tidak ada masalah. Tetapi pengambil dan penyetel adalah penting dan @EnableConfigurationPropertiesharus memiliki @ConfigurationPropertiespekerjaan.

Saya mencoba ide ini dari solusi asyik yang disediakan oleh @Szymon Stepniak, menurut saya akan berguna bagi seseorang.

raksja
sumber
11
Terima kasih! Saya menggunakan boot musim semi 1.3.1, dalam kasus saya, saya menemukan tidak perlu@EnableConfigurationProperties
zhuguowei
Saya mendapatkan kesalahan 'konstanta karakter tidak valid' saat menggunakan jawaban ini. Dapatkah Anda mengubah: @ConfigurationProperties (prefix = 'input') untuk menggunakan tanda kutip ganda untuk mencegah kesalahan ini.
Anton Rand
10
Jawaban bagus, tetapi anotasi @Value tidak diperlukan.
Robin
3
Alih-alih menulis dummy getter & setter, Anda dapat menggunakan anotasi Lombok @Setter (AccessLevel.PUBLIC) dan @Getter (AccessLevel.PUBLIC)
RiZKiT
Jenius. Perhatikan bahwa konfigurasi juga dapat disarangkan: Map <String, Map <String, String >>
Máthé Endre-Botond
16

Saya mengalami masalah yang sama hari ini, tetapi sayangnya solusi Andy tidak berhasil untuk saya. Di Spring Boot 1.2.1. RELEASE bahkan lebih mudah, tetapi Anda harus menyadari beberapa hal.

Inilah bagian yang menarik dari saya 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 hanya berisi satu entri peta, tujuan saya adalah menyediakan konfigurasi dinamis untuk penyedia OAuth lainnya. Saya ingin memasukkan peta ini ke dalam layanan yang akan menginisialisasi layanan berdasarkan konfigurasi yang disediakan dalam file yaml ini. Implementasi awal saya adalah:

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

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

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

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

Setelah memulai aplikasi, providersmap in OAuth2ProvidersServicetidak diinisialisasi. Saya mencoba solusi yang disarankan oleh Andy, tetapi tidak berhasil juga. Saya menggunakan Groovy dalam aplikasi itu, jadi saya memutuskan untuk menghapus privatedan membiarkan Groovy menghasilkan getter dan setter. Jadi kode saya terlihat seperti ini:

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

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

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

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

Setelah perubahan kecil itu semuanya berhasil.

Meski ada satu hal yang mungkin perlu disebutkan. Setelah saya membuatnya bekerja, saya memutuskan untuk membuat bidang ini privatedan menyediakan penyetel dengan tipe argumen langsung dalam metode penyetel. Sayangnya itu tidak akan berhasil. Penyebabnya org.springframework.beans.NotWritablePropertyExceptiondengan pesan:

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?

Ingatlah jika Anda menggunakan Groovy di aplikasi Spring Boot Anda.

Szymon Stepniak
sumber
15

Untuk mengambil peta dari konfigurasi, Anda memerlukan kelas konfigurasi. @Nilai anotasi tidak akan berhasil, sayangnya.

Application.yml

entries:
  map:
     key1: value1
     key2: value2

Kelas konfigurasi:

@Configuration
@ConfigurationProperties("entries")
@Getter
@Setter
 public static class MyConfig {
     private Map<String, String> map;
 }
Orbit
sumber
menguji solusi di atas bekerja terhadap versi 2.1.0
Tugrul ASLAN
6

Solusi untuk menarik Peta menggunakan @Value dari properti application.yml yang dikodekan sebagai multiline

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 

Di sini nilai untuk properti peta kami "my-map-property-name" disimpan dalam format JSON di dalam string dan kami telah mencapai multiline menggunakan \ di akhir baris

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

}

Penjelasan lebih lanjut

  • \ in yaml digunakan untuk memecah string menjadi multiline

  • \ " adalah karakter escape untuk" (kutipan) dalam string yaml

  • {key: value} JSON di yaml yang akan dikonversi ke Map oleh @Value

  • # {} ini adalah ekspresi SpEL dan bisa digunakan dalam @Value untuk mengonversi json int Map atau Array / list Reference

Diuji dalam proyek sepatu bot musim semi

Milan
sumber
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
sumber
7
Selamat datang di Stack Overflow! Meskipun cuplikan kode ini dapat menyelesaikan pertanyaan, menyertakan penjelasan sangat membantu meningkatkan kualitas posting Anda. Ingatlah bahwa Anda menjawab pertanyaan untuk pembaca di masa mendatang, dan orang-orang itu mungkin tidak tahu alasan saran kode Anda.
Scott Weldon
link ke wiki sangat berharga. Penjelasannya ada di github.com/spring-projects/spring-boot/wiki/…
dschulten
1

Anda dapat membuatnya lebih sederhana, jika Anda ingin menghindari struktur tambahan.

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

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

}

Dan kemudian gunakan seperti biasa, misalnya dengan konstruktor:

public class Foo {

  private final Map<String, String> serviceMappings;

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

}
Alexander Korolev
sumber