Spring Boot Property Injection: Difference between revisions

From NovaOrdis Knowledge Base
Jump to navigation Jump to search
 
(45 intermediate revisions by the same user not shown)
Line 2: Line 2:


* [[Spring_Property_Injection_Concepts#Reading_Properties_from_Environment|Spring Property Injection Concepts]]
* [[Spring_Property_Injection_Concepts#Reading_Properties_from_Environment|Spring Property Injection Concepts]]
* [[Spring Boot Concepts#Spring_Boot_Property_Injection|Spring Boot Property Injection]]
* [[Spring Boot Concepts#Spring_Boot_Property_Injection|Spring Boot Concepts]]


=Overview=
=Overview=
Line 46: Line 46:


Conventionally, hardcoded defaults are specified as initialization values for the member variables.
Conventionally, hardcoded defaults are specified as initialization values for the member variables.
{{Warn|The private member variables must be exposed via public mutators, or the property holder should be declared a Lombok [[Lombok#.40Data|@Data]], otherwise Spring Boot runtime has no way of setting the values, even if they are found in the O/S environment or as system properties.}}


The property values can then be set externally in a configuration file ([[application.properties]], [[application.yml]], etc.) as follows:
The property values can then be set externally in a configuration file ([[application.properties]], [[application.yml]], etc.) as follows:
Line 57: Line 59:
</syntaxhighlight>
</syntaxhighlight>


The full playground project that demonstrates property holders is available here: {{External|[https://github.com/ovidiuf/playground/tree/master/spring/property-injection/02-%40ConfigurtionProperties Playground Property Injection Example]}}
The full playground project that demonstrates property holders is available here: {{External|[https://github.com/ovidiuf/playground/tree/master/spring/spring-boot/02-property-injection-with-%40ConfigurationProperties Playground Spring Boot @ConfigurationProperties Example]}}


===@ConfigurationProperties===
===@ConfigurationProperties===
Line 67: Line 69:
The mechanism that ends with [[application.properties]]/[[application.yml]]-declared properties being injected in configuration holders consists of two steps:
The mechanism that ends with [[application.properties]]/[[application.yml]]-declared properties being injected in configuration holders consists of two steps:


1. The [[application.properties]], [[application.yml]] and other configuration files placed in conventional locations are read and parsed by the [https://docs.spring.io/spring-boot/docs/current/api/org/springframework/boot/context/config/ConfigFileApplicationListener.html ConfigFileApplicationListener] [[Spring_Framework_Event_Handling#Application_Event_Listener|application event listener]] instance. The listener looks at [[application.properties]] and/or [[application.yml]] in several known locations: classpath:, file:./, classpath:config, file:./config/:. The listener is triggered by [https://docs.spring.io/spring-boot/docs/current/api/org/springframework/boot/context/event/ApplicationEnvironmentPreparedEvent.html ApplicationEnvironmentPreparedEvent] or [https://docs.spring.io/spring-boot/docs/current/api/org/springframework/boot/context/event/ApplicationPreparedEvent.html ApplicationPreparedEvent] instances.
1. The [[application.properties]], [[application.yml]] and other configuration files placed in conventional locations (classpath:, file:./, classpath:config, file:./config/:) are read and parsed by the [https://docs.spring.io/spring-boot/docs/current/api/org/springframework/boot/context/config/ConfigFileApplicationListener.html ConfigFileApplicationListener] [[Spring_Framework_Event_Handling#Application_Event_Listener|application event listener]] instance. The listener is triggered by [https://docs.spring.io/spring-boot/docs/current/api/org/springframework/boot/context/event/ApplicationEnvironmentPreparedEvent.html ApplicationEnvironmentPreparedEvent] or [https://docs.spring.io/spring-boot/docs/current/api/org/springframework/boot/context/event/ApplicationPreparedEvent.html ApplicationPreparedEvent] instances.
 
2. Once a [[@ConfigurationProperties]]-annotated bean is loaded, the [https://docs.spring.io/spring-boot/docs/current/api/org/springframework/boot/context/properties/ConfigurationPropertiesBindingPostProcessor.html ConfigurationPropertiesBindingPostProcessor] post-processor is applied. The post-processor pulls the property values from the environment and injects them into the component. This process in called "binding".
 
====@ConfigurationProperties Support outside Spring Boot====
 
The following Playground example demonstrates usage of [[@ConfigurationProperties]] outside of Spring Boot. The dependencies are still needed, but we don't need a Spring Boot runtime to declare property in [[application.properties]]/[[application.yml]] and bind them to property holders. The idea behind the example is to use the [https://docs.spring.io/spring-boot/docs/current/api/org/springframework/boot/context/config/ConfigFileApplicationListener.html ConfigFileApplicationListener] code to force configuration file processing - their content will be exposed as a property source in the environment. [[@ConfigurationProperties]] will be automatically handled as long as the "org.springframework.boot:spring-boot" Spring Boot dependency is in the classpath. [[Using_Property_Configuration_Holders_with_Plain_Spring_TestContext_Framework#Custom_Initializer|This]] article explains provides an example of how the properties can be explicitly injected with a BeanPostProcessor, if necessary. If YAML files need to be added, then "org.yaml:[[snakeyaml]]:1.23" or newer should be added to the classpath.
 
<syntaxhighlight lang='java'>
import org.springframework.boot.SpringApplication;
import org.springframework.boot.context.config.ConfigFileApplicationListener;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
...
final AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
applicationContext.scan("playground.spring");
new ConfigFileApplicationListener().postProcessEnvironment(applicationContext.getEnvironment(), new SpringApplication());
applicationContext.refresh();
</syntaxhighlight>
 
{{External|[https://github.com/ovidiuf/playground/tree/master/spring/property-injection/%40ConfigurationProperties-outside-spring-boot Playground @ConfigurationProperties Support outside Spring Boot]}}
 
===@NestedConfigurationProperty===
 
[[@NestedConfigurationProperty]] can be used within the context of a class annotated with [[@ConfigurationProperties]] to declare nested properties. The Playground example linked below shows how.
 
{{External|[https://github.com/ovidiuf/playground/tree/master/spring/spring-boot/02.1-property-injection-with-%40ConfigurationProperties-and-%40NestedConfigurationProperty @NestedConfigurationProperty Playground Example]}}


==Using Property Configuration Holders with Plain Spring TestContext Framework==
==Using Property Configuration Holders with Plain Spring TestContext Framework==
Line 114: Line 141:
The beans that are automatically configured by the Spring Boot autoconfiguration. process are all configurable by properties drawn from the [[Spring_Property_Injection_Concepts#The_Environment_Abstraction|Spring environment]].
The beans that are automatically configured by the Spring Boot autoconfiguration. process are all configurable by properties drawn from the [[Spring_Property_Injection_Concepts#The_Environment_Abstraction|Spring environment]].


=<span id='Defining_Profile-Specific_Properties_in_application.yml'></span>Defining Profile-Specific Properties in application.yml|application.properties=
=Spring Boot and Profiles=
 
Also see: {{Internal|Spring_Property_Injection_Concepts#Spring_Boot_and_Profiles|Spring Property Injection}}
 
==Playground Example==
 
{{External|[https://github.com/ovidiuf/playground/tree/master/spring/spring-boot/03-spring-boot-profiles Playground Spring Boot Profile]}}
 
==<span id='Defining_Profile-Specific_Properties_in_application.yml'></span>Defining Profile-Specific Properties in application.yml|application.properties==


There are two ways of defining profile-specific properties in [[Application.yml#Defining_Profile-Specific_Properties|application.yml]]: using a [[#Use_a_File_per_Profile|file per profile]], or using [[#Use_Different_Sections_in_the_Same_File|different sections in the same file]]. In case of an [[application.properties]], profile-specific properties can only be defined if we use a [[#Use_a_File_per_Profile|file per profile]], the different-sections-in-the-same-file method is not available.
There are two ways of defining profile-specific properties in [[Application.yml#Defining_Profile-Specific_Properties|application.yml]]: using a [[#Use_a_File_per_Profile|file per profile]], or using [[#Use_Different_Sections_in_the_Same_File|different sections in the same file]]. In case of an [[application.properties]], profile-specific properties can only be defined if we use a [[#Use_a_File_per_Profile|file per profile]], the different-sections-in-the-same-file method is not available.


==Use a File per Profile==
===Use a File per Profile===


Create an YAML or a properties file that contains only the properties for a specific profile. The name of the file should follow the following convention:
Create an YAML or a properties file that contains only the properties for a specific profile. The name of the file should follow the following convention:
Line 124: Line 159:
  application-<''profile-name''>.yml|properties
  application-<''profile-name''>.yml|properties


==Use Different Sections in the Same File==
===Use Different Sections in the Same File===


This method only works for [[application.yml]] files. It involves placing profile-specific properties alongside non-profile-specific properties, separated by three hyphens and the "spring.profiles" property set to the name of the profile:
This method only works for [[application.yml]] files. It involves placing profile-specific properties alongside non-profile-specific properties, separated by three hyphens and the "spring.profiles" property set to the name of the profile:


<syntaxhighlight lang='yaml'>
<syntaxhighlight lang='yaml'>
TODO
playground.color: "red"
 
---
spring.profiles: "san_francisco"
 
playground.color: "yellow"
 
---
spring.profiles: "new_york"
 
playground.color: "green"
</syntaxhighlight>
</syntaxhighlight>


=Configuration Property Variables=
By using this syntax, the configuration file is divided into sections, each section containing property values for the corresponding profile. The initial section is not associated with any profile, therefore its properties are common to all profiles or are defaults if the active profile doesn't otherwise have the properties set.
 
==Active Profile==
 
Aside from the [[Spring_Property_Injection_Concepts#Active_Profile|conventional methods]] to specify the active profile, Spring Boot allows for a special command line syntax:
 
<syntaxhighlight lang='bash'>
java -jar app.jar --spring.profiles.active=prod
</syntaxhighlight>
 
==bootstrap.yml==
{{Internal|bootstrap.yml|bootstrap.yml}}
 
=Configuration Property Placeholders=


When setting properties, you aren’t limited to declaring their values as string and numeric values. Instead, property values can be derived from from other configuration properties, using the "${...}" (placeholder marker) syntax:
When setting properties, you aren’t limited to declaring their values as string and numeric values. Instead, property values can be derived from from other configuration properties, using the "${...}" (placeholder marker) syntax:
Line 147: Line 205:


Also see: {{Internal|Spring_Property_Injection_Concepts#Property_Placeholders|Property Placeholders}}
Also see: {{Internal|Spring_Property_Injection_Concepts#Property_Placeholders|Property Placeholders}}
==Default Values for Property Placeholders==
It is possible to specify in-line default values for property placeholders, with the ":<''default-value''>" syntax. These values are used when the property the placeholder is relying on does not exist in the environment:
<syntaxhighlight lang='yaml'>
some.color: "${some.other.color:blue}"
</syntaxhighlight>


=Configuration Property Metadata=
=Configuration Property Metadata=

Latest revision as of 18:03, 13 December 2018

Internal

Overview

Dependencies

dependencies {
    implementation('org.springframework.boot:spring-boot')
}

This declaration assumes we're using Spring Framework Dependency Management System, for Spring Framework-based libraries, or we're building a Spring Boot application.

Injecting Properties into Beans

Property injection is supported by the @ConfigurationProperties annotation. It can be used as shown below.

Configuration Property Holders

A common pattern used to handle injected configuration is to declare a configuration property holder class, whose sole purpose in the application is to be holder of configuration data. Such class bundles several configuration properties together, under a common property namespace. There is nothing special about configuration property holders. They are ordinary Spring components that have their properties injected from the Spring environment, by the virtue of @ConfigurationProperties annotations - to see how this annotation actually works, see @ConfigurationProperties below.

They can be injected into any other bean that needs those properties. One benefit of this approach is that it keeps configuration-specific details out of controllers and other application specific classes. Another benefit is that it makes it easy to share common configuration properties among several beans that may need this information. All configuration properties related to a certain piece of functionality are kept in a single place, as opposite to being scattered across several components, and if we need to add a new property or change an existing property, we do it in a single place.

Configuration property holder classes are also a good location to apply JavaBeans Validation annotations.

@Component
@ConfigurationProperties(prefix = "playground.spring.pi")
@Data
@Validated
public class MyPropertyConfiguration {

  public static final int DEFAULT_SIZE = 20;
  public static final String DEFAULT_COLOR = "blue";

  @Min(value = 5, message = "the size must be larger or equal than 5")
  @Max(value = 40, message = "the size must be smaller or equal than 40")
  private int size = DEFAULT_SIZE;
  private String color = DEFAULT_COLOR;
}

Conventionally, hardcoded defaults are specified as initialization values for the member variables.


The private member variables must be exposed via public mutators, or the property holder should be declared a Lombok @Data, otherwise Spring Boot runtime has no way of setting the values, even if they are found in the O/S environment or as system properties.

The property values can then be set externally in a configuration file (application.properties, application.yml, etc.) as follows:

playground:
  spring:
    pi:
      size: 25
      color: "red"

The full playground project that demonstrates property holders is available here:

Playground Spring Boot @ConfigurationProperties Example

@ConfigurationProperties

This is a Spring Boot annotation, which works by default, without any additional se up, in a Spring Boot environment.

@ConfigurationProperties Implementation

The mechanism that ends with application.properties/application.yml-declared properties being injected in configuration holders consists of two steps:

1. The application.properties, application.yml and other configuration files placed in conventional locations (classpath:, file:./, classpath:config, file:./config/:) are read and parsed by the ConfigFileApplicationListener application event listener instance. The listener is triggered by ApplicationEnvironmentPreparedEvent or ApplicationPreparedEvent instances.

2. Once a @ConfigurationProperties-annotated bean is loaded, the ConfigurationPropertiesBindingPostProcessor post-processor is applied. The post-processor pulls the property values from the environment and injects them into the component. This process in called "binding".

@ConfigurationProperties Support outside Spring Boot

The following Playground example demonstrates usage of @ConfigurationProperties outside of Spring Boot. The dependencies are still needed, but we don't need a Spring Boot runtime to declare property in application.properties/application.yml and bind them to property holders. The idea behind the example is to use the ConfigFileApplicationListener code to force configuration file processing - their content will be exposed as a property source in the environment. @ConfigurationProperties will be automatically handled as long as the "org.springframework.boot:spring-boot" Spring Boot dependency is in the classpath. This article explains provides an example of how the properties can be explicitly injected with a BeanPostProcessor, if necessary. If YAML files need to be added, then "org.yaml:snakeyaml:1.23" or newer should be added to the classpath.

import org.springframework.boot.SpringApplication;
import org.springframework.boot.context.config.ConfigFileApplicationListener;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
...
final AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
applicationContext.scan("playground.spring");
new ConfigFileApplicationListener().postProcessEnvironment(applicationContext.getEnvironment(), new SpringApplication());
applicationContext.refresh();
Playground @ConfigurationProperties Support outside Spring Boot

@NestedConfigurationProperty

@NestedConfigurationProperty can be used within the context of a class annotated with @ConfigurationProperties to declare nested properties. The Playground example linked below shows how.

@NestedConfigurationProperty Playground Example

Using Property Configuration Holders with Plain Spring TestContext Framework

Property configuration holders are a Spring Boot pattern. They require Spring Boot dependencies and runtime to function. However, they're useful, so this is a method to get them to work with plain TestContext Framework tests:

Using Property Configuration Holders with Plain Spring TestContext Framework

Exposing Individual Configuration Properties on Components

Individual properties can also be injected directly into established components, as shown:

@Component
@ConfigurationProperties(prefix = "playground.spring.pi")
public class MyComponent {

  public static final int DEFAULT_WEIGHT = 50;

  private int weight = DEFAULT_WEIGHT;

  public void setWeight(int weight) {

    this.weight = weight;
  }
  ...
}

The property values can then be set externally in a configuration file as follows:

playground:
  spring:
    pi:
      weight: 30

However, if you have the option, bundling configuration properties into configuration property holders is generally a better approach, for the reasons presented in that section.

@EnableConfigurationProperties

@EnableConfigurationProperties

Property Injection and Auto-Configuration

The beans that are automatically configured by the Spring Boot autoconfiguration. process are all configurable by properties drawn from the Spring environment.

Spring Boot and Profiles

Also see:

Spring Property Injection

Playground Example

Playground Spring Boot Profile

Defining Profile-Specific Properties in application.yml|application.properties

There are two ways of defining profile-specific properties in application.yml: using a file per profile, or using different sections in the same file. In case of an application.properties, profile-specific properties can only be defined if we use a file per profile, the different-sections-in-the-same-file method is not available.

Use a File per Profile

Create an YAML or a properties file that contains only the properties for a specific profile. The name of the file should follow the following convention:

application-<profile-name>.yml|properties

Use Different Sections in the Same File

This method only works for application.yml files. It involves placing profile-specific properties alongside non-profile-specific properties, separated by three hyphens and the "spring.profiles" property set to the name of the profile:

playground.color: "red"

---
spring.profiles: "san_francisco"

playground.color: "yellow"

---
spring.profiles: "new_york"

playground.color: "green"

By using this syntax, the configuration file is divided into sections, each section containing property values for the corresponding profile. The initial section is not associated with any profile, therefore its properties are common to all profiles or are defaults if the active profile doesn't otherwise have the properties set.

Active Profile

Aside from the conventional methods to specify the active profile, Spring Boot allows for a special command line syntax:

java -jar app.jar --spring.profiles.active=prod

bootstrap.yml

bootstrap.yml

Configuration Property Placeholders

When setting properties, you aren’t limited to declaring their values as string and numeric values. Instead, property values can be derived from from other configuration properties, using the "${...}" (placeholder marker) syntax:

some: 
  attribute: "bright"

playground:
  spring:
    pi:
      color: "${some.attribute} red"

Also see:

Property Placeholders

Default Values for Property Placeholders

It is possible to specify in-line default values for property placeholders, with the ":<default-value>" syntax. These values are used when the property the placeholder is relying on does not exist in the environment:

some.color: "${some.other.color:blue}"

Configuration Property Metadata

Some IDEs attempt to find metadata about configuration properties, and display a warning around them if such metadata is not found.

Configuration Property Metadata Before.png

Configuration property metadata is completely optional and doesn’t prevent configuration properties from working, but the metadata can be useful for providing some minimal documentation around the configuration properties in the IDE. A way to provide configuration property metadata is to provide a ./src/main/resources/META-INF/additional-spring-configuration-metadata.json file:

{
  "properties": [
    {
      "name": "some.attribute",
      "type": "java.lang.String",
      "description": "this is description for 'some.attribute'."
    },
    {
      "name": "playground.spring.pi.size",
      "type": "java.lang.Integer",
      "description": "this is description for 'playground.spring.pi.size'."
    },
    {
      "name": "playground.spring.pi.color",
      "type": "java.lang.String",
      "description": "this is description for 'playground.spring.pi.color'."
    },
    {
      "name": "playground.spring.pi.weight",
      "type": "java.lang.Integer",
      "description": "this is description for 'playground.spring.pi.weight'."
    }
   ]
}

In IntelliJ IDEA, you will also need to re-run Spring Boot Configuration Annotation Processor to update generated metadata.

TODO: https://docs.spring.io/spring-boot/docs/2.0.6.RELEASE/reference/html/configuration-metadata.html#configuration-metadata-annotation-processor.