Spring Property Injection Concepts: Difference between revisions

From NovaOrdis Knowledge Base
Jump to navigation Jump to search
(Replaced content with "=Internal= * Spring Framework {{Internal|tmp|tmp}}")
Line 3: Line 3:
* [[Spring_Framework#Spring_Framework_Core_Technologies_Concepts|Spring Framework]]
* [[Spring_Framework#Spring_Framework_Core_Technologies_Concepts|Spring Framework]]


=TODO=
{{Internal|tmp|tmp}}
 
<font color=darkgray>
* When done, do one more pass and reconcile the fact that this is a Spring Framework article but we need spring boot for @ConfigurationProperties.
* Make clear that @ConfigurationProperties is a SpringBoot thing and it does now work (by default) in Spring Framework.
**What can it be done to make it work – in involves registering and triggering a org.springframework.boot.context.config. ConfigFileApplicationListener.
** As soon as I figure it, try it with playground/spring/testing/02-integration-test-with-config-property-holder Main, and then try to make it work from the test.
** We need to make it work from a library that does not rely on a SpringBoot runtime for testing, because it will be used in SpringBoot runtimes.
* Diagram with property sources and their relative priority, including the configuration service (chapter 14).
* Augment https://kb.novaordis.com/index.php/Spring_Boot_Concepts#Autoconfiguration with specialized sections on property injection for DataSources, etc.
* How do I read the effective values of all configuration properties during testing and at runtime?
* You can figure out the active profile from within a Spring application by @Autowire-ing org.springframework.core.env.Environment and inspecting it.
* https://docs.spring.io/spring-boot/docs/2.1.0.RELEASE/reference/htmlsingle/#configuration-metadata-annotation-processor
* https://docs.spring.io/spring-boot/docs/2.0.6.RELEASE/reference/html/configuration-metadata.html#configuration-metadata-annotation-processor
</font>
 
=Overview=
 
Spring has two different, but related kinds of configurations: '''bean wiring''', which refers to declaring application components and their dependencies, and how those dependencies should be injected into each other, as described in the [[Spring_Dependency_Injection_and_Inversion_of_Control_Container_Concepts|Dependency Injection and Inversion of Control Container]] section, and '''property injection''', which is the process that allows external pieces of data, known as [[#Configuration_Property|configuration properties]], to be provided to the application runtime at startup or while it is running, in form of Java system properties, environment variables and by other means. This section addresses property injection concepts.
 
=Dependencies=
 
<syntaxhighlight lang='groovy'>
dependencies {
    implementation('org.springframework.boot:spring-boot')
}
</syntaxhighlight>
 
This declaration assumes we're using [[Spring_Framework#Spring_Framework_Dependency_Management|Spring Framework Dependency Management System]], for Spring Framework-based libraries, or we're building a SpringBoot application.
 
=<span id='Configuration_Property'></span>Configuration Properties=
 
'''Configuration properties''' are pieces of data coded into Spring components using the [[JavaBeans#Overview|JavaBeans property conventions]]. They usually have a corresponding member variable, a getter and a setter. The Spring Framework provides conventions and mechanisms to automatically inject values into configuration properties, while those values come from several different [[#Property_Sources|property sources]].
 
=<span id='Spring_Environment'></span>The Environment Abstraction=
 
The Spring environment is the only source of [[#Configuration_Property|configuration properties]] for components needing them. The environment abstracts the [[#Property_Sources|origins of properties]] and applies [[#Precedence|precedence rules]] when the same property is specified in more than one source. It pulls property values from several sources: [[#Java_System_Properties|Java system properties]], [[#Command-Line_Arguments|command-line arguments]], [[#Environment_Variables|environment variables]], [[#Property_Configuration_Files|configuration files]], etc. The property sources are described in detail in their [[#Property_Sources|corresponding sections]], below.
 
<font color=darkgray>TODO: Environment can be injected and inspected.</font>
 
=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|Spring environment]]. They can be injected inot 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.
 
<span id='Configuration_Property_Holder_and_Validation'></span>Configuration property holder classes are a good location to apply [[Spring_Validation_Concepts#JavaBean_Validation_and_Configuration_Property_Holders|JavaBeans Validation]] annotations.
 
<syntaxhighlight lang='java'>
@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;
}
</syntaxhighlight>
 
Conventionally, hardcoded defaults are specified as initialization values for the member variables.
 
The property values can then be set externally in a configuration file as follows:
 
<syntaxhighlight lang='yaml'>
playground:
  spring:
    pi:
      size: 25
      color: "red"
</syntaxhighlight>
 
The full playground project is available here: {{External|[https://github.com/ovidiuf/playground/tree/master/spring/property-injection/01-environment-and-property-sources Playground Property Injection Example]}}
 
===@ConfigurationProperties===
 
<font color=darkgray>
This is a Spring Boot annotation, which works by default, without any additional se up, in a Spring Boot environment.
 
Internally, property files [[application.properties]] and/or [[application.yml]] are looked for in known locations (classpath:, file:./, classpath:config, file:./config/:) and loaded. The class responsible for doing that is 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]].
</font>
 
==Exposing Individual Configuration Properties on Components==
 
Individual properties can also be injected directly into established components, as shown:
<syntaxhighlight lang='java'>
@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;
  }
  ...
}
</syntaxhighlight>
 
The property values can then be set externally in a configuration file as follows:
 
<syntaxhighlight lang='yaml'>
playground:
  spring:
    pi:
      weight: 30
</syntaxhighlight>
 
However, if you have the option, bundling configuration properties into [[#Configuration_Property_Holders|configuration property holders]] is generally a better approach, for the reasons presented in that section.
 
==Configuration Property Metadata==
 
==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 environment]].
 
=Property Sources=
 
==Java System Properties==
 
==Command-Line Arguments==
 
java -jar ... --<''property.name''>=<''value''>
Example:
java -jar ... --server.port=9999
 
==Environment Variables==
 
The naming style should accommodate restrictions placed on environment variable names by the operating system.
 
export SERVER_PORT=9999
 
==Property Configuration Files==
 
===application.properties===
 
{{Internal|application.properties|application.properties}}
 
==Configuration Service==
 
The configuration service is also referred to as configuration server.
 
==Precedence==
 
==Configuration Property Variables==
 
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:
 
<syntaxhighlight lang='yaml'>
some:
  attribute: "bright"
 
playground:
  spring:
    pi:
      color: "${some.attribute} red"
</syntaxhighlight>
 
==Configuration Property Metadata==
 
Some IDEs attempt to find metadata about configuration properties, and display a warning around them if such metadata is not found.
 
:[[Image: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:
 
<syntaxhighlight lang='json'>
{
  "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'."
    }
  ]
}
</syntaxhighlight>
 
In IntelliJ IDEA, you will also need to re-run Spring Boot Configuration Annotation Processor to update generated metadata.
 
=Profiles=
 
A profile is conditional configuration where different beans, configuration classes and configuration properties are applied or ignored based on what profiles are active at runtime. Profiles are a solution for the problem posed by the fact that configuration details differ when applications are deployed in different runtime environments. The classical example is the database URL and credentials: the testing environment uses a different database than the production environment. One way to address the problem is to configure these details with environment variables. Another option is to use profiles.
 
More than one profile can be active at a time.
 
==Declaring Profile-Based Configuration==
 
==Activating Profiles==

Revision as of 05:15, 2 December 2018