Spring Boot - đưa bản đồ vào từ application.yml


99

Tôi có một ứng dụng Spring Boot với những thứ sau application.yml- được lấy về cơ bản từ đây :

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

Tôi có thể chèn các giá trị cụ thể, ví dụ:

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

Tuy nhiên, tôi muốn nhập toàn bộ bản đồ, tức là một cái gì đó như thế này:

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

Điều đó (hoặc một cái gì đó tương tự) có thể không? Rõ ràng, tôi có thể tải yaml trực tiếp, nhưng đang tự hỏi liệu có thứ gì đó đã được hỗ trợ bởi Spring hay không.

Câu trả lời:


71

Bạn có thể đưa vào bản đồ bằng cách sử dụng @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;
        }
    }
}

Chạy điều này với yaml trong câu hỏi sẽ tạo ra:

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

Có nhiều tùy chọn khác nhau để đặt tiền tố, kiểm soát cách xử lý các thuộc tính bị thiếu, v.v. Xem javadoc để biết thêm thông tin.


Cảm ơn Andy - điều này hoạt động như mong đợi. Điều thú vị là nó không hoạt động nếu không có lớp bổ sung - tức là bạn không thể đưa infobản đồ vào bên trong MapBindingSamplevì một số lý do (có thể vì nó được sử dụng để chạy ứng dụng trong SpringApplication.runcuộc gọi).
levant Pied

1
Có cách nào để đưa vào một bản đồ phụ không? Ví dụ: tiêm info.buildthay vì infotừ bản đồ trên?
levant Pied

1
Đúng. Đặt tiền tố trên @ConfigurationProperties để thông tin và sau đó cập nhật thử nghiệm thay thế getInfo () với một phương thức có tên getBuild ()
Andy Wilkinson

Rất vui, cảm ơn Andy, đã làm việc như một cái duyên! Một điều nữa - khi cài đặt locations(để lấy các thuộc tính từ ymltệp khác thay vì mặc định application.yml) @ConfigurationProperties, nó hoạt động, ngoại trừ nó không dẫn đến việc thay thế trình giữ chỗ. Ví dụ: nếu bạn đã project.version=123đặt thuộc tính hệ thống , ví dụ bạn đưa ra trong câu trả lời sẽ trả về version=123, trong khi sau khi thiết lập, locationsnó sẽ trả về project.version=${project.version}. Bạn có biết nếu có một số giới hạn ở đây?
levant Pied

Đó là một hạn chế. Tôi đã mở một vấn đề ( github.com/spring-projects/spring-boot/issues/1301 ) để thực hiện thay thế giữ chỗ khi bạn sử dụng một vị trí tuỳ chỉnh
Andy Wilkinson

108

Giải pháp dưới đây là cách viết tắt cho giải pháp của @Andy Wilkinson, ngoại trừ việc nó không phải sử dụng một lớp riêng biệt hoặc trên một @Beanphương thức có chú thích.

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

}

Chúng tôi có thể kết hợp cả @Valuechú thích và @ConfigurationProperties, không có vấn đề gì. Nhưng getters và setters là quan trọng và @EnableConfigurationPropertiesphải có @ConfigurationPropertiesđể hoạt động.

Tôi đã thử ý tưởng này từ giải pháp groovy do @Szymon Stepniak cung cấp, tôi nghĩ rằng nó sẽ hữu ích cho ai đó.


11
cảm ơn! Tôi đã qua sử dụng khởi động mùa xuân 1.3.1, trong trường hợp của tôi, tôi thấy không cần@EnableConfigurationProperties
zhuguowei

Tôi gặp lỗi 'hằng số ký tự không hợp lệ' khi sử dụng câu trả lời này. Bạn có thể thay đổi: @ConfigurationProperties (prefix = 'input') để sử dụng dấu ngoặc kép để tránh lỗi này.
Anton Rand

10
Câu trả lời tốt, nhưng chú thích @Value là không cần thiết.
Robin

3
Thay vì viết các getter giả & setter bạn có thể sử dụng Lombok chú thích @Setter (AccessLevel.PUBLIC) và @Getter (AccessLevel.PUBLIC)
RiZKiT

Quý tộc. Lưu ý rằng cấu hình cũng có thể được lồng vào nhau: Bản đồ <Chuỗi, Bản đồ <Chuỗi, Chuỗi >>
Máthé Endre-Botond

16

Tôi gặp phải vấn đề tương tự ngày hôm nay, nhưng tiếc là giải pháp của Andy không phù hợp với tôi. Trong Spring Boot 1.2.1.RELEASE thậm chí còn dễ dàng hơn, nhưng bạn phải lưu ý một số điều.

Đây là phần thú vị từ của tôi application.yml:

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

providersbản đồ chỉ chứa một mục nhập bản đồ, mục tiêu của tôi là cung cấp cấu hình động cho các nhà cung cấp OAuth khác. Tôi muốn đưa bản đồ này vào một dịch vụ sẽ khởi tạo các dịch vụ dựa trên cấu hình được cung cấp trong tệp yaml này. Cách triển khai ban đầu của tôi là:

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

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

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

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

Sau khi khởi động ứng dụng, providersbản đồ trong OAuth2ProvidersServicekhông được khởi tạo. Tôi đã thử giải pháp do Andy đề xuất, nhưng nó không hiệu quả. Tôi sử dụng Groovy trong ứng dụng đó, vì vậy tôi quyết định xóa privatevà để Groovy tạo getter và setter. Vì vậy, mã của tôi trông như thế này:

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

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

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

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

Sau thay đổi nhỏ đó, mọi thứ đều hoạt động.

Mặc dù có một điều có thể đáng nói. Sau khi làm cho nó hoạt động, tôi quyết định tạo trường này privatevà cung cấp setter với kiểu đối số thẳng trong phương thức setter. Thật không may, nó sẽ không làm việc đó. Nó gây ra org.springframework.beans.NotWritablePropertyExceptionvới thông báo:

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?

Hãy ghi nhớ điều này nếu bạn đang sử dụng Groovy trong ứng dụng Spring Boot của mình.


14

Để lấy bản đồ từ cấu hình, bạn sẽ cần lớp cấu hình. Rất tiếc, chú thích giá trị sẽ không thực hiện được thủ thuật này.

Application.yml

entries:
  map:
     key1: value1
     key2: value2

Lớp cấu hình:

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

đã thử nghiệm giải pháp trên hoạt động dựa trên phiên bản 2.1.0
Tugrul ASLAN

6

Giải pháp kéo Bản đồ bằng cách sử dụng @Value từ thuộc tính application.yml được mã hóa là đa dòng

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 

Ở đây, giá trị cho thuộc tính bản đồ của chúng tôi "my-map-property-name" được lưu trữ ở định dạng JSON bên trong một chuỗi và chúng tôi đã tìm được nhiều dòng bằng cách sử dụng \ 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...
    } }

}

Giải thích thêm

  • \ in yaml nó được sử dụng để ngắt chuỗi thành nhiều dòng

  • \ " là bộ giải mã thoát cho" (trích dẫn) trong chuỗi yaml

  • {key: value} JSON trong yaml sẽ được chuyển đổi thành Map bởi @Value

  • # {} nó là SpEL expresion và có thể được sử dụng trong @Value để chuyển đổi json int Map hoặc Array / list Reference

Đã thử nghiệm trong một dự án khởi động mùa xuân


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
Chào mừng bạn đến với Stack Overflow! Mặc dù đoạn mã này có thể giải quyết câu hỏi, bao gồm một lời giải thích thực sự giúp cải thiện chất lượng bài đăng của bạn. Hãy nhớ rằng bạn đang trả lời câu hỏi cho người đọc trong tương lai và những người đó có thể không biết lý do cho đề xuất mã của bạn.
Scott Weldon

Tuy nhiên, liên kết đến wiki có giá trị. Lời giải thích có tại github.com/spring-projects/spring-boot/wiki/…
dschulten

0

Bạn có thể làm cho nó đơn giản hơn nữa, nếu bạn muốn tránh các cấu trúc thừa.

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

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

}

Và sau đó sử dụng nó như bình thường, ví dụ với một hàm tạo:

public class Foo {

  private final Map<String, String> serviceMappings;

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

}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.