Spring @PropertySource sử dụng YAML


107

Spring Boot cho phép chúng tôi thay thế các tệp application.properties của chúng tôi bằng các tệp tương đương YAML. Tuy nhiên, tôi dường như gặp khó khăn với các bài kiểm tra của mình. Nếu tôi chú thích của mình TestConfiguration(một cấu hình Java đơn giản), nó đang mong đợi một tệp thuộc tính.

Ví dụ, điều này không hoạt động: @PropertySource(value = "classpath:application-test.yml")

Nếu tôi có cái này trong tệp YAML của mình:

db:
  url: jdbc:oracle:thin:@pathToMyDb
  username: someUser
  password: fakePassword

Và tôi sẽ tận dụng những giá trị đó với một cái gì đó như thế này:

@Value("${db.username}") String username

Tuy nhiên, tôi kết thúc với và lỗi như vậy:

Could not resolve placeholder 'db.username' in string value "${db.username}"

Làm cách nào tôi có thể tận dụng tính tốt của YAML trong các thử nghiệm của mình?


Xác định "không hoạt động". Ngoại lệ / lỗi / cảnh báo là gì?
Emerson Farrugia

Spring Boot làm phẳng tệp YAML để nó xuất hiện dưới dạng tệp thuộc tính với ký hiệu dấu chấm. Điều đó không xảy ra.
rô bốt

Và chỉ để xác nhận, điều này hoạt động trong mã không thử nghiệm?
Emerson Farrugia

1
Đúng. Đây là một tài liệu giải thích về các dự án.spring.io/spring-boot/docs/spring-boot-actuator/… và một cách xuống trang có nội dung 'Lưu ý rằng đối tượng YAML được làm phẳng bằng cách sử dụng dấu phân cách.'
séc

9
SpingBoot cho biết họ không thể tải YAML với PropertySource: 24.6.4 Thiếu sót về YAML Không thể tải tệp YAML thông qua chú thích @PropertySource. Vì vậy, trong trường hợp bạn cần tải các giá trị theo cách đó, bạn cần sử dụng tệp thuộc tính.
Lex Pro

Câu trả lời:


54

Spring-boot có một người trợ giúp cho việc này, chỉ cần thêm

@ContextConfiguration(initializers = ConfigFileApplicationContextInitializer.class)

ở đầu các lớp thử nghiệm của bạn hoặc một lớp cha thử nghiệm trừu tượng.

Chỉnh sửa: Tôi đã viết câu trả lời này năm năm trước. Nó không hoạt động với các phiên bản Spring Boot gần đây. Đây là những gì tôi làm bây giờ (vui lòng dịch Kotlin sang Java nếu cần):

@TestPropertySource(locations=["classpath:application.yml"])
@ContextConfiguration(
        initializers=[ConfigFileApplicationContextInitializer::class]
)

được thêm vào đầu, sau đó

    @Configuration
    open class TestConfig {

        @Bean
        open fun propertiesResolver(): PropertySourcesPlaceholderConfigurer {
            return PropertySourcesPlaceholderConfigurer()
        }
    }

vào ngữ cảnh.


3
đừng quên PropertySourcesPlaceholderConfigurer
Kalpesh Soni

@KalpeshSoni thực sự, nếu không có Configurer, nó sẽ không hoạt động.
Ola Sundell

Tôi đã có thêm initializer để @SpringJunitConfig thay@SpringJUnitConfig(value = {...}, initializers = {ConfigFileApplicationContextInitializer.class})
Tomas F

1
@Jan Galinski, bạn có thể thử câu trả lời của tôi, nó rất dễ sử dụng và nó hoạt động tốt trên env của tôi. stackoverflow.com/questions/21271468/…
Forest

59

Như nó đã được đề cập @PropertySourcekhông tải tệp yaml. Như một giải pháp thay thế, hãy tự tải tệp và thêm các thuộc tính đã tải vào Environment.

Thực hiện ApplicationContextInitializer:

public class YamlFileApplicationContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
  @Override
  public void initialize(ConfigurableApplicationContext applicationContext) {
    try {
        Resource resource = applicationContext.getResource("classpath:file.yml");
        YamlPropertySourceLoader sourceLoader = new YamlPropertySourceLoader();
        PropertySource<?> yamlTestProperties = sourceLoader.load("yamlTestProperties", resource, null);
        applicationContext.getEnvironment().getPropertySources().addFirst(yamlTestProperties);
    } catch (IOException e) {
        throw new RuntimeException(e);
    }
  }
}

Thêm trình khởi tạo của bạn vào thử nghiệm của bạn:

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = Application.class, initializers = YamlFileApplicationContextInitializer.class)
public class SimpleTest {
  @Test
  public test(){
    // test your properties
  }
}

Trên thực tế, đây sẽ là câu trả lời tốt nhất, cảm ơn nó đã hoạt động!
Adelin

Mateusz, tôi đã đăng câu trả lời với YamlFileApplicationContextInitializerlớp nơi vị trí YAML được xác định cho mỗi trường hợp thử nghiệm. Nếu bạn nghĩ nó thú vị, vui lòng kết hợp nó vào câu trả lời của bạn và tôi sẽ xóa của tôi. Chỉ cần cho tôi biết trong một bình luận bên dưới câu trả lời của tôi.
Michal Foksa

Vâng, đây là câu trả lời tốt nhất
Richard HM

34

@PropertySourcecó thể được cấu hình bằng factoryđối số. Vì vậy, bạn có thể làm điều gì đó như:

@PropertySource(value = "classpath:application-test.yml", factory = YamlPropertyLoaderFactory.class)

Trình YamlPropertyLoaderFactorytải thuộc tính tùy chỉnh của bạn ở đâu :

public class YamlPropertyLoaderFactory extends DefaultPropertySourceFactory {
    @Override
    public PropertySource<?> createPropertySource(String name, EncodedResource resource) throws IOException {
        if (resource == null){
            return super.createPropertySource(name, resource);
        }

        return new YamlPropertySourceLoader().load(resource.getResource().getFilename(), resource.getResource(), null);
    }
}

Lấy cảm hứng từ https://stackoverflow.com/a/45882447/4527110


2
Đây cơ bản yaml phân tích cú pháp ném một IllegalStateExceptionkhi các tập tin không tồn tại thay vì thích FileNotFoundException- vì vậy để thực hiện công việc này với @PropertySource(..., ignoreResourceNotFound = true), bạn sẽ cần phải bắt và xử lý các trường hợp này: try { return new YamlPropertySourceLoader().load(resource.getResource().getFilename(), resource.getResource(), null); } catch (IllegalStateException e) { throw (IOException) e.getCause(); }
Christian Opitz

2
Nếu bạn cần lấy thuộc tính cho một cấu hình cụ thể, thì tham số thứ ba trong YamlPropertySourceLoader.load () là tên cấu hình. YamlPropertySourceLoader.load () đã thay đổi để trả về một danh sách thay vì một nguồn thuộc tính. Đây là thông tin khác stackoverflow.com/a/53697551/10668441
pcoates

1
Đây là cách tiếp cận sạch nhất cho đến nay.
Michal Foksa

7
đối với tôi, nó đòi hỏi một sự thay đổi nhỏ để đổi lại như sau:CompositePropertySource propertySource = new CompositePropertySource(name); new YamlPropertySourceLoader().load(resource.getResource().getFilename(), resource.getResource()).stream().forEach(propertySource::addPropertySource); return propertySource;
xorcus

28

@PropertySourcechỉ hỗ trợ các tệp thuộc tính (đó là một hạn chế của Spring, không phải bản thân Boot). Vui lòng mở một phiếu yêu cầu tính năng trong JIRA .


Tôi hy vọng có một cách để sử dụng lại trình nghe yaml hoặc tải yaml theo cách thủ công trong Môi trường có thể được chuyển vào cấu hình thử nghiệm.
séc

10
Tôi cho rằng bạn có thể viết một ApplicationContextInitializervà thêm nó vào cấu hình thử nghiệm (chỉ cần sử dụng một YamlPropertySourceLoaderđể nâng cao Environment). Cá nhân tôi thích nó hơn nếu @PropertySourcesẽ hỗ trợ hành vi này một cách nguyên bản.
Dave Syer

đây vẫn là trường hợp? '@PropertySource' không hỗ trợ YAML?
domi

1
stackoverflow.com/questions/21271468/… sử dụng điều này có thể giải quyết @PropertySource chỉ hỗ trợ các tệp thuộc tính
Forest

Tôi bị sốc khi giải quyết vấn đề của mình với bài đăng 6 năm tuổi này.
Jin Kwon

20

Một tùy chọn khác là đặt spring.config.locationthông qua @TestPropertySource:

@TestPropertySource(properties = { "spring.config.location = classpath:<path-to-your-yml-file>" }

3
Tôi đã tham số hóa đầu vào bằng dòng sau: @TestPropertySource(properties = {"spring.config.location=classpath:application-${test.env}.yml" }) IMO của bạn là câu trả lời tốt nhất trên tất cả.
leventunver

1
Ý tưởng tuyệt vời và rất tối giản cho các bài kiểm tra, cảm ơn rất nhiều! Chỉ cần thêm, người ta có thể bao gồm nhiều file cấu hình, mỗi:@TestPropertySource(properties = {"spring.config.location=classpath:application-config.yml,classpath:test-config.yml,..." })
STX

1
Đây là câu trả lời tốt nhất cho đến nay! lưu ý rằng bạn cần phải có @SpringBootTestchú thích
Mistriel

Nó hoạt động một cách kỳ diệu!
user1079877

19

Từ Spring Boot 1.4, bạn có thể sử dụng @SpringBootTestchú thích mới để đạt được điều này dễ dàng hơn (và để đơn giản hóa thiết lập kiểm tra tích hợp của bạn nói chung) bằng cách khởi động các kiểm tra tích hợp của bạn bằng cách sử dụng hỗ trợ Spring Boot.

Thông tin chi tiết trên Blog mùa xuân .

Theo như tôi có thể nói, điều này có nghĩa là bạn nhận được tất cả lợi ích của tính tốt cấu hình bên ngoài của Spring Boot giống như trong mã sản xuất của bạn, bao gồm tự động chọn cấu hình YAML từ classpath.

Theo mặc định, chú thích này sẽ

... trước tiên hãy thử tải @Configurationtừ bất kỳ lớp bên trong nào và nếu không thành công, nó sẽ tìm kiếm @SpringBootApplicationlớp chính của bạn .

nhưng bạn có thể chỉ định các lớp cấu hình khác nếu được yêu cầu.

Đối với trường hợp cụ thể này, bạn có thể kết hợp @SpringBootTestvới @ActiveProfiles( "test" )và Spring sẽ chọn cấu hình YAML của bạn, miễn là nó tuân theo các tiêu chuẩn đặt tên Khởi động bình thường (tức là application-test.yml).

@RunWith( SpringRunner.class )
@SpringBootTest
@ActiveProfiles( "test" )
public class SpringBootITest {

    @Value("${db.username}")
    private String username;

    @Autowired
    private MyBean myBean;

    ...

}

Lưu ý: SpringRunner.classlà tên mới choSpringJUnit4ClassRunner.class


1
:) Sử dụng @ActiveProfiles là tùy chọn duy nhất hoạt động. Cảm ơn!
zcourts

10

Cách tiếp cận để tải các thuộc tính yaml, IMHO có thể được thực hiện theo hai cách:

a. Bạn có thể đặt cấu hình ở một vị trí tiêu chuẩn - application.ymltrong classpath root - thường src/main/resourcesvà thuộc tính yaml này sẽ tự động được tải bằng Spring boot với tên đường dẫn phẳng mà bạn đã đề cập.

b. Cách tiếp cận thứ hai rộng hơn một chút, về cơ bản xác định một lớp để giữ các thuộc tính của bạn theo cách này:

@ConfigurationProperties(path="classpath:/appprops.yml", name="db")
public class DbProperties {
    private String url;
    private String username;
    private String password;
...
}

Vì vậy, về cơ bản đây là nói rằng tải tệp yaml và điền lớp DbProperties dựa trên phần tử gốc của "db".

Bây giờ để sử dụng nó trong bất kỳ lớp nào, bạn sẽ phải làm điều này:

@EnableConfigurationProperties(DbProperties.class)
public class PropertiesUsingService {

    @Autowired private DbProperties dbProperties;

}

Một trong những cách tiếp cận này sẽ hiệu quả với bạn khi sử dụng Spring-boot.


Đảm bảo rằng bạn có snakeyml trong classpath của mình và phần trên sẽ hoạt động.
hoserdude

3
Những ngày này (mặc dù không phải tại thời điểm câu hỏi này được hỏi), snakeyamlđược đưa vào như một phụ thuộc bắc cầu bởi spring-boot-starter, vì vậy không cần phải thêm nó vào của bạn pom.xmlhoặc build.gradle, trừ khi bạn có nhu cầu sử dụng một phiên bản khác. :)
Steve

2
Nó bây giờ locations, không path, và ConfigFileApplicationContextInitializercũng là bắt buộc.
OrangeDog

3

Tôi đã tìm thấy giải pháp thay thế bằng cách sử dụng @ActiveProfiles("test")và thêm tệp application-test.yml vào src / test / resources.

Nó kết thúc như thế này:

@SpringApplicationConfiguration(classes = Application.class, initializers = ConfigFileApplicationContextInitializer.class)
@ActiveProfiles("test")
public abstract class AbstractIntegrationTest extends AbstractTransactionalJUnit4SpringContextTests {

}

Tệp application-test.yml chỉ chứa các thuộc tính mà tôi muốn ghi đè từ application.yml (có thể tìm thấy trong src / main / resources).


Đây là những gì tôi cũng đang cố gắng sử dụng. Vì một số lý do nó không hoạt động (Spring Boot 1.3.3) khi tôi sử dụng @Value("${my.property}")nhưng nó hoạt động tốt nếu tôi sử dụng environment.getProperty("my.property").
martin-g

1

đó là do bạn chưa cấu hình snakeyml. khởi động mùa xuân đi kèm với tính năng @EnableAutoConfiguration. cũng có cấu hình snakeyml khi bạn gọi chú thích này ..

đây là cách của tôi:

@Configuration
@EnableAutoConfiguration
public class AppContextTest {
}

đây là bài kiểm tra của tôi:

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(
        classes = {
                AppContextTest.class,
                JaxbConfiguration.class,
        }
)

public class JaxbTest {
//tests are ommited
}

0

Tôi cần đọc một số thuộc tính trong mã của mình và điều này hoạt động với spring-boot 1.3.0.

@Autowired
private ConfigurableListableBeanFactory beanFactory;

// access a properties.yml file like properties
@Bean
public PropertySource properties() {
    PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer = new PropertySourcesPlaceholderConfigurer();
    YamlPropertiesFactoryBean yaml = new YamlPropertiesFactoryBean();
    yaml.setResources(new ClassPathResource("properties.yml"));
    propertySourcesPlaceholderConfigurer.setProperties(yaml.getObject());
    // properties need to be processed by beanfactory to be accessible after
    propertySourcesPlaceholderConfigurer.postProcessBeanFactory(beanFactory);
    return propertySourcesPlaceholderConfigurer.getAppliedPropertySources().get(PropertySourcesPlaceholderConfigurer.LOCAL_PROPERTIES_PROPERTY_SOURCE_NAME);
}

0

Đang tải tệp yml tùy chỉnh với nhiều cấu hình cấu hình trong Spring Boot.

1) Thêm bean thuộc tính với khởi động SpringBootApplication như sau

@SpringBootApplication
@ComponentScan({"com.example.as.*"})
public class TestApplication {

    public static void main(String[] args) {
        SpringApplication.run(TestApplication.class, args);
    }

    @Bean
    @Profile("dev")
    public PropertySourcesPlaceholderConfigurer propertiesStage() {
        return properties("dev");
    }

    @Bean
    @Profile("stage")
    public PropertySourcesPlaceholderConfigurer propertiesDev() {
        return properties("stage");
    }

    @Bean
    @Profile("default")
    public PropertySourcesPlaceholderConfigurer propertiesDefault() {
        return properties("default");

    }
   /**
    * Update custom specific yml file with profile configuration.
    * @param profile
    * @return
    */
    public static PropertySourcesPlaceholderConfigurer properties(String profile) {
       PropertySourcesPlaceholderConfigurer propertyConfig = null;
       YamlPropertiesFactoryBean yaml  = null;

       propertyConfig  = new PropertySourcesPlaceholderConfigurer();
       yaml = new YamlPropertiesFactoryBean();
       yaml.setDocumentMatchers(new SpringProfileDocumentMatcher(profile));// load profile filter.
       yaml.setResources(new ClassPathResource("env_config/test-service-config.yml"));
       propertyConfig.setProperties(yaml.getObject());
       return propertyConfig;
    }
}

2) Định cấu hình đối tượng Java pojo như sau

@Component
@JsonIgnoreProperties(ignoreUnknown = true)
@JsonInclude(Include.NON_NULL)
@ConfigurationProperties(prefix = "test-service")
public class TestConfig {

    @JsonProperty("id") 
    private  String id;

    @JsonProperty("name")
    private String name;

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }   

}

3) Tạo yml tùy chỉnh (và đặt nó dưới đường dẫn tài nguyên như sau, Tên tệp YML: test-service-config.yml

Ví dụ: Cấu hình trong tệp yml.

test-service: 
    id: default_id
    name: Default application config
---
spring:
  profiles: dev

test-service: 
  id: dev_id
  name: dev application config

--- 
spring:
  profiles: stage

test-service: 
  id: stage_id
  name: stage application config

0

Tôi đã gặp phải một tình huống cụ thể mà tôi không thể tải lớp @ConfigurationProperties do đặt tên thuộc tính tệp tùy chỉnh. Cuối cùng, điều duy nhất hoạt động là (cảm ơn @Mateusz Balbus):

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.List;

import org.apache.commons.io.IOUtils;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.bind.Bindable;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.boot.env.YamlPropertySourceLoader;
import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.PropertySource;
import org.springframework.core.io.Resource;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner;

@RunWith(SpringRunner.class)
@ContextConfiguration(classes = {MyTest.ContextConfiguration.class})
public class MyTest {

    @TestConfiguration
    public static class ContextConfiguration {

        @Autowired
        ApplicationContext applicationContext;

        @Bean
        public ConfigurationPropertiesBean myConfigurationPropertiesBean() throws IOException {
            Resource resource = applicationContext.getResource("classpath:my-properties-file.yml");

            YamlPropertySourceLoader sourceLoader = new YamlPropertySourceLoader();
            List<PropertySource<?>> loadedSources = sourceLoader.load("yamlTestProperties", resource);
            PropertySource<?> yamlTestProperties = loadedSources.get(0);
            ConfigurableEnvironment configurableEnvironment = (ConfigurableEnvironment)applicationContext.getEnvironment();
            configurableEnvironment.getPropertySources().addFirst(yamlTestProperties);

            Binder binder = Binder.get(applicationContext.getEnvironment());
            ConfigurationPropertiesBean configurationPropertiesBean = binder.bind("my-properties-file-prefix", Bindable.of(ConfigurationPropertiesBean.class)).get();
            return configurationPropertiesBean;
        }

    }

    @Autowired
    ConfigurationPropertiesBean configurationPropertiesBean;

    @Test
    public void test() {

        configurationPropertiesBean.getMyProperty();

    }

}

0
<dependency>
  <groupId>com.github.yingzhuo</groupId>
  <artifactId>spring-boot-stater-env</artifactId>
  <version>0.0.3</version>
</dependency>

Chào mừng bạn đến sử dụng thư viện của tôi. Bây giờ yaml , toml , hocon đã được hỗ trợ.

Nguồn: github.com


0

Đây không phải là câu trả lời cho câu hỏi ban đầu, mà là một giải pháp thay thế cho nhu cầu có cấu hình khác trong thử nghiệm ...

Thay vì @PropertySourcebạn có thể sử dụng -Dspring.config.additional-location=classpath:application-tests.yml.

Hãy lưu ý, hậu tố testsđó không có nghĩa là hồ sơ ...

Trong một tệp YAML đó, người ta có thể chỉ định nhiều cấu hình, có thể kế thừa lẫn nhau, đọc thêm tại đây - Giải quyết thuộc tính cho nhiều cấu hình Spring (cấu hình yaml)

Sau đó, bạn có thể chỉ định trong thử nghiệm của mình, rằng các cấu hình đang hoạt động (đang sử dụng @ActiveProfiles("profile1,profile2")) là profile1,profile2nơi profile2sẽ chỉ ghi đè (một số, không cần ghi đè tất cả) các thuộc tính từ đó profile1.


0

Tôi đã thử tất cả các câu hỏi được liệt kê, nhưng tất cả chúng đều không phù hợp với nhiệm vụ của tôi: sử dụng tệp yaml cụ thể cho một số bài kiểm tra đơn vị. Trong trường hợp của tôi, nó hoạt động như thế này:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(initializers = {ConfigFileApplicationContextInitializer.class})
@TestPropertySource(properties = {"spring.config.location=file:../path/to/specific/config/application.yml"})
public class SomeTest {


    @Value("${my.property.value:#{null}}")
    private String value;

    @Test
    public void test() {
        System.out.println("value = " + value);
    }

}

-6

Không cần thêm như YamlPropertyLoaderFactory hoặc YamlFileApplicationContextInitializer. Bạn nên chuyển đổi ý tưởng của mình. giống như dự án mùa xuân chung. Bạn biết đấy, không sử dụng cấu hình Java. Chỉ * .xml

Làm theo các bước sau:

Chỉ cần thêm applicationContext.xml như

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd"
       default-autowire="byName">

    <context:property-placeholder location="classpath*:*.yml"/>
</beans>

sau đó thêm

@ImportResource({"classpath:applicationContext.xml"})

cho của bạn ApplicationMainClass.

Điều này có thể giúp quét application-test.yml của bạn

db:
  url: jdbc:oracle:thin:@pathToMyDb
  username: someUser
  password: fakePassword

Câu hỏi đặt ra có liên quan với yaml (đó là IMHO một phương pháp cấu hình tốt)
Aldebaran-ms
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.