Thuộc tính với Spring và Spring Boot

1. Khái quát chung

Hướng dẫn này sẽ chỉ ra cách thiết lập và sử dụng các thuộc tính trong Spring thông qua cấu hình Java và @PropertySource.

Chúng ta cũng sẽ xem các thuộc tính hoạt động như thế nào trong Spring Boot.

2. Đăng ký Tệp Thuộc tính qua Chú thích

Spring 3.1 cũng giới thiệu chú thích @PropertySource mới như một cơ chế thuận tiện để thêm các nguồn thuộc tính vào môi trường.

Chúng tôi có thể sử dụng chú thích này cùng với chú thích @Configuration :

@Configuration @PropertySource("classpath:foo.properties") public class PropertiesWithJavaConfig { //... }

Một cách rất hữu ích khác để đăng ký tệp thuộc tính mới là sử dụng trình giữ chỗ, cho phép chúng tôi chọn động tệp phù hợp trong thời gian chạy :

@PropertySource({ "classpath:persistence-${envTarget:mysql}.properties" }) ...

2.1. Xác định Nhiều Vị trí Thuộc tính

Các @PropertySource chú thích là lặp lại theo Java 8 công ước. Do đó, nếu chúng tôi đang sử dụng Java 8 trở lên, chúng tôi có thể sử dụng chú thích này để xác định nhiều vị trí thuộc tính:

@PropertySource("classpath:foo.properties") @PropertySource("classpath:bar.properties") public class PropertiesWithJavaConfig { //... }

Tất nhiên, chúng ta cũng có thể sử dụng chú thích @PropertySources và chỉ định một mảng của @PropertySource . Điều này hoạt động trong bất kỳ phiên bản Java nào được hỗ trợ, không chỉ trong Java 8 trở lên:

@PropertySources({ @PropertySource("classpath:foo.properties"), @PropertySource("classpath:bar.properties") }) public class PropertiesWithJavaConfig { //... }

Trong cả hai trường hợp, cần lưu ý rằng trong trường hợp có xung đột tên thuộc tính, việc đọc nguồn cuối cùng sẽ được ưu tiên.

3. Sử dụng / Tiêm thuộc tính

Việc chèn một thuộc tính với chú thích @Value rất đơn giản:

@Value( "${jdbc.url}" ) private String jdbcUrl;

Chúng tôi cũng có thể chỉ định một giá trị mặc định cho thuộc tính:

@Value( "${jdbc.url:aDefaultUrl}" ) private String jdbcUrl;

PropertySourcesPlaceholderConfigurer mới được thêm vào Spring 3.1 giải quyết các trình giữ chỗ $ {…} trong các giá trị thuộc tính định nghĩa bean và chú thích @Value .

Cuối cùng, chúng ta có thể lấy giá trị của một thuộc tính bằng API Môi trường :

@Autowired private Environment env; ... dataSource.setUrl(env.getProperty("jdbc.url"));

4. Thuộc tính với Spring Boot

Trước khi đi vào các tùy chọn cấu hình nâng cao hơn cho các thuộc tính, chúng ta hãy dành thời gian xem xét các hỗ trợ thuộc tính mới trong Spring Boot.

Nói chung, hỗ trợ mới này liên quan đến cấu hình ít hơn so với Spring tiêu chuẩn , tất nhiên đây là một trong những mục tiêu chính của Boot.

4.1. application.properties: Tệp Thuộc tính Mặc định

Boot áp dụng quy ước điển hình của nó đối với cách tiếp cận cấu hình cho các tệp thuộc tính. Điều này có nghĩa là chúng ta có thể chỉ cần đặt một tệp application.properties vào thư mục src / main / resources và nó sẽ được tự động phát hiện . Sau đó, chúng tôi có thể đưa vào bất kỳ thuộc tính nào được tải từ nó như bình thường.

Vì vậy, bằng cách sử dụng tệp mặc định này, chúng tôi không phải đăng ký Nguồn tài sản một cách rõ ràng hoặc thậm chí cung cấp đường dẫn đến tệp thuộc tính.

Chúng tôi cũng có thể định cấu hình một tệp khác trong thời gian chạy nếu chúng tôi cần, bằng cách sử dụng thuộc tính môi trường:

java -jar app.jar --spring.config.location=classpath:/another-location.properties

Kể từ Spring Boot 2.3, chúng tôi cũng có thể chỉ định vị trí ký tự đại diện cho các tệp cấu hình .

Ví dụ, chúng ta có thể đặt thuộc tính spring.config.location thành config / * / :

java -jar app.jar --spring.config.location=config/*/

Bằng cách này, Spring Boot sẽ tìm kiếm các tệp cấu hình khớp với mẫu thư mục config / * / bên ngoài tệp jar của chúng ta. Điều này có ích khi chúng ta có nhiều nguồn thuộc tính cấu hình.

Kể từ phiên bản 2.4.0 , Spring Boot hỗ trợ sử dụng các tệp thuộc tính đa tài liệu , tương tự như YAML thực hiện theo thiết kế:

baeldung.customProperty=defaultValue #--- baeldung.customProperty=overriddenValue

Lưu ý rằng đối với các tệp thuộc tính, ký hiệu ba dấu gạch ngang được đặt trước một ký tự chú thích ( # ).

4.2. Tệp thuộc tính dành riêng cho môi trường

Nếu chúng ta cần nhắm mục tiêu các môi trường khác nhau, có một cơ chế tích hợp cho điều đó trong Boot.

Chúng ta có thể chỉ cần xác định một tệp application-enosystem.properties trong thư mục src / main / resources , và sau đó thiết lập một cấu hình Spring có cùng tên môi trường.

Ví dụ: nếu chúng ta xác định một môi trường “staging”, điều đó có nghĩa là chúng ta sẽ phải xác định một staging profile và sau đó là application-staging.properties .

Tệp env này sẽ được tải và sẽ được ưu tiên hơn tệp thuộc tính mặc định. Lưu ý rằng tệp mặc định sẽ vẫn được tải, chỉ là khi có xung đột thuộc tính, tệp thuộc tính dành riêng cho môi trường sẽ được ưu tiên hơn.

4.3. Tệp thuộc tính thử nghiệm cụ thể

Chúng tôi cũng có thể có yêu cầu sử dụng các giá trị thuộc tính khác nhau khi ứng dụng của chúng tôi đang được thử nghiệm.

Spring Boot handles this for us by looking in our src/test/resources directory during a test run. Again, default properties will still be injectable as normal but will be overridden by these if there is a collision.

4.4. The @TestPropertySource Annotation

If we need more granular control over test properties, then we can use the @TestPropertySource annotation.

This allows us to set test properties for a specific test context, taking precedence over the default property sources:

@RunWith(SpringRunner.class) @TestPropertySource("/foo.properties") public class FilePropertyInjectionUnitTest { @Value("${foo}") private String foo; @Test public void whenFilePropertyProvided_thenProperlyInjected() { assertThat(foo).isEqualTo("bar"); } }

If we don't want to use a file, we can specify names and values directly:

@RunWith(SpringRunner.class) @TestPropertySource(properties = {"foo=bar"}) public class PropertyInjectionUnitTest { @Value("${foo}") private String foo; @Test public void whenPropertyProvided_thenProperlyInjected() { assertThat(foo).isEqualTo("bar"); } }

We can also achieve a similar effect using the properties argument of the @SpringBootTest annotation:

@RunWith(SpringRunner.class) @SpringBootTest( properties = {"foo=bar"}, classes = SpringBootPropertiesTestApplication.class) public class SpringBootPropertyInjectionIntegrationTest { @Value("${foo}") private String foo; @Test public void whenSpringBootPropertyProvided_thenProperlyInjected() { assertThat(foo).isEqualTo("bar"); } }

4.5. Hierarchical Properties

If we have properties that are grouped together, we can make use of the @ConfigurationProperties annotation, which will map these property hierarchies into Java objects graphs.

Let's take some properties used to configure a database connection:

database.url=jdbc:postgresql:/localhost:5432/instance database.username=foo database.password=bar

And then let's use the annotation to map them to a database object:

@ConfigurationProperties(prefix = "database") public class Database { String url; String username; String password; // standard getters and setters }

Spring Boot applies it's convention over configuration approach again, automatically mapping between property names and their corresponding fields. All that we need to supply is the property prefix.

If you want to dig deeper into configuration properties, have a look at our in-depth article.

4.6. Alternative: YAML Files

Spring also supports YAML files.

All the same naming rules apply for test-specific, environment-specific, and default property files. The only difference is the file extension and a dependency on the SnakeYAML library being on our classpath.

YAML is particularly good for hierarchical property storage; the following property file:

database.url=jdbc:postgresql:/localhost:5432/instance database.username=foo database.password=bar secret: foo

is synonymous with the following YAML file:

database: url: jdbc:postgresql:/localhost:5432/instance username: foo password: bar secret: foo

It's also worth mentioning that YAML files do not support the @PropertySource annotation, so if we need to use this annotation, it would constrain us to using a properties file.

Another remarkable point is that in version 2.4.0 Spring Boot changed the way in which properties are loaded from multi-document YAML files. Previously, the order in which they were added was based on the profile activation order. With the new version, however, the framework follows the same ordering rules that we indicated earlier for .properties files; properties declared lower in the file will simply override those higher up.

Additionally, in this version profiles can no longer be activated from profile-specific documents, making the outcome clearer and more predictable.

4.7. Importing Additional Configuration Files

Prior to version 2.4.0, Spring Boot allowed including additional configuration files using the spring.config.location and spring.config.additional-location properties, but they had certain limitations. For instance, they had to be defined before starting the application (as environment or system properties, or using command-line arguments) as they were used early in the process.

In the mentioned version, we can use the spring.config.import property within the application.properties or application.yml file to easily include additional files. This property supports some interesting features:

  • adding several files or directories
  • the files can be loaded either from the classpath or from an external directory
  • indicating if the startup process should fail if a file is not found, or if it's an optional file
  • importing extensionless files

Let's see a valid example:

spring.config.import=classpath:additional-application.properties, classpath:additional-application[.yml], optional:file:./external.properties, classpath:additional-application-properties/

Note: here we formatted this property using line breaks just for clarity.

Spring will treat imports as a new document inserted immediately below the import declaration.

4.8. Properties From Command Line Arguments

Besides using files, we can pass properties directly on the command line:

java -jar app.jar --property="value"

We can also do this via system properties, which are provided before the -jar command rather than after it:

java -Dproperty.name="value" -jar app.jar

4.9. Properties From Environment Variables

Spring Boot will also detect environment variables, treating them as properties:

export name=value java -jar app.jar 

4.10. Randomization of Property Values

If we don't want determinist property values, we can use RandomValuePropertySource to randomize the values of properties:

random.number=${random.int} random.long=${random.long} random.uuid=${random.uuid}

4.11. Additional Types of Property Sources

Spring Boot supports a multitude of property sources, implementing a well-thought-out ordering to allow sensible overriding. It's worth consulting the official documentation, which goes further than the scope of this article.

5. Configuration Using Raw Beans — the PropertySourcesPlaceholderConfigurer

Besides the convenient methods of getting properties into Spring, we can also define and regiter the property configuration bean manually.

Working with the PropertySourcesPlaceholderConfigurer gives us full control over the configuration, with the downside of being more verbose and most of the time, unnecessary.

Let's see how we can define this bean using Java configuration:

@Bean public static PropertySourcesPlaceholderConfigurer properties(){ PropertySourcesPlaceholderConfigurer pspc = new PropertySourcesPlaceholderConfigurer(); Resource[] resources = new ClassPathResource[ ] { new ClassPathResource( "foo.properties" ) }; pspc.setLocations( resources ); pspc.setIgnoreUnresolvablePlaceholders( true ); return pspc; }

6. Properties in Parent-Child Contexts

This question comes up again and again: What happens when our web application has a parent and a child context? The parent context may have some common core functionality and beans, and then one (or multiple) child contexts, maybe containing servlet-specific beans.

In that case, what's the best way to define properties files and include them in these contexts? And how to best retrieve these properties from Spring?

We'll give a simple breakdown.

If the file is defined in the Parent context:

  • @Value works in Child context: YES
  • @Value works in Parent context: YES
  • environment.getProperty in Child context: YES
  • environment.getProperty in Parent context: YES

If the file is defined in the Child context:

  • @Value works in Child context: YES
  • @Value works in Parent context: NO
  • environment.getProperty in Child context: YES
  • environment.getProperty in Parent context: NO

7. Conclusion

This article showed several examples of working with properties and properties files in Spring.

Như mọi khi, toàn bộ mã hỗ trợ bài viết có sẵn trên GitHub.