Spring Boot - ฉีดแผนที่จาก application.yml


103

ฉันมีแอปพลิเคชั่นSpring Bootดังต่อไปนี้application.yml- โดยพื้นฐานแล้วจากที่นี่ :

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

ฉันสามารถฉีดค่าเฉพาะเช่น

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

อย่างไรก็ตามฉันต้องการฉีดแผนที่ทั้งหมดเช่นสิ่งนี้:

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

เป็นไปได้ (หรือสิ่งที่คล้ายกัน)? เห็นได้ชัดว่าฉันสามารถโหลด yaml ได้โดยตรง แต่สงสัยว่า Spring มีบางอย่างรองรับหรือไม่

คำตอบ:


71

คุณสามารถฉีดแผนที่โดยใช้@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;
        }
    }
}

การรันสิ่งนี้ด้วย yaml ในคำถามก่อให้เกิด:

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

มีตัวเลือกมากมายสำหรับการตั้งค่าคำนำหน้าควบคุมวิธีจัดการคุณสมบัติที่ขาดหายไป ฯลฯ ดูjavadocสำหรับข้อมูลเพิ่มเติม


ขอบคุณแอนดี้ - ทำงานได้ตามที่คาดไว้ น่าสนใจที่มันใช้ไม่ได้หากไม่มีคลาสเสริมนั่นคือคุณไม่สามารถinfoวางแผนที่ไว้ภายในได้MapBindingSampleด้วยเหตุผลบางประการ (อาจเป็นเพราะมันถูกใช้เพื่อเรียกใช้แอพในการSpringApplication.runโทร)
levant pied

1
มีวิธีฉีดซับแมพหรือไม่? เช่นฉีดinfo.buildแทนinfoจากแผนที่ด้านบน?
levant pied

1
ใช่. ตั้งค่าคำนำหน้าใน @ConfigurationProperties เป็นข้อมูลจากนั้นอัปเดตทดสอบแทนที่ getInfo () ด้วยเมธอดชื่อ getBuild ()
Andy Wilkinson

ดีขอบคุณแอนดี้ทำงานได้อย่างมีเสน่ห์! อีกอย่างหนึ่ง - เมื่อการตั้งค่าlocations(เพื่อรับคุณสมบัติจากymlไฟล์อื่นแทนที่จะเป็นค่าเริ่มต้นapplication.yml) @ConfigurationPropertiesจะใช้งานได้ยกเว้นจะไม่ส่งผลให้ตัวยึดตำแหน่งถูกแทนที่ เช่นถ้าคุณมีคุณสมบัติระบบproject.version=123ชุดตัวอย่างที่คุณให้ในคำตอบที่จะกลับมาversion=123ในขณะที่หลังจากการตั้งค่าก็จะกลับมาlocations project.version=${project.version}คุณทราบหรือไม่ว่ามีข้อ จำกัด บางประเภทที่นี่?
levant pied

นั่นเป็นข้อ จำกัด ฉันได้เปิดปัญหา ( github.com/spring-projects/spring-boot/issues/1301 ) เพื่อทำการแทนที่ตัวยึดตำแหน่งเมื่อคุณใช้ตำแหน่งที่กำหนดเอง
Andy Wilkinson

113

วิธีแก้ปัญหาด้านล่างนี้เป็นชวเลขสำหรับโซลูชันของ @Andy Wilkinson ยกเว้นว่าไม่จำเป็นต้องใช้คลาสแยกต่างหากหรือใช้@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;
    }

}

เราสามารถจับกลุ่มได้ทั้ง@Valueคำอธิบายประกอบและ@ConfigurationPropertiesไม่มีปัญหา แต่ตัวรับและตัวตั้งมีความสำคัญและ@EnableConfigurationPropertiesต้องมีความ@ConfigurationPropertiesสามารถ

ฉันลองใช้แนวคิดนี้จากโซลูชันที่น่าสนใจโดย @Szymon Stepniak คิดว่าจะมีประโยชน์สำหรับใครบางคน


11
ขอบคุณ! ฉันใช้สปริงบูต 1.3.1 ในกรณีของฉันฉันพบว่าไม่ต้องการ@EnableConfigurationProperties
zhuguowei

ฉันได้รับข้อผิดพลาด 'ค่าคงที่ของอักขระไม่ถูกต้อง' เมื่อใช้คำตอบนี้ คุณสามารถเปลี่ยน: @ConfigurationProperties (คำนำหน้า = 'input') เพื่อใช้เครื่องหมายคำพูดคู่เพื่อป้องกันข้อผิดพลาดนี้
Anton Rand

10
คำตอบที่ดี แต่คำอธิบายประกอบ @Value ไม่จำเป็น
Robin

3
แทนที่จะเขียน dummy getter & setter คุณสามารถใช้คำอธิบายประกอบ Lombok @Setter (AccessLevel.PUBLIC) และ @Getter (AccessLevel.PUBLIC)
RiZKiT

ใจดี. โปรดทราบว่าการกำหนดค่าสามารถซ้อนกันได้: Map <String, Map <String, String >>
Máthé Endre-Botond

16

วันนี้ฉันประสบปัญหาเดียวกัน แต่น่าเสียดายที่วิธีแก้ปัญหาของ Andy ไม่ได้ผลสำหรับฉัน ใน Spring Boot 1.2.1 ปล่อยมันง่ายกว่าเดิม แต่คุณต้องระวังบางสิ่ง

นี่คือส่วนที่น่าสนใจจากฉัน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แผนที่มีรายการแผนที่เพียงรายการเดียวเป้าหมายของฉันคือจัดเตรียมการกำหนดค่าแบบไดนามิกสำหรับผู้ให้บริการ OAuth รายอื่น ฉันต้องการแทรกแผนที่นี้ลงในบริการที่จะเริ่มต้นบริการตามการกำหนดค่าที่ให้ไว้ในไฟล์ yaml นี้ การใช้งานครั้งแรกของฉันคือ:

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

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

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

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

หลังจากเริ่มแอปพลิเคชันprovidersแผนที่ในOAuth2ProvidersServiceไม่ได้ถูกเริ่มต้น ฉันลองใช้วิธีแก้ปัญหาที่ Andy แนะนำแล้ว แต่ก็ไม่ได้ผลเช่นกัน ฉันใช้Groovyในแอปพลิเคชันนั้นดังนั้นฉันจึงตัดสินใจลบprivateและปล่อยให้ Groovy สร้าง getter และ setter ดังนั้นรหัสของฉันจึงมีลักษณะดังนี้:

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

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

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

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

หลังจากนั้นการเปลี่ยนแปลงเล็ก ๆ ทุกอย่างก็ใช้ได้ผล

แม้ว่าจะมีสิ่งหนึ่งที่ควรค่าแก่การกล่าวถึง หลังจากที่ฉันทำให้มันใช้งานได้ฉันตัดสินใจที่จะสร้างฟิลด์นี้privateและจัดหา setter ด้วยประเภทอาร์กิวเมนต์แบบตรงในเมธอด setter น่าเสียดายที่มันไม่ได้ผล มันทำให้เกิด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?

โปรดทราบว่าหากคุณใช้ Groovy ในแอปพลิเคชัน Spring Boot


15

ในการดึงแผนที่จากการกำหนดค่าคุณจะต้องมีคลาสการกำหนดค่า คำอธิบายประกอบ @ มูลค่าจะไม่ทำเคล็ดลับน่าเสียดาย

Application.yml

entries:
  map:
     key1: value1
     key2: value2

คลาสการกำหนดค่า:

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

ทดสอบว่าโซลูชันข้างต้นใช้งานได้กับเวอร์ชัน 2.1.0
Tugrul ASLAN

6

โซลูชันสำหรับการดึงMapโดยใช้@Value จาก คุณสมบัติapplication.yml ที่เข้ารหัสเป็น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 

ที่นี่ค่าสำหรับคุณสมบัติแผนที่ของเรา "my-map-property-name" ถูกจัดเก็บในรูปแบบJSONภายในสตริง และเราได้รับหลายบรรทัดโดยใช้\ at end of line

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

}

คำอธิบายเพิ่มเติม

  • \ใน yaml ใช้เพื่อแบ่งสตริงออกเป็นหลายสาย

  • \ "คือ Escape charater สำหรับ" (quote) ในสตริง yaml

  • {key: value} JSON ใน yaml ซึ่งจะถูกแปลงเป็น Map โดย @Value

  • # {}เป็น SpEL expresion และสามารถใช้ใน @Value เพื่อแปลง json int Map หรือ Array / list Reference

ผ่านการทดสอบในโครงการสปริงบูต


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! แม้ว่าข้อมูลโค้ดนี้จะช่วยแก้ปัญหาได้ แต่การมีคำอธิบายจะช่วยปรับปรุงคุณภาพของโพสต์ของคุณได้มาก โปรดจำไว้ว่าคุณกำลังตอบคำถามสำหรับผู้อ่านในอนาคตและบุคคลเหล่านั้นอาจไม่ทราบสาเหตุของการแนะนำโค้ดของคุณ
Scott Weldon

อย่างไรก็ตามลิงก์ไปยัง wiki นั้นมีค่า คำอธิบายอยู่ที่github.com/spring-projects/spring-boot/wiki/…
dschulten

0

คุณสามารถทำให้มันง่ายขึ้นได้หากคุณต้องการหลีกเลี่ยงโครงสร้างพิเศษ

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

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

}

จากนั้นใช้งานตามปกติตัวอย่างเช่นกับตัวสร้าง:

public class Foo {

  private final Map<String, String> serviceMappings;

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

}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.